diff --git a/.github/cml_gpu_test.yaml b/.github/cml_gpu_test.yaml new file mode 100644 index 000000000..3e898c10e --- /dev/null +++ b/.github/cml_gpu_test.yaml @@ -0,0 +1,48 @@ +name: CML Single GPU Test +on: [push] +jobs: + run: + runs-on: [self-hosted,cml,gpu] + steps: + - uses: actions/checkout@v2 + - name: cml_run + env: + repo_token: ${{ secrets.GITHUB_TOKEN }} + run: | + nvidia-smi + + python -m pip install --upgrade pip + pip install -e "." + pip install -r requirements/requirements.txt + pip install -r requirements/requirements-pytorch.txt + pip install -r requirements/requirements-jax.txt + pip install -r requirements/requirements-tf2.txt + pip install -r requirements/requirements-dev.txt + pip install -r requirements/requirements-gpu.txt + + find tutorials/future/jax/ -name '*.py' | while read f + do + python $f + if [ $? -ne 0 ] + then + exit 1 + fi + done + + find tutorials/future/torch/ -name '*.py' | while read f + do + python $f + if [ $? -ne 0 ] + then + exit 1 + fi + done + + find tutorials/future/tf2/ -name '*.py' | while read f + do + python $f + if [ $? -ne 0 ] + then + exit 1 + fi + done \ No newline at end of file diff --git a/.github/workflows/style_type_check.yml b/.github/workflows/style_type_check.yml new file mode 100644 index 000000000..619b1bcc0 --- /dev/null +++ b/.github/workflows/style_type_check.yml @@ -0,0 +1,47 @@ +name: Style and type checks + +on: + push: + paths: + - 'cleverhans/**/*' + - 'tutorials/**/*' + + pull_request: + paths: + - 'cleverhans/**/*' + - 'tutorials/**/*' + +jobs: + build: + + runs-on: ubuntu-latest + strategy: + matrix: + python-version: [3.6] + + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -e "." + pip install -r requirements/requirements.txt + pip install -r requirements/requirements-pytorch.txt + pip install -r requirements/requirements-tf2.txt + pip install -r requirements/requirements-jax.txt + pip install -r requirements/requirements-dev.txt + - name: Code reformat check + run: | + black --check cleverhans/ + black --check tutorials/ +# - name: flake8 linting check +# run: | +# flake8 cleverhans/ +# flake8 tutorials/ +# - name: Type check +# run: | +# mypy cleverhans/future/ diff --git a/.github/workflows/tests-pytorch.yml b/.github/workflows/tests-pytorch.yml new file mode 100644 index 000000000..30a171d00 --- /dev/null +++ b/.github/workflows/tests-pytorch.yml @@ -0,0 +1,28 @@ +name: PyTorch Tests + +on: + push: + paths: + - 'cleverhans/torch/**/*' + + pull_request: + paths: + - 'cleverhans/torch/**/*' + +jobs: + run: + runs-on: [self-hosted,cml,gpu] + steps: + - uses: actions/checkout@v2 + - name: cml_run + env: + repo_token: ${{ secrets.GITHUB_TOKEN }} + run: | + python -m pip install --upgrade pip + pip install -e "." + pip install -r requirements/requirements.txt + pip install -r requirements/requirements-pytorch.txt + pip install -r requirements/requirements-dev.txt + pip install -r requirements/requirements-gpu.txt + + pytest cleverhans/torch/tests diff --git a/.github/workflows/tutorials-jax.yml b/.github/workflows/tutorials-jax.yml new file mode 100644 index 000000000..8a8d74c94 --- /dev/null +++ b/.github/workflows/tutorials-jax.yml @@ -0,0 +1,37 @@ +name: JAX Tutorials + +on: + push: + paths: + - 'cleverhans/jax/**/*' + - 'tutorials/jax/**/*' + + pull_request: + paths: + - 'cleverhans/jax/**/*' + - 'tutorials/jax/**/*' + +jobs: + run: + runs-on: [self-hosted,cml,gpu] + steps: + - uses: actions/checkout@v2 + - name: cml_run + env: + repo_token: ${{ secrets.GITHUB_TOKEN }} + run: | + python -m pip install --upgrade pip + pip install -e "." + pip install -r requirements/requirements.txt + pip install -r requirements/requirements-jax.txt + pip install -r requirements/requirements-dev.txt + pip install -r requirements/requirements-gpu.txt + + find tutorials/jax/ -name '*.py' | while read f + do + python $f + if [ $? -ne 0 ] + then + exit 1 + fi + done diff --git a/.github/workflows/tutorials-pytorch.yml b/.github/workflows/tutorials-pytorch.yml new file mode 100644 index 000000000..e2d2c46f3 --- /dev/null +++ b/.github/workflows/tutorials-pytorch.yml @@ -0,0 +1,37 @@ +name: PyTorch Tutorials + +on: + push: + paths: + - 'cleverhans/torch/**/*' + - 'tutorials/torch/**/*' + + pull_request: + paths: + - 'cleverhans/torch/**/*' + - 'tutorials/torch/**/*' + +jobs: + run: + runs-on: [self-hosted,cml,gpu] + steps: + - uses: actions/checkout@v2 + - name: cml_run + env: + repo_token: ${{ secrets.GITHUB_TOKEN }} + run: | + python -m pip install --upgrade pip + pip install -e "." + pip install -r requirements/requirements.txt + pip install -r requirements/requirements-pytorch.txt + pip install -r requirements/requirements-dev.txt + pip install -r requirements/requirements-gpu.txt + + find tutorials/torch/ -name '*.py' | while read f + do + python $f + if [ $? -ne 0 ] + then + exit 1 + fi + done diff --git a/.github/workflows/tutorials-tf2.yml b/.github/workflows/tutorials-tf2.yml new file mode 100644 index 000000000..993b0ffe4 --- /dev/null +++ b/.github/workflows/tutorials-tf2.yml @@ -0,0 +1,37 @@ +name: TensorFlow 2 Tutorials + +on: + push: + paths: + - 'cleverhans/tf2/**/*' + - 'tutorials/tf2/**/*' + + pull_request: + paths: + - 'cleverhans/tf2/**/*' + - 'tutorials/tf2/**/*' + +jobs: + run: + runs-on: [self-hosted,cml,gpu] + steps: + - uses: actions/checkout@v2 + - name: cml_run + env: + repo_token: ${{ secrets.GITHUB_TOKEN }} + run: | + python -m pip install --upgrade pip + pip install -e "." + pip install -r requirements/requirements.txt + pip install -r requirements/requirements-tf2.txt + pip install -r requirements/requirements-dev.txt + pip install -r requirements/requirements-gpu.txt + + find tutorials/tf2/ -name '*.py' | while read f + do + python $f + if [ $? -ne 0 ] + then + exit 1 + fi + done diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 000000000..b355efd02 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,5 @@ +repos: +- repo: https://github.com/psf/black + rev: 20.8b1 + hooks: + - id: black diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index ce875d6f0..000000000 --- a/.travis.yml +++ /dev/null @@ -1,86 +0,0 @@ -# adapted from https://github.com/fchollet/keras/blob/master/.travis.yml -sudo: required -dist: trusty -language: python -python: - - 3.5 -env: - - KERAS_BACKEND=tensorflow TENSORFLOW_V=1.8.0 PYTORCH=True - - KERAS_BACKEND=tensorflow TENSORFLOW_V=1.12.0 PYTORCH=True - - KERAS_BACKEND=tensorflow TENSORFLOW_V=1.8.0 PYTORCH=True - -install: - # code below is taken from http://conda.pydata.org/docs/travis.html - - wget https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh; - - bash miniconda.sh -b -p $HOME/miniconda - - export PATH="$HOME/miniconda/bin:$PATH" - - hash -r - - conda config --set always_yes yes --set changeps1 no - - conda update -q conda - # Useful for debugging any issues with conda - - conda info -a - - - conda create -q -n test-environment python=$TRAVIS_PYTHON_VERSION pandas h5py six mkl-service - - source activate test-environment - - pip install numpy - - pip install scipy - - pip install matplotlib - - # install TensorFlow - - if [[ "$TRAVIS_PYTHON_VERSION" == "3.5" && "$TENSORFLOW_V" == "1.8.0" ]]; then - pip install https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-1.8.0-cp35-cp35m-linux_x86_64.whl; - elif [[ "$TRAVIS_PYTHON_VERSION" == "3.5" && "$TENSORFLOW_V" == "1.12.0" ]]; then - pip install https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-1.12.0rc1-cp35-cp35m-linux_x86_64.whl; - fi - - - time pip install -q -e ".[test]" - - if [[ "$PYTORCH" == True ]]; then - pip install torch==1.1.0 torchvision==0.3.0 -q; - fi - # workaround for version incompatibility between the scipy version in conda - # and the system-provided /usr/lib/x86_64-linux-gnu/libstdc++.so.6 - # by installing a conda-provided libgcc and adding it to the load path - - conda install libgcc - - export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/travis/miniconda/envs/test-environment/lib - - # install serialization dependencies - - pip install joblib - # install dependencies for adversarial competition eval infra tests - - pip install google-cloud==0.33.1 - - pip install Pillow - # Style checks - - pip install pylint==1.9.3 - # To be able to exclude subdirectories in nose tests - - pip install nose-exclude - -# command to run tests -script: - # exit on first error - - set -e - # run keras backend init to initialize backend config - - python -c "from tensorflow import keras" - # create dataset directory to avoid concurrent directory creation at runtime - - mkdir ~/.keras/datasets - # set up keras backend - - sed -i -e 's/"backend":[[:space:]]*"[^"]*/"backend":\ "'$KERAS_BACKEND'/g' ~/.keras/keras.json; - - echo -e "Running tests with the following config:\n$(cat ~/.keras/keras.json)" - # test for evaluation infrastructure are very fast, so running them first - - nosetests -v --nologcapture -w examples/nips17_adversarial_competition/eval_infra/code/ eval_lib/tests/ - # - # --nologcapture: avoids a large amount of unnecessary tensorflow output - # --stop: stop on first error. Gives feedback from travis faster - # - # Tests for certification code require Tensorflow 1.9 or higher, - # thus skipping these tests for lower version of Tensorflow. - - if [[ "$PYTORCH" == True ]]; then - nosetests --nologcapture -v --stop cleverhans/future/torch/tests; - elif [[ "$PYTORCH" == False ]]; then - if [[ "$TENSORFLOW_V" == "1.8.0" ]]; then - nosetests -v --nologcapture --stop --exclude-dir=cleverhans/experimental/certification cleverhans; - else - nosetests -v --nologcapture --stop cleverhans; - fi; - for test_path in tests_tf/*.py ; do - nosetests --nocapture -v --stop $test_path; - done - fi diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2dc88ea33..cf747076d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -15,14 +15,66 @@ we strongly encourage you to add it to CleverHans so that others may evaluate it fairly in their own work. To speed the code review process, we ask that: - -* New efforts and features be coordinated -on the mailing list for CleverHans development: [cleverhans-dev@googlegroups.com](https://groups.google.com/forum/#!forum/cleverhans-dev). -* When making code contributions to CleverHans, you follow the -`PEP8` coding style in your pull requests. -* When making your first pull request, you [sign the Google CLA](https://cla.developers.google.com/clas) +* New efforts and features be coordinated on the [discussion board](https://github.com/cleverhans-lab/cleverhans/discussions). +* When making code contributions to CleverHans, you should follow the +[`Black`](https://black.readthedocs.io/en/stable/index.html) coding style in your pull requests. * We do not accept pull requests that add git submodules because of [the problems that arise when maintaining git submodules](https://medium.com/@porteneuve/mastering-git-submodules-34c65e940407) Bug fixes can be initiated through Github pull requests. + +## Development setup + +Please follow the usual +[git forking workflow](https://www.atlassian.com/git/tutorials/comparing-workflows/forking-workflow) +when contributing. + +### Setting up Cleverhans on your machine + +Then create a new Conda or Virtualenv environment. + +Conda: +``` +$ conda create --name cleverhans python=3.6 +$ conda activate cleverhans +``` + +Virtualenv: +``` +$ python3 -m venv /path/to/new/virtual/environment +$ cd /path/to/new/virtual/environment +$ source ./bin/activate +``` + +Then, after `cd`-ing into the `cleverhans` directory, install the +Cleverhans library and all corresponding requirements into your +newly created environment. + +``` +$ pip install -e "." +$ pip install -r requirements/requirements.txt +$ pip install -r requirements/requirements-pytorch.txt +$ pip install -r requirements/requirements-jax.txt +$ pip install -r requirements/requirements-tf2.txt +$ pip install -r requirements/requirements-dev.txt +``` + +Optionally also install GPU dependencies for JAX (PyTorch and +TF2 already come with GPU as part of their default package): +``` +$ pip install -r requirements/requirements-gpu.txt +``` + +### Add git pre-commit hooks + +Install our pre-commit hooks that ensure that your code is always formatted +via `black` before committing. + +``` +$ pre-commit install +``` + +Note that we do have code style checks in place for every submitted +PR and will reject PRs that do not meet these checks. By installing the +pre-commit hooks, this will be taken care of automatically \ No newline at end of file diff --git a/README.md b/README.md index bd96ab837..d5d82a5ea 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,7 @@ -# CleverHans (latest release: v3.0.1) +# CleverHans (latest release: v4.0.0) cleverhans logo -[![Build Status](https://travis-ci.org/tensorflow/cleverhans.svg?branch=master)](https://travis-ci.org/tensorflow/cleverhans) -[![Documentation Status](https://readthedocs.org/projects/cleverhans/badge/?version=latest)](https://cleverhans.readthedocs.io/en/latest/?badge=latest) This repository contains the source code for CleverHans, a Python library to benchmark machine learning systems' vulnerability to @@ -11,77 +9,32 @@ benchmark machine learning systems' vulnerability to You can learn more about such vulnerabilities on the accompanying [blog](http://cleverhans.io). The CleverHans library is under continual development, always welcoming -[contributions](https://github.com/tensorflow/cleverhans#contributing) +[contributions](https://github.com/cleverhans-lab/cleverhans#contributing) of the latest attacks and defenses. -In particular, we always welcome help towards resolving the [issues](https://github.com/tensorflow/cleverhans/issues) +In particular, we always welcome help towards resolving the [issues](https://github.com/cleverhans-lab/cleverhans/issues) currently open. -## Major updates coming to CleverHans +Since v4.0.0, CleverHans supports 3 frameworks: JAX, PyTorch, and TF2. We are currently prioritizing implementing +attacks in PyTorch, but we very much welcome contributions for all 3 frameworks. In versions v3.1.0 and prior, +CleverHans supported TF1; the code for v3.1.0 can be found under `cleverhans_v3.1.0/` or by checking +out a prior Github release. -CleverHans will soon support 3 frameworks: JAX, PyTorch, and TF2. The package -itself will focus on its initial principle: reference implementation of attacks +The library focuses on providing reference implementation of attacks against machine learning models to help with benchmarking models against -adversarial examples. This repository will also contain two folders: -`tutorials/` for scripts demonstrating the features of CleverHans and -`defenses/` for scripts that contain authoritative implementations of defenses -in one of the 3 supported frameworks. The structure of the future repository -will look like this: +adversarial examples. -``` -cleverhans/ - jax/ - attacks/ - ... - tests/ - ... - tf2/ - attacks/ - ... - tests/ - ... - torch/ - attacks/ - ... - tests/ - ... -defenses/ - jax/ - ... - tf2/ - ... - torch/ - ... -tutorials/ - jax/ - ... - tf2/ - ... - torch/ - ... -``` - -In the meanwhile, all of these folders can be found in the correspond `future/` -subdirectory (e.g., `cleverhans/future/jax/attacks`, `cleverhans/future/jax/tests` or `defenses/future/jax/`). - -A public milestone has been created to track the changes that are to be -implemented before the library version is incremented to v4. +The directory structure is as follows: +`cleverhans/` contain attack implementations, `tutorials/` contain scripts demonstrating the features +of CleverHans, and `defenses/` contains defense implementations. Each framework has its own subdirectory +within these folders, e.g. `cleverhans/jax`. ## Setting up CleverHans ### Dependencies -This library uses [TensorFlow](https://www.tensorflow.org/) to accelerate graph +This library uses [Jax](https://github.com/google/jax), [PyTorch](https://pytorch.org/) or [TensorFlow 2](https://www.tensorflow.org/) to accelerate graph computations performed by many machine learning models. -Therefore, installing TensorFlow is a pre-requisite. - -You can find instructions -[here](https://www.tensorflow.org/install/). -For better performance, it is also recommended to install TensorFlow -with GPU support (detailed instructions on how to do this are available -in the TensorFlow installation documentation). - -Installing TensorFlow will -take care of all other dependencies like `numpy` and `scipy`. +Therefore, installing one of these libraries is a pre-requisite. ### Installation @@ -90,8 +43,7 @@ Once dependencies have been taken care of, you can install CleverHans using #### `pip` installation -If you are installing CleverHans using `pip`, run the following command -after installing TensorFlow: +If you are installing CleverHans using `pip`, run the following command: ``` pip install cleverhans @@ -102,7 +54,7 @@ This will install the last version uploaded to If you'd instead like to install the bleeding edge version, use: ``` -pip install git+https://github.com/tensorflow/cleverhans.git#egg=cleverhans +pip install git+https://github.com/cleverhans-lab/cleverhans.git#egg=cleverhans ``` #### Installation for development @@ -112,7 +64,7 @@ develop the library and contribute changes back, first fork the repository on GitHub and then clone your fork into a directory of your choice: ``` -git clone https://github.com/tensorflow/cleverhans +git clone https://github.com//cleverhans ``` You can then install the local package in "editable" mode in order to add it to @@ -126,19 +78,8 @@ pip install -e . ### Currently supported setups Although CleverHans is likely to work on many other machine configurations, we -currently [test it](https://travis-ci.org/tensorflow/cleverhans) it with Python -3.5 and TensorFlow {1.8, 1.12} on Ubuntu 14.04.5 LTS (Trusty Tahr). -Support for Python 2.7 is deprecated. -CleverHans 3.0.1 supports Python 2.7 and the master branch is likely to -continue to work in Python 2.7 for some time, but we no longer run the tests -in Python 2.7 and we do not plan to fix bugs affecting only Python 2.7 after -2019-07-04. -Support for TensorFlow prior to 1.12 is deprecated. -Backwards compatibility wrappers for these versions may be removed after 2019-07-07, -and we will not fix bugs for those versions after that date. -Support for TensorFlow 1.7 and earlier is already deprecated: we do not fix -bugs for those versions and any remaining wrapper code for those versions -may be removed without further notice. +currently test it with Python +3.7, Jax 0.2, PyTorch 1.7, and Tensorflow 2.4 on Ubuntu 18.04 LTS (Bionic Beaver). ## Getting support @@ -150,55 +91,27 @@ issue tracker should *only* be used to report bugs or make feature requests. ## Contributing Contributions are welcomed! To speed the code review process, we ask that: -* New efforts and features be coordinated -on the mailing list for CleverHans development: [cleverhans-dev@googlegroups.com](https://groups.google.com/forum/#!forum/cleverhans-dev). -* When making code contributions to CleverHans, you follow the -`PEP8 with two spaces` coding style (the same as the one used -by TensorFlow) in your pull requests. -In most cases this can be done by running `autopep8 -i --indent-size 2 ` -on the files you have edited. -You can check your code by running `nosetests cleverhans/devtools/tests/test_format.py` or check an individual file by running `pylint ` from inside the cleverhans repository root directory. -* When making your first pull request, you [sign the Google CLA](https://cla.developers.google.com/clas) +* New efforts and features be coordinated on the [discussion board](https://github.com/cleverhans-lab/cleverhans/discussions). +* When making code contributions to CleverHans, you should follow the [`Black`](https://black.readthedocs.io/en/stable/index.html) + coding style in your pull requests. * We do not accept pull requests that add git submodules because of [the problems that arise when maintaining git - submodules](https://medium.com/@porteneuve/mastering-git-submodules-34c65e940407) + submodules](https://medium.com/@porteneuve/mastering-git-submodules-34c65e940407). Bug fixes can be initiated through Github pull requests. -## Scripts: `scripts` directory - -The `scripts` directory contains command line utilities. -In many cases you can use these to run CleverHans functionality on your -saved models without needing to write any of your own Python code. - -You may want to set your `.bashrc` / `.bash_profile` file to add the -CleverHans `scripts` directory to your `PATH` environment variable -so that these scripts will be conveniently executable from any directory. - -## Tutorials: `cleverhans_tutorials` directory +## Tutorials: `tutorials` directory To help you get started with the functionalities provided by this library, the -`cleverhans_tutorials/` folder comes with the following tutorials: -* **MNIST with FGSM** ([code](cleverhans_tutorials/mnist_tutorial_tf.py)): this -tutorial covers how to train a MNIST model using TensorFlow, -craft adversarial examples using the [fast gradient sign method](https://arxiv.org/abs/1412.6572), -and make the model more robust to adversarial examples using adversarial training. -* **MNIST with FGSM using Keras** ([code](cleverhans_tutorials/mnist_tutorial_keras_tf.py)): this -tutorial covers how to define a MNIST model with Keras and train it using TensorFlow, -craft adversarial examples using the [fast gradient sign method](https://arxiv.org/abs/1412.6572), -and make the model more robust to adversarial -examples using adversarial training. -* **MNIST with JSMA** ([code](cleverhans_tutorials/mnist_tutorial_jsma.py)): this second -tutorial covers how to define a MNIST model with Keras and train it using TensorFlow and -craft adversarial examples using the [Jacobian-based saliency map approach](https://arxiv.org/abs/1511.07528). -* **MNIST using a black-box attack** ([code](cleverhans_tutorials/mnist_blackbox.py)): -this tutorial implements the black-box -attack described in this [paper](https://arxiv.org/abs/1602.02697). -The adversary train a substitute model: a copy that imitates the black-box -model by observing the labels that the black-box model assigns to inputs chosen -carefully by the adversary. The adversary then uses the substitute -model’s gradients to find adversarial examples that are misclassified by the -black-box model as well. +`tutorials/` folder comes with the following tutorials: +* **MNIST with FGSM and PGD** ([jax](tutorials/jax/mnist_tutorial.py), [tf2](tutorials/tf2/mnist_tutorial.py): +this tutorial covers how to train an MNIST model and craft adversarial examples using the + [fast gradient sign method](https://arxiv.org/abs/1412.6572) and + [projected gradient descent](https://arxiv.org/abs/1706.06083). +* **CIFAR10 with FGSM and PGD** ([pytorch](tutorials/torch/cifar10_tutorial.py), [tf2](tutorials/tf2/cifar10_tutorial.py)): +this tutorial covers how to train a CIFAR10 model and +craft adversarial examples using the [fast gradient sign method](https://arxiv.org/abs/1412.6572) and + [projected gradient descent](https://arxiv.org/abs/1706.06083). NOTE: the tutorials are maintained carefully, in the sense that we use continuous integration to make sure they continue working. They are not @@ -207,7 +120,7 @@ You should not write 3rd party code that imports the tutorials and expect that the interface will not break. Only the main library is subject to our six month interface deprecation warning rule. -NOTE: please write to cleverhans-dev@googlegroups.com before writing a new +NOTE: please start a thread on the [discussion board](https://github.com/cleverhans-lab/cleverhans/discussions) before writing a new tutorial. Because each new tutorial involves a large amount of duplicated code relative to the existing tutorials, and because every line of code requires ongoing testing and maintenance indefinitely, we generally prefer @@ -223,16 +136,21 @@ example contests. We do not offer nearly as much ongoing maintenance or support for this directory as the rest of the library, and if code in here gets broken we may just delete it without warning. +Since we recently discontinued support for TF1, the `examples/` folder is currently +empty, but you are welcome to submit your uses via a pull request :) + +Old examples for CleverHans v3.1.0 and prior can be found under `cleverhans_v3.1.0/examples/`. + ## Reporting benchmarks When reporting benchmarks, please: -* Use a versioned release of CleverHans. You can find a list of released versions [here](https://github.com/tensorflow/cleverhans/releases). +* Use a versioned release of CleverHans. You can find a list of released versions [here](https://github.com/cleverhans-lab/cleverhans/releases). * Either use the latest version, or, if comparing to an earlier publication, use the same version as the earlier publication. * Report which attack method was used. * Report any configuration variables used to determine the behavior of the attack. For example, you might report "We benchmarked the robustness of our method to -adversarial attack using v3.0.1 of CleverHans. On a test set modified by the +adversarial attack using v4.0.0 of CleverHans. On a test set modified by the `FastGradientMethod` with a max-norm `eps` of 0.3, we obtained a test set accuracy of 71.3%." ## Citing this work @@ -273,8 +191,10 @@ other inputs. ## Authors -This library is managed and maintained by Ian Goodfellow (Google Brain) and -Nicolas Papernot (Google Brain). +This library is collectively maintained by the [CleverHans Lab](https://cleverhans-lab.github.io/) +at the University of Toronto. The current point of contact is Jonas Guan. +It was previously maintained by Ian Goodfellow and Nicolas Papernot. + The following authors contributed 100 lines or more (ordered according to the GitHub contributors page): * Ian Goodfellow (Google Brain) @@ -323,7 +243,8 @@ The following authors contributed 100 lines or more (ordered according to the Gi * Erfan Noury (UMBC) * Robert Wagner (Case Western Reserve University) * Erh-Chung Chen (National Tsing Hua University) +* Joel Frank (Ruhr-University Bochum) ## Copyright -Copyright 2019 - Google Inc., OpenAI and Pennsylvania State University. +Copyright 2021 - Google Inc., OpenAI, Pennsylvania State University, University of Toronto. diff --git a/cleverhans/__init__.py b/cleverhans/__init__.py index ddfbed404..519ff0442 100644 --- a/cleverhans/__init__.py +++ b/cleverhans/__init__.py @@ -3,4 +3,4 @@ # If possible attach a hex digest to the version string to keep track of # changes in the development branch -__version__ = append_dev_version('3.0.1') +__version__ = append_dev_version("4.0.0") diff --git a/cleverhans/attack_bundling.py b/cleverhans/attack_bundling.py deleted file mode 100644 index c04033bac..000000000 --- a/cleverhans/attack_bundling.py +++ /dev/null @@ -1,1156 +0,0 @@ -""" -Runs multiple attacks against each example. - -References: https://openreview.net/forum?id=H1g0piA9tQ - https://arxiv.org/abs/1811.03685 -""" -# pylint: disable=missing-docstring -import copy -import logging -import time - -import numpy as np -import six -from six.moves import range -import tensorflow as tf - -from cleverhans.attacks import Noise -from cleverhans.attacks import ProjectedGradientDescent -from cleverhans.attacks import SPSA -from cleverhans.evaluation import correctness_and_confidence -from cleverhans.evaluation import batch_eval_multi_worker, run_attack -from cleverhans.model import Model -from cleverhans import serial -from cleverhans.utils import create_logger, deep_copy, safe_zip -from cleverhans.utils_tf import infer_devices -from cleverhans.confidence_report import ConfidenceReport -from cleverhans.confidence_report import ConfidenceReportEntry -from cleverhans.confidence_report import print_stats - -_logger = create_logger("attack_bundling") -_logger.setLevel(logging.INFO) - -devices = infer_devices() -num_devices = len(devices) -DEFAULT_EXAMPLES_PER_DEVICE = 128 -BATCH_SIZE = DEFAULT_EXAMPLES_PER_DEVICE * num_devices -REPORT_TIME_INTERVAL = 60 - -# TODO: lower priority: make it possible to initialize one attack with -# the output of an earlier attack - - -def single_run_max_confidence_recipe(sess, model, x, y, nb_classes, eps, - clip_min, clip_max, eps_iter, nb_iter, - report_path, - batch_size=BATCH_SIZE, - eps_iter_small=None): - """A reasonable attack bundling recipe for a max norm threat model and - a defender that uses confidence thresholding. This recipe uses both - uniform noise and randomly-initialized PGD targeted attacks. - - References: - https://openreview.net/forum?id=H1g0piA9tQ - - This version runs each attack (noise, targeted PGD for each class with - nb_iter iterations, target PGD for each class with 25X more iterations) - just once and then stops. See `basic_max_confidence_recipe` for a version - that runs indefinitely. - - :param sess: tf.Session - :param model: cleverhans.model.Model - :param x: numpy array containing clean example inputs to attack - :param y: numpy array containing true labels - :param nb_classes: int, number of classes - :param eps: float, maximum size of perturbation (measured by max norm) - :param eps_iter: float, step size for one version of PGD attacks - (will also run another version with eps_iter_small step size) - :param nb_iter: int, number of iterations for the cheaper PGD attacks - (will also run another version with 25X more iterations) - :param report_path: str, the path that the report will be saved to. - :param batch_size: int, the total number of examples to run simultaneously - :param eps_iter_small: optional, float. - The second version of the PGD attack is run with 25 * nb_iter iterations - and eps_iter_small step size. If eps_iter_small is not specified it is - set to eps_iter / 25. - """ - noise_attack = Noise(model, sess) - pgd_attack = ProjectedGradientDescent(model, sess) - threat_params = {"eps": eps, "clip_min": clip_min, "clip_max": clip_max} - noise_attack_config = AttackConfig(noise_attack, threat_params, "noise") - attack_configs = [noise_attack_config] - pgd_attack_configs = [] - pgd_params = copy.copy(threat_params) - pgd_params["eps_iter"] = eps_iter - pgd_params["nb_iter"] = nb_iter - assert batch_size % num_devices == 0 - dev_batch_size = batch_size // num_devices - ones = tf.ones(dev_batch_size, tf.int32) - expensive_pgd = [] - if eps_iter_small is None: - eps_iter_small = eps_iter / 25. - for cls in range(nb_classes): - cls_params = copy.copy(pgd_params) - cls_params['y_target'] = tf.to_float(tf.one_hot(ones * cls, nb_classes)) - cls_attack_config = AttackConfig(pgd_attack, cls_params, "pgd_" + str(cls)) - pgd_attack_configs.append(cls_attack_config) - expensive_params = copy.copy(cls_params) - expensive_params["eps_iter"] = eps_iter_small - expensive_params["nb_iter"] *= 25. - expensive_config = AttackConfig( - pgd_attack, expensive_params, "expensive_pgd_" + str(cls)) - expensive_pgd.append(expensive_config) - attack_configs = [noise_attack_config] + pgd_attack_configs + expensive_pgd - new_work_goal = {config: 1 for config in attack_configs} - goals = [MaxConfidence(t=1., new_work_goal=new_work_goal)] - bundle_attacks(sess, model, x, y, attack_configs, goals, report_path, attack_batch_size=batch_size, - eval_batch_size=batch_size) - - -def basic_max_confidence_recipe(sess, model, x, y, nb_classes, eps, - clip_min, clip_max, eps_iter, nb_iter, - report_path, - batch_size=BATCH_SIZE, - eps_iter_small=None): - """A reasonable attack bundling recipe for a max norm threat model and - a defender that uses confidence thresholding. - - References: - https://openreview.net/forum?id=H1g0piA9tQ - - This version runs indefinitely, updating the report on disk continuously. - - :param sess: tf.Session - :param model: cleverhans.model.Model - :param x: numpy array containing clean example inputs to attack - :param y: numpy array containing true labels - :param nb_classes: int, number of classes - :param eps: float, maximum size of perturbation (measured by max norm) - :param eps_iter: float, step size for one version of PGD attacks - (will also run another version with eps_iter_small) - :param nb_iter: int, number of iterations for one version of PGD attacks - (will also run another version with 25X more iterations) - :param report_path: str, the path that the report will be saved to. - :batch_size: int, the total number of examples to run simultaneously - :param eps_iter_small: optional, float. - The second version of the PGD attack is run with 25 * nb_iter iterations - and eps_iter_small step size. If eps_iter_small is not specified it is - set to eps_iter / 25. - """ - noise_attack = Noise(model, sess) - pgd_attack = ProjectedGradientDescent(model, sess) - threat_params = {"eps": eps, "clip_min": clip_min, "clip_max": clip_max} - noise_attack_config = AttackConfig(noise_attack, threat_params) - attack_configs = [noise_attack_config] - pgd_attack_configs = [] - pgd_params = copy.copy(threat_params) - pgd_params["eps_iter"] = eps_iter - pgd_params["nb_iter"] = nb_iter - assert batch_size % num_devices == 0 - dev_batch_size = batch_size // num_devices - ones = tf.ones(dev_batch_size, tf.int32) - expensive_pgd = [] - if eps_iter_small is None: - eps_iter_small = eps_iter / 25. - for cls in range(nb_classes): - cls_params = copy.copy(pgd_params) - cls_params['y_target'] = tf.to_float(tf.one_hot(ones * cls, nb_classes)) - cls_attack_config = AttackConfig(pgd_attack, cls_params, "pgd_" + str(cls)) - pgd_attack_configs.append(cls_attack_config) - expensive_params = copy.copy(cls_params) - expensive_params["eps_iter"] = eps_iter_small - expensive_params["nb_iter"] *= 25. - expensive_config = AttackConfig( - pgd_attack, expensive_params, "expensive_pgd_" + str(cls)) - expensive_pgd.append(expensive_config) - attack_configs = [noise_attack_config] + pgd_attack_configs + expensive_pgd - new_work_goal = {config: 5 for config in attack_configs} - pgd_work_goal = {config: 5 for config in pgd_attack_configs} - goals = [Misclassify(new_work_goal={noise_attack_config: 50}), - Misclassify(new_work_goal=pgd_work_goal), - MaxConfidence(t=0.5, new_work_goal=new_work_goal), - MaxConfidence(t=0.75, new_work_goal=new_work_goal), - MaxConfidence(t=0.875, new_work_goal=new_work_goal), - MaxConfidence(t=0.9375, new_work_goal=new_work_goal), - MaxConfidence(t=0.96875, new_work_goal=new_work_goal), - MaxConfidence(t=0.984375, new_work_goal=new_work_goal), - MaxConfidence(t=1.)] - bundle_attacks(sess, model, x, y, attack_configs, goals, report_path) - # This runs forever - - -def fixed_max_confidence_recipe(sess, model, x, y, nb_classes, eps, - clip_min, clip_max, eps_iter, nb_iter, - report_path, - batch_size=BATCH_SIZE, - eps_iter_small=None): - """A reasonable attack bundling recipe for a max norm threat model and - a defender that uses confidence thresholding. - - References: - https://openreview.net/forum?id=H1g0piA9tQ - - This version runs each attack a fixed number of times. - It is more exhaustive than `single_run_max_confidence_recipe` but because - it uses a fixed budget rather than running indefinitely it is more - appropriate for making fair comparisons between two models. - - :param sess: tf.Session - :param model: cleverhans.model.Model - :param x: numpy array containing clean example inputs to attack - :param y: numpy array containing true labels - :param nb_classes: int, number of classes - :param eps: float, maximum size of perturbation (measured by max norm) - :param eps_iter: float, step size for one version of PGD attacks - (will also run another version with smaller step size) - :param nb_iter: int, number of iterations for one version of PGD attacks - (will also run another version with 25X more iterations) - :param report_path: str, the path that the report will be saved to. - :batch_size: int, the total number of examples to run simultaneously - :param eps_iter_small: float, the step size to use for more expensive version of the attack. - If not specified, usess eps_iter / 25 - """ - noise_attack = Noise(model, sess) - pgd_attack = ProjectedGradientDescent(model, sess) - threat_params = {"eps": eps, "clip_min": clip_min, "clip_max": clip_max} - noise_attack_config = AttackConfig(noise_attack, threat_params) - attack_configs = [noise_attack_config] - pgd_attack_configs = [] - pgd_params = copy.copy(threat_params) - pgd_params["eps_iter"] = eps_iter - pgd_params["nb_iter"] = nb_iter - assert batch_size % num_devices == 0 - dev_batch_size = batch_size // num_devices - ones = tf.ones(dev_batch_size, tf.int32) - if eps_iter_small is None: - eps_iter_small = eps_iter / 25. - expensive_pgd = [] - for cls in range(nb_classes): - cls_params = copy.copy(pgd_params) - cls_params['y_target'] = tf.to_float(tf.one_hot(ones * cls, nb_classes)) - cls_attack_config = AttackConfig(pgd_attack, cls_params, "pgd_" + str(cls)) - pgd_attack_configs.append(cls_attack_config) - expensive_params = copy.copy(cls_params) - expensive_params["eps_iter"] = eps_iter_small - expensive_params["nb_iter"] *= 25. - expensive_config = AttackConfig( - pgd_attack, expensive_params, "expensive_pgd_" + str(cls)) - expensive_pgd.append(expensive_config) - attack_configs = [noise_attack_config] + pgd_attack_configs + expensive_pgd - new_work_goal = {config: 5 for config in attack_configs} - pgd_work_goal = {config: 5 for config in pgd_attack_configs} - # TODO: lower priority: make sure bundler won't waste time running targeted - # attacks on examples where the target class is the true class. - goals = [Misclassify(new_work_goal={noise_attack_config: 50}), - Misclassify(new_work_goal=pgd_work_goal), - MaxConfidence(t=0.5, new_work_goal=new_work_goal), - MaxConfidence(t=0.75, new_work_goal=new_work_goal), - MaxConfidence(t=0.875, new_work_goal=new_work_goal), - MaxConfidence(t=0.9375, new_work_goal=new_work_goal), - MaxConfidence(t=0.96875, new_work_goal=new_work_goal), - MaxConfidence(t=0.984375, new_work_goal=new_work_goal), - MaxConfidence(t=1., new_work_goal=new_work_goal)] - bundle_attacks(sess, model, x, y, attack_configs, goals, report_path) - - -def random_search_max_confidence_recipe(sess, model, x, y, eps, - clip_min, clip_max, - report_path, batch_size=BATCH_SIZE, - num_noise_points=10000): - """Max confidence using random search. - - References: - https://openreview.net/forum?id=H1g0piA9tQ - Describes the max_confidence procedure used for the bundling in this recipe - https://arxiv.org/abs/1802.00420 - Describes using random search with 1e5 or more random points to avoid - gradient masking. - - :param sess: tf.Session - :param model: cleverhans.model.Model - :param x: numpy array containing clean example inputs to attack - :param y: numpy array containing true labels - :param nb_classes: int, number of classes - :param eps: float, maximum size of perturbation (measured by max norm) - :param eps_iter: float, step size for one version of PGD attacks - (will also run another version with 25X smaller step size) - :param nb_iter: int, number of iterations for one version of PGD attacks - (will also run another version with 25X more iterations) - :param report_path: str, the path that the report will be saved to. - :batch_size: int, the total number of examples to run simultaneously - """ - noise_attack = Noise(model, sess) - threat_params = {"eps": eps, "clip_min": clip_min, "clip_max": clip_max} - noise_attack_config = AttackConfig(noise_attack, threat_params) - attack_configs = [noise_attack_config] - assert batch_size % num_devices == 0 - new_work_goal = {noise_attack_config: num_noise_points} - goals = [MaxConfidence(t=1., new_work_goal=new_work_goal)] - bundle_attacks(sess, model, x, y, attack_configs, goals, report_path) - - -class AttackConfig(object): - """ - An attack and associated parameters. - :param attack: cleverhans.attacks.Attack - :param params: dict of keyword arguments to pass to attack.generate - :param name: str, name to be returned by __str__ / __repr__ - :param pass_y: bool, whether to pass y to `attack.generate` - """ - - def __init__(self, attack, params=None, name=None, pass_y=False): - self.attack = attack - self.params = params - self.name = name - if params is not None: - assert isinstance(params, dict) - for key in params: - assert isinstance(key, six.string_types), type(key) - self.pass_y = pass_y - - def __str__(self): - if self.name is not None: - return self.name - return "AttackConfig(" + str(self.attack) + ", " + str(self.params) + ")" - - def __repr__(self): - return self.__str__() - - -def bundle_attacks(sess, model, x, y, attack_configs, goals, report_path, - attack_batch_size=BATCH_SIZE, eval_batch_size=BATCH_SIZE): - """ - Runs attack bundling. - Users of cleverhans may call this function but are more likely to call - one of the recipes above. - - Reference: https://openreview.net/forum?id=H1g0piA9tQ - - :param sess: tf.session.Session - :param model: cleverhans.model.Model - :param x: numpy array containing clean example inputs to attack - :param y: numpy array containing true labels - :param attack_configs: list of AttackConfigs to run - :param goals: list of AttackGoals to run - The bundler works through the goals in order, until each is satisfied. - Some goals may never be satisfied, in which case the bundler will run - forever, updating the report on disk as it goes. - :param report_path: str, the path the report will be saved to - :param attack_batch_size: int, batch size for generating adversarial examples - :param eval_batch_size: int, batch size for evaluating the model on clean / adversarial examples - :returns: - adv_x: The adversarial examples, in the same format as `x` - run_counts: dict mapping each AttackConfig to a numpy array reporting - how many times that AttackConfig was run on each example - """ - assert isinstance(sess, tf.Session) - assert isinstance(model, Model) - assert all(isinstance(attack_config, AttackConfig) for attack_config - in attack_configs) - assert all(isinstance(goal, AttackGoal) for goal in goals) - assert isinstance(report_path, six.string_types) - if x.shape[0] != y.shape[0]: - raise ValueError("Number of input examples does not match number of labels") - - # Note: no need to precompile attacks, correctness_and_confidence - # caches them - - run_counts = {} - for attack_config in attack_configs: - run_counts[attack_config] = np.zeros(x.shape[0], dtype=np.int64) - - # TODO: make an interface to pass this in if it has already been computed - # elsewhere - _logger.info("Running on clean data to initialize the report...") - packed = correctness_and_confidence(sess, model, x, y, batch_size=eval_batch_size, - devices=devices) - _logger.info("...done") - correctness, confidence = packed - _logger.info("Accuracy: " + str(correctness.mean())) - report = ConfidenceReport() - report['clean'] = ConfidenceReportEntry(correctness, confidence) - - adv_x = x.copy() - - for goal in goals: - bundle_attacks_with_goal(sess, model, x, y, adv_x, attack_configs, - run_counts, - goal, report, report_path, - attack_batch_size=attack_batch_size, eval_batch_size=eval_batch_size) - - # Many users will set `goals` to make this run forever, so the return - # statement is not the primary way to get information out. - return adv_x, run_counts - -def bundle_attacks_with_goal(sess, model, x, y, adv_x, attack_configs, - run_counts, - goal, report, report_path, - attack_batch_size=BATCH_SIZE, eval_batch_size=BATCH_SIZE): - """ - Runs attack bundling, working on one specific AttackGoal. - This function is mostly intended to be called by `bundle_attacks`. - - Reference: https://openreview.net/forum?id=H1g0piA9tQ - - :param sess: tf.session.Session - :param model: cleverhans.model.Model - :param x: numpy array containing clean example inputs to attack - :param y: numpy array containing true labels - :param adv_x: numpy array containing the adversarial examples made so far - by earlier work in the bundling process - :param attack_configs: list of AttackConfigs to run - :param run_counts: dict mapping AttackConfigs to numpy arrays specifying - how many times they have been run on each example - :param goal: AttackGoal to run - :param report: ConfidenceReport - :param report_path: str, the path the report will be saved to - :param attack_batch_size: int, batch size for generating adversarial examples - :param eval_batch_size: int, batch size for evaluating the model on adversarial examples - """ - goal.start(run_counts) - _logger.info("Running criteria for new goal...") - criteria = goal.get_criteria(sess, model, adv_x, y, batch_size=eval_batch_size) - assert 'correctness' in criteria - _logger.info("Accuracy: " + str(criteria['correctness'].mean())) - assert 'confidence' in criteria - while not goal.is_satisfied(criteria, run_counts): - run_batch_with_goal(sess, model, x, y, adv_x, criteria, attack_configs, - run_counts, - goal, report, report_path, - attack_batch_size=attack_batch_size) - # Save after finishing all goals. - # The incremental saves run on a timer. This save is needed so that the last - # few attacks after the timer don't get discarded - report.completed = True - save(criteria, report, report_path, adv_x) - - -def run_batch_with_goal(sess, model, x, y, adv_x_val, criteria, attack_configs, - run_counts, goal, report, report_path, - attack_batch_size=BATCH_SIZE): - """ - Runs attack bundling on one batch of data. - This function is mostly intended to be called by - `bundle_attacks_with_goal`. - - :param sess: tf.session.Session - :param model: cleverhans.model.Model - :param x: numpy array containing clean example inputs to attack - :param y: numpy array containing true labels - :param adv_x_val: numpy array containing the adversarial examples made so far - by earlier work in the bundling process - :param criteria: dict mapping string names of criteria to numpy arrays with - their values for each example - (Different AttackGoals track different criteria) - :param run_counts: dict mapping AttackConfigs to numpy arrays reporting how - many times they have been run on each example - :param goal: the AttackGoal to work on - :param report: dict, see `bundle_attacks_with_goal` - :param report_path: str, path to save the report to - """ - attack_config = goal.get_attack_config(attack_configs, run_counts, criteria) - idxs = goal.request_examples(attack_config, criteria, run_counts, - attack_batch_size) - x_batch = x[idxs] - assert x_batch.shape[0] == attack_batch_size - y_batch = y[idxs] - assert y_batch.shape[0] == attack_batch_size - adv_x_batch = run_attack(sess, model, x_batch, y_batch, - attack_config.attack, attack_config.params, - attack_batch_size, devices, pass_y=attack_config.pass_y) - criteria_batch = goal.get_criteria(sess, model, adv_x_batch, y_batch, - batch_size=min(attack_batch_size, - BATCH_SIZE)) - # This can't be parallelized because some orig examples are copied more - # than once into the batch - cur_run_counts = run_counts[attack_config] - for batch_idx, orig_idx in enumerate(idxs): - cur_run_counts[orig_idx] += 1 - should_copy = goal.new_wins(criteria, orig_idx, criteria_batch, batch_idx) - if should_copy: - adv_x_val[orig_idx] = adv_x_batch[batch_idx] - for key in criteria: - criteria[key][orig_idx] = criteria_batch[key][batch_idx] - assert np.allclose(y[orig_idx], y_batch[batch_idx]) - report['bundled'] = ConfidenceReportEntry(criteria['correctness'], criteria['confidence']) - - should_save = False - new_time = time.time() - if hasattr(report, 'time'): - if new_time - report.time > REPORT_TIME_INTERVAL: - should_save = True - else: - should_save = True - if should_save: - report.time = new_time - goal.print_progress(criteria, run_counts) - save(criteria, report, report_path, adv_x_val) - - -def save(criteria, report, report_path, adv_x_val): - """ - Saves the report and adversarial examples. - :param criteria: dict, of the form returned by AttackGoal.get_criteria - :param report: dict containing a confidence report - :param report_path: string, filepath - :param adv_x_val: numpy array containing dataset of adversarial examples - """ - print_stats(criteria['correctness'], criteria['confidence'], 'bundled') - - print("Saving to " + report_path) - serial.save(report_path, report) - - assert report_path.endswith(".joblib") - adv_x_path = report_path[:-len(".joblib")] + "_adv.npy" - np.save(adv_x_path, adv_x_val) - - -class AttackGoal(object): - """Specifies goals for attack bundling. - Different bundling recipes can have different priorities. - - When choosing which examples to attack in the next batch, do we want - to focus on examples that are not yet misclassified? Among the - still correctly classified ones, do we want to focus on the ones that - have not been attacked many times yet? Do we want to focus on the ones - that have low loss / low confidence so far? - - After an attack has been run, do we prefer the new adversarial example - or the old one? Is the new one better if it causes higher confidence - in the wrong prediction? If it succeeds with a smaller perturbation? - For different use cases, the answers to these questions is different. - Implement different AttackGoal subclasses to represent the priorities - for your use case. - """ - - def start(self, run_counts): - """ - Called by the bundler when it starts working on the goal. - - :param run_counts: dict mapping AttackConfigs to numpy arrays reporting - how many times they have been run on each example. - """ - - def get_criteria(self, sess, model, advx, y, batch_size=BATCH_SIZE): - """ - Returns a dictionary mapping the name of each criterion to a NumPy - array containing the value of that criterion for each adversarial - example. - Subclasses can add extra criteria by implementing the `extra_criteria` - method. - - :param sess: tf.session.Session - :param model: cleverhans.model.Model - :param adv_x: numpy array containing the adversarial examples made so far - by earlier work in the bundling process - :param y: numpy array containing true labels - :param batch_size: int, batch size - """ - - names, factory = self.extra_criteria() - factory = _CriteriaFactory(model, factory) - results = batch_eval_multi_worker(sess, factory, [advx, y], - batch_size=batch_size, devices=devices) - names = ['correctness', 'confidence'] + names - out = dict(safe_zip(names, results)) - return out - - def extra_criteria(self): - """ - Subclasses implement this to specify any extra criteria they need to track. - : returns: list of criterion names, _ExtraCriteriaFactory implementing them - """ - return [], None - - def request_examples(self, attack_config, criteria, run_counts, batch_size): - """ - Returns a numpy array of integer example indices to run in the next batch. - """ - raise NotImplementedError(str(type(self)) + - "needs to implement request_examples") - - def is_satisfied(self, criteria, run_counts): - """ - Returns a bool indicating whether the goal has been satisfied. - """ - raise NotImplementedError(str(type(self)) + - " needs to implement is_satisfied.") - - def print_progress(self, criteria, run_counts): - """ - Prints a progress message about how much has been done toward the goal. - :param criteria: dict, of the format returned by get_criteria - :param run_counts: dict mapping each AttackConfig to a numpy array - specifying how many times it has been run for each example - """ - print("Working on a " + self.__class__.__name__ + " goal.") - - def get_attack_config(self, attack_configs, run_counts, criteria): - """ - Returns an AttackConfig to run on the next batch. - """ - raise NotImplementedError(str(type(self)) + - " needs to implement get_attack_config") - - def new_wins(self, orig_criteria, orig_idx, new_criteria, new_idx): - """ - Returns a bool indicating whether a new adversarial example is better - than the pre-existing one for the same clean example. - :param orig_criteria: dict mapping names of criteria to their value - for each example in the whole dataset - :param orig_idx: The position of the pre-existing example within the - whole dataset. - :param new_criteria: dict, like orig_criteria, but with values only - on the latest batch of adversarial examples - :param new_idx: The position of the new adversarial example within - the batch - """ - raise NotImplementedError(str(type(self)) - + " needs to implement new_wins.") - - -class Misclassify(AttackGoal): - """An AttackGoal that prioritizes misclassifying all examples. - - Times out when each attack has been run the requested number of times. - Some examples may be attacked more than the goal number because we - always run a full batch of attacks and sometimes the batch size is - larger than the number of examples with attacks left to do. - :param new_work_goal: dict - Maps AttackConfigs to ints specifying how many times they should be - run before timing out. - If this dict is not specified, all attacks will be run, repeatedly, - until all examples are misclassified (or forever if some cannot - be changed into misclassified examples). - If this dict is specfied, only attacks in the dict will be run. - :param break_ties: string name of tie-breaking scheme for `new_wins` - When two examples are misclassified, how do we choose between them? - Currently the only scheme is 'wrong_confidence', where we prefer the - one with higher confidence assigned to a single wrong class. - In the future we may support other schemes like smaller perturbation - size, higher loss, etc. - """ - - def __init__(self, new_work_goal=None, break_ties='wrong_confidence'): - super(Misclassify, self).__init__() - self.new_work_goal = new_work_goal - assert all(isinstance(key, AttackConfig) for key in new_work_goal.keys()) - assert all(isinstance(value, int) for value in new_work_goal.values()) - self.rng = np.random.RandomState([2018, 10, 5, 9]) - self.break_ties = break_ties - - def start(self, run_counts): - for key in run_counts: - value = run_counts[key] - assert value.ndim == 1 - _logger.info("Started working on a Misclassify goal") - self.work_before = deep_copy(run_counts) - - def is_satisfied(self, criteria, run_counts): - correctness = criteria['correctness'] - assert correctness.dtype == np.bool - assert correctness.ndim == 1 - if correctness.max() == 0: - _logger.info("Everything is misclassified! Done with Misclassify goal") - return True - if self.new_work_goal is None: - return False - correct_run_counts = self.filter(run_counts, criteria) - correct_work_before = self.filter(self.work_before, criteria) - unfinished = unfinished_attack_configs(self.new_work_goal, - correct_work_before, - correct_run_counts) - finished = len(unfinished) == 0 - if finished: - _logger.info("Misclassify timed out after running all requested attacks") - else: - pass - # _logger.info("Miclassify goal still has attacks to run") - return finished - - def print_progress(self, criteria, run_counts): - print("Working on a " + self.__class__.__name__ + " goal.") - num_below = criteria['correctness'].sum() - print(str(num_below) + " examples are still correctly classified.") - if self.new_work_goal is None: - print("No work goal: running all attacks indefinitely") - else: - print("Working until all attacks have been run enough times") - filtered_run_counts = self.filter(run_counts, criteria) - filtered_work_before = self.filter(self.work_before, criteria) - for ac in self.new_work_goal: - goal = self.new_work_goal[ac] - new = filtered_run_counts[ac] - filtered_work_before[ac] - if new.size > 0: - min_new = new.min() - if min_new < goal: - num_min = (new == min_new).sum() - print("\t" + str(ac) + ": goal of " + str(goal) + " runs, but " - + str(num_min) + " examples have been run only " + str(min_new) - + " times") - - def filter(self, run_counts, criteria): - """ - Return run counts only for examples that are still correctly classified - """ - correctness = criteria['correctness'] - assert correctness.dtype == np.bool - filtered_counts = deep_copy(run_counts) - for key in filtered_counts: - filtered_counts[key] = filtered_counts[key][correctness] - return filtered_counts - - def get_attack_config(self, attack_configs, run_counts, criteria): - if self.new_work_goal is not None: - correct_work_before = self.filter(self.work_before, criteria) - correct_run_counts = self.filter(run_counts, criteria) - attack_configs = unfinished_attack_configs(self.new_work_goal, - correct_work_before, - correct_run_counts) - attack_config = attack_configs[self.rng.randint(len(attack_configs))] - return attack_config - - def extra_criteria(self): - if self.break_ties == "wrong_confidence": - return ["wrong_confidence"], _WrongConfidenceFactory() - else: - raise NotImplementedError() - - def request_examples(self, attack_config, criteria, run_counts, batch_size): - correctness = criteria['correctness'] - assert correctness.dtype == np.bool - total = correctness.size - total_correct = correctness.sum() - all_idxs = np.arange(total) - run_counts = run_counts[attack_config] - if total_correct > 0: - correct_idxs = all_idxs[correctness] - assert correct_idxs.size == total_correct - run_counts = run_counts[correctness] - pairs = safe_zip(correct_idxs, run_counts) - else: - pairs = safe_zip(all_idxs, run_counts) - # In PY3, pairs is now an iterator. - # To support sorting, we need to make it a list. - pairs = list(pairs) - - def key(pair): - return pair[1] - pairs.sort(key=key) - idxs = [pair[0] for pair in pairs] - while len(idxs) < batch_size: - needed = batch_size - len(idxs) - idxs = idxs + idxs[:needed] - if len(idxs) > batch_size: - idxs = idxs[:batch_size] - idxs = np.array(idxs) - return idxs - - def new_wins(self, orig_criteria, orig_idx, new_criteria, new_idx): - orig_correct = orig_criteria['correctness'][orig_idx] - new_correct = new_criteria['correctness'][new_idx] - if orig_correct and not new_correct: - return True - if (not orig_correct) and new_correct: - return False - assert orig_correct == new_correct - if self.break_ties == "wrong_confidence": - new = new_criteria["wrong_confidence"][new_idx] - orig = orig_criteria['wrong_confidence'][orig_idx] - return new > orig - else: - raise NotImplementedError(self.break_ties) - - -class MaxConfidence(AttackGoal): - """ - The AttackGoal corresponding the MaxConfidence procedure. - - Reference: https://openreview.net/forum?id=H1g0piA9tQ - - This should be used with a recipe that includes AttackConfigs - that target all of the classes, plus an any additional AttackConfigs - that may help to avoid gradient masking. - - This AttackGoal prioritizes getting all examples above a specified - threshold. (If the threshold is set to 1, then no examples are above - the threshold, so all are attacked equally often). The MaxConfidence - attack procedure against *a single example* is optimal regardless of - threshold, so long as t >= 0.5, but when attacking a population of - examples with finite computation time, knowledge of the threshold is - necessary to determine which examples to prioritize attacking. - - :param t: Prioritize pushing examples above this threshold. - :param new_work_goal: Optional dict mapping AttackConfigs to ints. - The int specifies the number of times to run each AttackConfig on each - below-threshold example before giving up. - If not specified, this goal runs all available attacks and never gives - up. - """ - - def __init__(self, t=1., new_work_goal=None): - super(MaxConfidence, self).__init__() - self.t = t - self.new_work_goal = new_work_goal - if new_work_goal is not None: - for key in new_work_goal: - assert isinstance(key, AttackConfig) - assert isinstance(new_work_goal[key], int) - self.rng = np.random.RandomState([2018, 10, 7, 12]) - - def filter(self, run_counts, criteria): - """ - Return the counts for only those examples that are below the threshold - """ - wrong_confidence = criteria['wrong_confidence'] - below_t = wrong_confidence <= self.t - filtered_counts = deep_copy(run_counts) - for key in filtered_counts: - filtered_counts[key] = filtered_counts[key][below_t] - return filtered_counts - - def extra_criteria(self): - return ["wrong_confidence"], _WrongConfidenceFactory() - - def is_satisfied(self, criteria, run_counts): - wrong_confidence = criteria['wrong_confidence'] - if wrong_confidence.min() > self.t: - _logger.info("Everything is above threshold " + str(self.t)) - _logger.info("Done with MaxConfidence goal") - return True - if self.new_work_goal is None: - return False - filtered_run_counts = self.filter(run_counts, criteria) - filtered_work_before = self.filter(self.work_before, criteria) - unfinished = unfinished_attack_configs(self.new_work_goal, - filtered_work_before, - filtered_run_counts, - log=False) - finished = len(unfinished) == 0 - if finished: - _logger.info( - "MaxConfidence timed out after running all requested attacks") - else: - pass - return finished - - def print_progress(self, criteria, run_counts): - print("Working on a " + self.__class__.__name__ + " goal.") - if self.t == 1.: - print("Threshold of 1, so just driving up confidence of all examples.") - else: - print("Target threshold of " + str(self.t)) - num_below = (criteria['wrong_confidence'] <= self.t).sum() - print(str(num_below) + " examples are below the target threshold.") - if self.new_work_goal is None: - print("No work goal: running all attacks indefinitely") - else: - print("Working until all attacks have been run enough times") - filtered_run_counts = self.filter(run_counts, criteria) - filtered_work_before = self.filter(self.work_before, criteria) - for ac in self.new_work_goal: - goal = self.new_work_goal[ac] - new = filtered_run_counts[ac] - filtered_work_before[ac] - min_new = new.min() - if min_new < goal: - num_min = (new == min_new).sum() - print("\t" + str(ac) + ": goal of " + str(goal) + " runs, but " - + str(num_min) + " examples have been run only " + str(min_new) - + " times") - - def get_attack_config(self, attack_configs, run_counts, criteria): - # TODO: refactor to avoid this duplicated method - if self.new_work_goal is not None: - correct_work_before = self.filter(self.work_before, criteria) - correct_run_counts = self.filter(run_counts, criteria) - attack_configs = unfinished_attack_configs(self.new_work_goal, - correct_work_before, - correct_run_counts) - attack_config = attack_configs[self.rng.randint(len(attack_configs))] - return attack_config - - def start(self, run_counts): - _logger.info("Started working on a MaxConfidence goal") - _logger.info("Threshold: " + str(self.t)) - if self.new_work_goal is None: - if self.t >= 1.: - _logger.info("This goal will run forever") - else: - _logger.info("This goal will run until all examples have confidence" - + " greater than " + str(self.t) + ", which may never" - + " happen.") - self.work_before = deep_copy(run_counts) - - def request_examples(self, attack_config, criteria, run_counts, batch_size): - wrong_confidence = criteria['wrong_confidence'] - below_t = wrong_confidence <= self.t - assert below_t.dtype == np.bool - total = below_t.size - total_below = below_t.sum() - all_idxs = np.arange(total) - run_counts = run_counts[attack_config] - if total_below > 0: - correct_idxs = all_idxs[below_t] - assert correct_idxs.size == total_below - run_counts = run_counts[below_t] - pairs = safe_zip(correct_idxs, run_counts) - else: - pairs = safe_zip(all_idxs, run_counts) - - def key(pair): - return pair[1] - pairs.sort(key=key) - idxs = [pair[0] for pair in pairs] - while len(idxs) < batch_size: - needed = batch_size - len(idxs) - idxs = idxs + idxs[:needed] - if len(idxs) > batch_size: - idxs = idxs[:batch_size] - idxs = np.array(idxs) - return idxs - - def new_wins(self, orig_criteria, orig_idx, new_criteria, new_idx): - new_wrong_confidence = new_criteria['wrong_confidence'][new_idx] - orig_wrong_confidence = orig_criteria['wrong_confidence'][orig_idx] - return new_wrong_confidence > orig_wrong_confidence - - -def unfinished_attack_configs(new_work_goal, work_before, run_counts, - log=False): - """ - Returns a list of attack configs that have not yet been run the desired - number of times. - :param new_work_goal: dict mapping attacks to desired number of times to run - :param work_before: dict mapping attacks to number of times they were run - before starting this new goal. Should be prefiltered to include only - examples that don't already meet the primary goal - :param run_counts: dict mapping attacks to total number of times they have - ever been run. Should be prefiltered to include only examples that don't - already meet the primary goal - """ - - assert isinstance(work_before, dict), work_before - - for key in work_before: - value = work_before[key] - assert value.ndim == 1, value.shape - if key in run_counts: - assert run_counts[key].shape == value.shape - - attack_configs = [] - for attack_config in new_work_goal: - done_now = run_counts[attack_config] - if log: - _logger.info(str(attack_config) + - " ave run count: " + str(done_now.mean())) - _logger.info(str(attack_config) + - " min run count: " + str(done_now.min())) - done_before = work_before[attack_config] - if log: - _logger.info(str(attack_config) + " mean work before: " + - str(done_before.mean())) - # This is the vector for all examples - new = done_now - done_before - # The work is only done when it has been done for every example - new = new.min() - assert isinstance(new, (int, np.int64)), type(new) - new_goal = new_work_goal[attack_config] - assert isinstance(new_goal, int), type(new_goal) - if new < new_goal: - if log: - _logger.info(str(attack_config) + " has run " + - str(new) + " of " + str(new_goal)) - attack_configs.append(attack_config) - return attack_configs - - -class _CriteriaFactory(object): - """ - A factory that builds the expression to evaluate all criteria. - """ - - def __init__(self, model, extra_criteria_factory=None): - self.model = model - self.extra_criteria_factory = extra_criteria_factory - properties_to_hash = (model, ) - if extra_criteria_factory is not None: - if extra_criteria_factory.properties_to_hash is not None: - extra_properties = extra_criteria_factory.properties_to_hash - properties_to_hash = properties_to_hash + extra_properties - self.properties_to_hash = properties_to_hash - - def __hash__(self): - # Make factory hashable so that no two factories for the - # same model will be used to build redundant tf graphs - return self.properties_to_hash.__hash__() - - def __eq__(self, other): - # Make factory hashable so that no two factories for the - # same model will be used to build redundant tf graphs - if not isinstance(other, _CriteriaFactory): - return False - if (type(self.extra_criteria_factory) is not - type(other.extra_criteria_factory)): - return False - return self.properties_to_hash == other.properties_to_hash - - def __call__(self): - x_batch = self.model.make_input_placeholder() - y_batch = self.model.make_label_placeholder() - - predictions = self.model.get_probs(x_batch) - correct = tf.equal(tf.argmax(y_batch, axis=-1), - tf.argmax(predictions, axis=-1)) - max_probs = tf.reduce_max(predictions, axis=1) - - if self.extra_criteria_factory is not None: - extra_criteria = self.extra_criteria_factory(x_batch, y_batch, - predictions, correct, - max_probs) - else: - extra_criteria = tuple([]) - - return (x_batch, y_batch), (correct, max_probs) + extra_criteria - - -class _ExtraCriteriaFactory(object): - """ - A factory that builds extra criteria - """ - - def __init__(self, properties_to_hash=None): - self.properties_to_hash = properties_to_hash - - def __hash__(self): - # Make factory hashable so that no two factories for the - # same model will be used to build redundant tf graphs - return self.properties_to_hash.__hash__() - - def __eq__(self, other): - # Make factory hashable so that no two factories for the - # same model will be used to build redundant tf graphs - if not isinstance(other, _ExtraCriteriaFactory): - return False - return self.properties_to_hash == other.properties_to_hash - - def __call__(self, x_batch, y_batch, predictions, correct, max_probs): - raise NotImplementedError() - - -class _WrongConfidenceFactory(_ExtraCriteriaFactory): - def __call__(self, x_batch, y_batch, predictions, correct, max_probs): - max_wrong_probs = tf.reduce_max(predictions * (1. - y_batch), axis=1) - return tuple([max_wrong_probs]) - - -def bundle_examples_with_goal(sess, model, adv_x_list, y, goal, - report_path, batch_size=BATCH_SIZE): - """ - A post-processor version of attack bundling, that chooses the strongest - example from the output of multiple earlier bundling strategies. - - :param sess: tf.session.Session - :param model: cleverhans.model.Model - :param adv_x_list: list of numpy arrays - Each entry in the list is the output of a previous bundler; it is an - adversarial version of the whole dataset. - :param y: numpy array containing true labels - :param goal: AttackGoal to use to choose the best version of each adversarial - example - :param report_path: str, the path the report will be saved to - :param batch_size: int, batch size - """ - - # Check the input - num_attacks = len(adv_x_list) - assert num_attacks > 0 - adv_x_0 = adv_x_list[0] - assert isinstance(adv_x_0, np.ndarray) - assert all(adv_x.shape == adv_x_0.shape for adv_x in adv_x_list) - - # Allocate the output - out = np.zeros_like(adv_x_0) - m = adv_x_0.shape[0] - # Initialize with negative sentinel values to make sure everything is - # written to - correctness = -np.ones(m, dtype='int32') - confidence = -np.ones(m, dtype='float32') - - # Gather criteria - criteria = [goal.get_criteria(sess, model, adv_x, y, batch_size=batch_size) for adv_x in adv_x_list] - assert all('correctness' in c for c in criteria) - assert all('confidence' in c for c in criteria) - _logger.info("Accuracy on each advx dataset: ") - for c in criteria: - _logger.info("\t" + str(c['correctness'].mean())) - - for example_idx in range(m): - # Index of the best attack for this example - attack_idx = 0 - # Find the winner - for candidate_idx in range(1, num_attacks): - if goal.new_wins(criteria[attack_idx], example_idx, - criteria[candidate_idx], example_idx): - attack_idx = candidate_idx - # Copy the winner into the output - out[example_idx] = adv_x_list[attack_idx][example_idx] - correctness[example_idx] = criteria[attack_idx]['correctness'][example_idx] - confidence[example_idx] = criteria[attack_idx]['confidence'][example_idx] - - assert correctness.min() >= 0 - assert correctness.max() <= 1 - assert confidence.min() >= 0. - assert confidence.max() <= 1. - correctness = correctness.astype('bool') - _logger.info("Accuracy on bundled examples: " + str(correctness.mean())) - - report = ConfidenceReport() - report['bundled'] = ConfidenceReportEntry(correctness, confidence) - serial.save(report_path, report) - assert report_path.endswith('.joblib') - adv_x_path = report_path[:-len('.joblib')] + "_adv_x.npy" - np.save(adv_x_path, out) - -def spsa_max_confidence_recipe(sess, model, x, y, nb_classes, eps, - clip_min, clip_max, nb_iter, - report_path, - spsa_samples=SPSA.DEFAULT_SPSA_SAMPLES, - spsa_iters=SPSA.DEFAULT_SPSA_ITERS, - eval_batch_size=BATCH_SIZE): - """Runs the MaxConfidence attack using SPSA as the underlying optimizer. - - Even though this runs only one attack, it must be implemented as a bundler - because SPSA supports only batch_size=1. The cleverhans.attacks.MaxConfidence - attack internally multiplies the batch size by nb_classes, so it can't take - SPSA as a base attacker. Insteader, we must bundle batch_size=1 calls using - cleverhans.attack_bundling.MaxConfidence. - - References: - https://openreview.net/forum?id=H1g0piA9tQ - - :param sess: tf.Session - :param model: cleverhans.model.Model - :param x: numpy array containing clean example inputs to attack - :param y: numpy array containing true labels - :param nb_classes: int, number of classes - :param eps: float, maximum size of perturbation (measured by max norm) - :param nb_iter: int, number of iterations for one version of PGD attacks - (will also run another version with 25X more iterations) - :param report_path: str, the path that the report will be saved to. - :param eval_batch_size: int, batch size for evaluation (as opposed to making attacks) - """ - spsa = SPSA(model, sess) - spsa_params = {"eps": eps, "clip_min" : clip_min, "clip_max" : clip_max, - "nb_iter": nb_iter, "spsa_samples": spsa_samples, - "spsa_iters": spsa_iters} - attack_configs = [] - dev_batch_size = 1 # The only batch size supported by SPSA - batch_size = num_devices - ones = tf.ones(dev_batch_size, tf.int32) - for cls in range(nb_classes): - cls_params = copy.copy(spsa_params) - cls_params['y_target'] = tf.to_float(tf.one_hot(ones * cls, nb_classes)) - cls_attack_config = AttackConfig(spsa, cls_params, "spsa_" + str(cls)) - attack_configs.append(cls_attack_config) - new_work_goal = {config: 1 for config in attack_configs} - goals = [MaxConfidence(t=1., new_work_goal=new_work_goal)] - bundle_attacks(sess, model, x, y, attack_configs, goals, report_path, - attack_batch_size=batch_size, eval_batch_size=eval_batch_size) diff --git a/cleverhans/attacks/attack.py b/cleverhans/attacks/attack.py deleted file mode 100644 index 4e42d972f..000000000 --- a/cleverhans/attacks/attack.py +++ /dev/null @@ -1,347 +0,0 @@ -""" -The Attack interface. -""" - -from abc import ABCMeta -import collections -import warnings - -import numpy as np -import tensorflow as tf - -from cleverhans.compat import reduce_max -from cleverhans.model import Model -from cleverhans import utils - -_logger = utils.create_logger("cleverhans.attacks.attack") - - -class Attack(object): - """ - Abstract base class for all attack classes. - """ - __metaclass__ = ABCMeta - - def __init__(self, model, sess=None, dtypestr='float32', **kwargs): - """ - :param model: An instance of the cleverhans.model.Model class. - :param sess: The (possibly optional) tf.Session to run graphs in. - :param dtypestr: Floating point precision to use (change to float64 - to avoid numerical instabilities). - :param back: (deprecated and will be removed on or after 2019-03-26). - The backend to use. Currently 'tf' is the only option. - """ - if 'back' in kwargs: - if kwargs['back'] == 'tf': - warnings.warn("Argument back to attack constructors is not needed" - " anymore and will be removed on or after 2019-03-26." - " All attacks are implemented using TensorFlow.") - else: - raise ValueError("Backend argument must be 'tf' and is now deprecated" - "It will be removed on or after 2019-03-26.") - - self.tf_dtype = tf.as_dtype(dtypestr) - self.np_dtype = np.dtype(dtypestr) - - if sess is not None and not isinstance(sess, tf.Session): - raise TypeError("sess is not an instance of tf.Session") - - from cleverhans import attacks_tf - attacks_tf.np_dtype = self.np_dtype - attacks_tf.tf_dtype = self.tf_dtype - - if not isinstance(model, Model): - raise TypeError("The model argument should be an instance of" - " the cleverhans.model.Model class.") - - # Prepare attributes - self.model = model - self.sess = sess - self.dtypestr = dtypestr - - # We are going to keep track of old graphs and cache them. - self.graphs = {} - - # When calling generate_np, arguments in the following set should be - # fed into the graph, as they are not structural items that require - # generating a new graph. - # This dict should map names of arguments to the types they should - # have. - # (Usually, the target class will be a feedable keyword argument.) - self.feedable_kwargs = tuple() - - # When calling generate_np, arguments in the following set should NOT - # be fed into the graph, as they ARE structural items that require - # generating a new graph. - # This list should contain the names of the structural arguments. - self.structural_kwargs = [] - - def generate(self, x, **kwargs): - """ - Generate the attack's symbolic graph for adversarial examples. This - method should be overriden in any child class that implements an - attack that is expressable symbolically. Otherwise, it will wrap the - numerical implementation as a symbolic operator. - - :param x: The model's symbolic inputs. - :param **kwargs: optional parameters used by child classes. - Each child class defines additional parameters as needed. - Child classes that use the following concepts should use the following - names: - clip_min: minimum feature value - clip_max: maximum feature value - eps: size of norm constraint on adversarial perturbation - ord: order of norm constraint - nb_iter: number of iterations - eps_iter: size of norm constraint on iteration - y_target: if specified, the attack is targeted. - y: Do not specify if y_target is specified. - If specified, the attack is untargeted, aims to make the output - class not be y. - If neither y_target nor y is specified, y is inferred by having - the model classify the input. - For other concepts, it's generally a good idea to read other classes - and check for name consistency. - :return: A symbolic representation of the adversarial examples. - """ - - error = "Sub-classes must implement generate." - raise NotImplementedError(error) - # Include an unused return so pylint understands the method signature - return x - - def construct_graph(self, fixed, feedable, x_val, hash_key): - """ - Construct the graph required to run the attack through generate_np. - - :param fixed: Structural elements that require defining a new graph. - :param feedable: Arguments that can be fed to the same graph when - they take different values. - :param x_val: symbolic adversarial example - :param hash_key: the key used to store this graph in our cache - """ - # try our very best to create a TF placeholder for each of the - # feedable keyword arguments, and check the types are one of - # the allowed types - class_name = str(self.__class__).split(".")[-1][:-2] - _logger.info("Constructing new graph for attack " + class_name) - - # remove the None arguments, they are just left blank - for k in list(feedable.keys()): - if feedable[k] is None: - del feedable[k] - - # process all of the rest and create placeholders for them - new_kwargs = dict(x for x in fixed.items()) - for name, value in feedable.items(): - given_type = value.dtype - if isinstance(value, np.ndarray): - if value.ndim == 0: - # This is pretty clearly not a batch of data - new_kwargs[name] = tf.placeholder(given_type, shape=[], name=name) - else: - # Assume that this is a batch of data, make the first axis variable - # in size - new_shape = [None] + list(value.shape[1:]) - new_kwargs[name] = tf.placeholder(given_type, new_shape, name=name) - elif isinstance(value, utils.known_number_types): - new_kwargs[name] = tf.placeholder(given_type, shape=[], name=name) - else: - raise ValueError("Could not identify type of argument " + - name + ": " + str(value)) - - # x is a special placeholder we always want to have - x_shape = [None] + list(x_val.shape)[1:] - x = tf.placeholder(self.tf_dtype, shape=x_shape) - - # now we generate the graph that we want - x_adv = self.generate(x, **new_kwargs) - - self.graphs[hash_key] = (x, new_kwargs, x_adv) - - if len(self.graphs) >= 10: - warnings.warn("Calling generate_np() with multiple different " - "structural parameters is inefficient and should" - " be avoided. Calling generate() is preferred.") - - def generate_np(self, x_val, **kwargs): - """ - Generate adversarial examples and return them as a NumPy array. - Sub-classes *should not* implement this method unless they must - perform special handling of arguments. - - :param x_val: A NumPy array with the original inputs. - :param **kwargs: optional parameters used by child classes. - :return: A NumPy array holding the adversarial examples. - """ - - if self.sess is None: - raise ValueError("Cannot use `generate_np` when no `sess` was" - " provided") - - packed = self.construct_variables(kwargs) - fixed, feedable, _, hash_key = packed - - if hash_key not in self.graphs: - self.construct_graph(fixed, feedable, x_val, hash_key) - else: - # remove the None arguments, they are just left blank - for k in list(feedable.keys()): - if feedable[k] is None: - del feedable[k] - - x, new_kwargs, x_adv = self.graphs[hash_key] - - feed_dict = {x: x_val} - - for name in feedable: - feed_dict[new_kwargs[name]] = feedable[name] - - return self.sess.run(x_adv, feed_dict) - - def construct_variables(self, kwargs): - """ - Construct the inputs to the attack graph to be used by generate_np. - - :param kwargs: Keyword arguments to generate_np. - :return: - Structural arguments - Feedable arguments - Output of `arg_type` describing feedable arguments - A unique key - """ - if isinstance(self.feedable_kwargs, dict): - warnings.warn("Using a dict for `feedable_kwargs is deprecated." - "Switch to using a tuple." - "It is not longer necessary to specify the types " - "of the arguments---we build a different graph " - "for each received type." - "Using a dict may become an error on or after " - "2019-04-18.") - feedable_names = tuple(sorted(self.feedable_kwargs.keys())) - else: - feedable_names = self.feedable_kwargs - if not isinstance(feedable_names, tuple): - raise TypeError("Attack.feedable_kwargs should be a tuple, but " - "for subclass " + str(type(self)) + " it is " - + str(self.feedable_kwargs) + " of type " - + str(type(self.feedable_kwargs))) - - # the set of arguments that are structural properties of the attack - # if these arguments are different, we must construct a new graph - fixed = dict( - (k, v) for k, v in kwargs.items() if k in self.structural_kwargs) - - # the set of arguments that are passed as placeholders to the graph - # on each call, and can change without constructing a new graph - feedable = {k: v for k, v in kwargs.items() if k in feedable_names} - for k in feedable: - if isinstance(feedable[k], (float, int)): - feedable[k] = np.array(feedable[k]) - - for key in kwargs: - if key not in fixed and key not in feedable: - raise ValueError(str(type(self)) + ": Undeclared argument: " + key) - - feed_arg_type = arg_type(feedable_names, feedable) - - if not all(isinstance(value, collections.Hashable) - for value in fixed.values()): - # we have received a fixed value that isn't hashable - # this means we can't cache this graph for later use, - # and it will have to be discarded later - hash_key = None - else: - # create a unique key for this set of fixed paramaters - hash_key = tuple(sorted(fixed.items())) + tuple([feed_arg_type]) - - return fixed, feedable, feed_arg_type, hash_key - - def get_or_guess_labels(self, x, kwargs): - """ - Get the label to use in generating an adversarial example for x. - The kwargs are fed directly from the kwargs of the attack. - If 'y' is in kwargs, then assume it's an untargeted attack and - use that as the label. - If 'y_target' is in kwargs and is not none, then assume it's a - targeted attack and use that as the label. - Otherwise, use the model's prediction as the label and perform an - untargeted attack. - """ - if 'y' in kwargs and 'y_target' in kwargs: - raise ValueError("Can not set both 'y' and 'y_target'.") - elif 'y' in kwargs: - labels = kwargs['y'] - elif 'y_target' in kwargs and kwargs['y_target'] is not None: - labels = kwargs['y_target'] - else: - preds = self.model.get_probs(x) - preds_max = reduce_max(preds, 1, keepdims=True) - original_predictions = tf.to_float(tf.equal(preds, preds_max)) - labels = tf.stop_gradient(original_predictions) - del preds - if isinstance(labels, np.ndarray): - nb_classes = labels.shape[1] - else: - nb_classes = labels.get_shape().as_list()[1] - return labels, nb_classes - - def parse_params(self, params=None): - """ - Take in a dictionary of parameters and applies attack-specific checks - before saving them as attributes. - - :param params: a dictionary of attack-specific parameters - :return: True when parsing was successful - """ - - if params is not None: - warnings.warn("`params` is unused and will be removed " - " on or after 2019-04-26.") - return True - - -def arg_type(arg_names, kwargs): - """ - Returns a hashable summary of the types of arg_names within kwargs. - :param arg_names: tuple containing names of relevant arguments - :param kwargs: dict mapping string argument names to values. - These must be values for which we can create a tf placeholder. - Currently supported: numpy darray or something that can ducktype it - returns: - API contract is to return a hashable object describing all - structural consequences of argument values that can otherwise - be fed into a graph of fixed structure. - Currently this is implemented as a tuple of tuples that track: - - whether each argument was passed - - whether each argument was passed and not None - - the dtype of each argument - Callers shouldn't rely on the exact structure of this object, - just its hashability and one-to-one mapping between graph structures. - """ - assert isinstance(arg_names, tuple) - passed = tuple(name in kwargs for name in arg_names) - passed_and_not_none = [] - for name in arg_names: - if name in kwargs: - passed_and_not_none.append(kwargs[name] is not None) - else: - passed_and_not_none.append(False) - passed_and_not_none = tuple(passed_and_not_none) - dtypes = [] - for name in arg_names: - if name not in kwargs: - dtypes.append(None) - continue - value = kwargs[name] - if value is None: - dtypes.append(None) - continue - assert hasattr(value, 'dtype'), type(value) - dtype = value.dtype - if not isinstance(dtype, np.dtype): - dtype = dtype.as_np_dtype - assert isinstance(dtype, np.dtype) - dtypes.append(dtype) - dtypes = tuple(dtypes) - return (passed, passed_and_not_none, dtypes) diff --git a/cleverhans/attacks/basic_iterative_method.py b/cleverhans/attacks/basic_iterative_method.py deleted file mode 100644 index a865020d2..000000000 --- a/cleverhans/attacks/basic_iterative_method.py +++ /dev/null @@ -1,16 +0,0 @@ -""" -The BasicIterativeMethod attack. -""" - -from cleverhans.attacks.projected_gradient_descent import ProjectedGradientDescent - - -class BasicIterativeMethod(ProjectedGradientDescent): - """ - The BasicIterativeMethod attack. - """ - def __init__(self, model, sess=None, dtypestr='float32', **kwargs): - super(BasicIterativeMethod, self).__init__(model, sess=sess, - dtypestr=dtypestr, - default_rand_init=False, - **kwargs) diff --git a/cleverhans/attacks/carlini_wagner_l2.py b/cleverhans/attacks/carlini_wagner_l2.py deleted file mode 100644 index 623398f8b..000000000 --- a/cleverhans/attacks/carlini_wagner_l2.py +++ /dev/null @@ -1,415 +0,0 @@ -"""The CarliniWagnerL2 attack -""" -# pylint: disable=missing-docstring -import logging - -import numpy as np -import tensorflow as tf - -from cleverhans.attacks.attack import Attack -from cleverhans.compat import reduce_sum, reduce_max -from cleverhans.model import CallableModelWrapper, Model, wrapper_warning_logits -from cleverhans import utils - -np_dtype = np.dtype('float32') -tf_dtype = tf.as_dtype('float32') - -_logger = utils.create_logger("cleverhans.attacks.carlini_wagner_l2") -_logger.setLevel(logging.INFO) - - -class CarliniWagnerL2(Attack): - """ - This attack was originally proposed by Carlini and Wagner. It is an - iterative attack that finds adversarial examples on many defenses that - are robust to other attacks. - Paper link: https://arxiv.org/abs/1608.04644 - - At a high level, this attack is an iterative attack using Adam and - a specially-chosen loss function to find adversarial examples with - lower distortion than other attacks. This comes at the cost of speed, - as this attack is often much slower than others. - - :param model: cleverhans.model.Model - :param sess: tf.Session - :param dtypestr: dtype of the data - :param kwargs: passed through to super constructor - """ - - def __init__(self, model, sess, dtypestr='float32', **kwargs): - """ - Note: the model parameter should be an instance of the - cleverhans.model.Model abstraction provided by CleverHans. - """ - if not isinstance(model, Model): - wrapper_warning_logits() - model = CallableModelWrapper(model, 'logits') - - super(CarliniWagnerL2, self).__init__(model, sess, dtypestr, **kwargs) - - self.feedable_kwargs = ('y', 'y_target') - - self.structural_kwargs = [ - 'batch_size', 'confidence', 'targeted', 'learning_rate', - 'binary_search_steps', 'max_iterations', 'abort_early', - 'initial_const', 'clip_min', 'clip_max' - ] - - def generate(self, x, **kwargs): - """ - Return a tensor that constructs adversarial examples for the given - input. Generate uses tf.py_func in order to operate over tensors. - - :param x: A tensor with the inputs. - :param kwargs: See `parse_params` - """ - assert self.sess is not None, \ - 'Cannot use `generate` when no `sess` was provided' - self.parse_params(**kwargs) - - labels, nb_classes = self.get_or_guess_labels(x, kwargs) - - attack = CWL2(self.sess, self.model, self.batch_size, self.confidence, - 'y_target' in kwargs, self.learning_rate, - self.binary_search_steps, self.max_iterations, - self.abort_early, self.initial_const, self.clip_min, - self.clip_max, nb_classes, - x.get_shape().as_list()[1:]) - - def cw_wrap(x_val, y_val): - return np.array(attack.attack(x_val, y_val), dtype=self.np_dtype) - - wrap = tf.py_func(cw_wrap, [x, labels], self.tf_dtype) - wrap.set_shape(x.get_shape()) - - return wrap - - def parse_params(self, - y=None, - y_target=None, - batch_size=1, - confidence=0, - learning_rate=5e-3, - binary_search_steps=5, - max_iterations=1000, - abort_early=True, - initial_const=1e-2, - clip_min=0, - clip_max=1): - """ - :param y: (optional) A tensor with the true labels for an untargeted - attack. If None (and y_target is None) then use the - original labels the classifier assigns. - :param y_target: (optional) A tensor with the target labels for a - targeted attack. - :param confidence: Confidence of adversarial examples: higher produces - examples with larger l2 distortion, but more - strongly classified as adversarial. - :param batch_size: Number of attacks to run simultaneously. - :param learning_rate: The learning rate for the attack algorithm. - Smaller values produce better results but are - slower to converge. - :param binary_search_steps: The number of times we perform binary - search to find the optimal tradeoff- - constant between norm of the purturbation - and confidence of the classification. - :param max_iterations: The maximum number of iterations. Setting this - to a larger value will produce lower distortion - results. Using only a few iterations requires - a larger learning rate, and will produce larger - distortion results. - :param abort_early: If true, allows early aborts if gradient descent - is unable to make progress (i.e., gets stuck in - a local minimum). - :param initial_const: The initial tradeoff-constant to use to tune the - relative importance of size of the perturbation - and confidence of classification. - If binary_search_steps is large, the initial - constant is not important. A smaller value of - this constant gives lower distortion results. - :param clip_min: (optional float) Minimum input component value - :param clip_max: (optional float) Maximum input component value - """ - - # ignore the y and y_target argument - self.batch_size = batch_size - self.confidence = confidence - self.learning_rate = learning_rate - self.binary_search_steps = binary_search_steps - self.max_iterations = max_iterations - self.abort_early = abort_early - self.initial_const = initial_const - self.clip_min = clip_min - self.clip_max = clip_max - - -def ZERO(): - return np.asarray(0., dtype=np_dtype) - - -class CWL2(object): - def __init__(self, sess, model, batch_size, confidence, targeted, - learning_rate, binary_search_steps, max_iterations, - abort_early, initial_const, clip_min, clip_max, num_labels, - shape): - """ - Return a tensor that constructs adversarial examples for the given - input. Generate uses tf.py_func in order to operate over tensors. - - :param sess: a TF session. - :param model: a cleverhans.model.Model object. - :param batch_size: Number of attacks to run simultaneously. - :param confidence: Confidence of adversarial examples: higher produces - examples with larger l2 distortion, but more - strongly classified as adversarial. - :param targeted: boolean controlling the behavior of the adversarial - examples produced. If set to False, they will be - misclassified in any wrong class. If set to True, - they will be misclassified in a chosen target class. - :param learning_rate: The learning rate for the attack algorithm. - Smaller values produce better results but are - slower to converge. - :param binary_search_steps: The number of times we perform binary - search to find the optimal tradeoff- - constant between norm of the purturbation - and confidence of the classification. - :param max_iterations: The maximum number of iterations. Setting this - to a larger value will produce lower distortion - results. Using only a few iterations requires - a larger learning rate, and will produce larger - distortion results. - :param abort_early: If true, allows early aborts if gradient descent - is unable to make progress (i.e., gets stuck in - a local minimum). - :param initial_const: The initial tradeoff-constant to use to tune the - relative importance of size of the pururbation - and confidence of classification. - If binary_search_steps is large, the initial - constant is not important. A smaller value of - this constant gives lower distortion results. - :param clip_min: (optional float) Minimum input component value. - :param clip_max: (optional float) Maximum input component value. - :param num_labels: the number of classes in the model's output. - :param shape: the shape of the model's input tensor. - """ - - self.sess = sess - self.TARGETED = targeted - self.LEARNING_RATE = learning_rate - self.MAX_ITERATIONS = max_iterations - self.BINARY_SEARCH_STEPS = binary_search_steps - self.ABORT_EARLY = abort_early - self.CONFIDENCE = confidence - self.initial_const = initial_const - self.batch_size = batch_size - self.clip_min = clip_min - self.clip_max = clip_max - self.model = model - - self.repeat = binary_search_steps >= 10 - - self.shape = shape = tuple([batch_size] + list(shape)) - - # the variable we're going to optimize over - modifier = tf.Variable(np.zeros(shape, dtype=np_dtype)) - - # these are variables to be more efficient in sending data to tf - self.timg = tf.Variable(np.zeros(shape), dtype=tf_dtype, name='timg') - self.tlab = tf.Variable( - np.zeros((batch_size, num_labels)), dtype=tf_dtype, name='tlab') - self.const = tf.Variable( - np.zeros(batch_size), dtype=tf_dtype, name='const') - - # and here's what we use to assign them - self.assign_timg = tf.placeholder(tf_dtype, shape, name='assign_timg') - self.assign_tlab = tf.placeholder( - tf_dtype, (batch_size, num_labels), name='assign_tlab') - self.assign_const = tf.placeholder( - tf_dtype, [batch_size], name='assign_const') - - # the resulting instance, tanh'd to keep bounded from clip_min - # to clip_max - self.newimg = (tf.tanh(modifier + self.timg) + 1) / 2 - self.newimg = self.newimg * (clip_max - clip_min) + clip_min - - # prediction BEFORE-SOFTMAX of the model - self.output = model.get_logits(self.newimg) - - # distance to the input data - self.other = (tf.tanh(self.timg) + 1) / \ - 2 * (clip_max - clip_min) + clip_min - self.l2dist = reduce_sum( - tf.square(self.newimg - self.other), list(range(1, len(shape)))) - - # compute the probability of the label class versus the maximum other - real = reduce_sum((self.tlab) * self.output, 1) - other = reduce_max((1 - self.tlab) * self.output - self.tlab * 10000, - 1) - - if self.TARGETED: - # if targeted, optimize for making the other class most likely - loss1 = tf.maximum(ZERO(), other - real + self.CONFIDENCE) - else: - # if untargeted, optimize for making this class least likely. - loss1 = tf.maximum(ZERO(), real - other + self.CONFIDENCE) - - # sum up the losses - self.loss2 = reduce_sum(self.l2dist) - self.loss1 = reduce_sum(self.const * loss1) - self.loss = self.loss1 + self.loss2 - - # Setup the adam optimizer and keep track of variables we're creating - start_vars = set(x.name for x in tf.global_variables()) - optimizer = tf.train.AdamOptimizer(self.LEARNING_RATE) - self.train = optimizer.minimize(self.loss, var_list=[modifier]) - end_vars = tf.global_variables() - new_vars = [x for x in end_vars if x.name not in start_vars] - - # these are the variables to initialize when we run - self.setup = [] - self.setup.append(self.timg.assign(self.assign_timg)) - self.setup.append(self.tlab.assign(self.assign_tlab)) - self.setup.append(self.const.assign(self.assign_const)) - - self.init = tf.variables_initializer(var_list=[modifier] + new_vars) - - def attack(self, imgs, targets): - """ - Perform the L_2 attack on the given instance for the given targets. - - If self.targeted is true, then the targets represents the target labels - If self.targeted is false, then targets are the original class labels - """ - - r = [] - for i in range(0, len(imgs), self.batch_size): - _logger.debug( - ("Running CWL2 attack on instance %s of %s", i, len(imgs))) - r.extend( - self.attack_batch(imgs[i:i + self.batch_size], - targets[i:i + self.batch_size])) - return np.array(r) - - def attack_batch(self, imgs, labs): - """ - Run the attack on a batch of instance and labels. - """ - - def compare(x, y): - if not isinstance(x, (float, int, np.int64)): - x = np.copy(x) - if self.TARGETED: - x[y] -= self.CONFIDENCE - else: - x[y] += self.CONFIDENCE - x = np.argmax(x) - if self.TARGETED: - return x == y - else: - return x != y - - batch_size = self.batch_size - - oimgs = np.clip(imgs, self.clip_min, self.clip_max) - - # re-scale instances to be within range [0, 1] - imgs = (imgs - self.clip_min) / (self.clip_max - self.clip_min) - imgs = np.clip(imgs, 0, 1) - # now convert to [-1, 1] - imgs = (imgs * 2) - 1 - # convert to tanh-space - imgs = np.arctanh(imgs * .999999) - - # set the lower and upper bounds accordingly - lower_bound = np.zeros(batch_size) - CONST = np.ones(batch_size) * self.initial_const - upper_bound = np.ones(batch_size) * 1e10 - - # placeholders for the best l2, score, and instance attack found so far - o_bestl2 = [1e10] * batch_size - o_bestscore = [-1] * batch_size - o_bestattack = np.copy(oimgs) - - for outer_step in range(self.BINARY_SEARCH_STEPS): - # completely reset adam's internal state. - self.sess.run(self.init) - batch = imgs[:batch_size] - batchlab = labs[:batch_size] - - bestl2 = [1e10] * batch_size - bestscore = [-1] * batch_size - _logger.debug(" Binary search step %s of %s", - outer_step, self.BINARY_SEARCH_STEPS) - - # The last iteration (if we run many steps) repeat the search once. - if self.repeat and outer_step == self.BINARY_SEARCH_STEPS - 1: - CONST = upper_bound - - # set the variables so that we don't have to send them over again - self.sess.run( - self.setup, { - self.assign_timg: batch, - self.assign_tlab: batchlab, - self.assign_const: CONST - }) - - prev = 1e6 - for iteration in range(self.MAX_ITERATIONS): - # perform the attack - _, l, l2s, scores, nimg = self.sess.run([ - self.train, self.loss, self.l2dist, self.output, - self.newimg - ]) - - if iteration % ((self.MAX_ITERATIONS // 10) or 1) == 0: - _logger.debug((" Iteration {} of {}: loss={:.3g} " + - "l2={:.3g} f={:.3g}").format( - iteration, self.MAX_ITERATIONS, l, - np.mean(l2s), np.mean(scores))) - - # check if we should abort search if we're getting nowhere. - if self.ABORT_EARLY and \ - iteration % ((self.MAX_ITERATIONS // 10) or 1) == 0: - if l > prev * .9999: - msg = " Failed to make progress; stop early" - _logger.debug(msg) - break - prev = l - - # adjust the best result found so far - for e, (l2, sc, ii) in enumerate(zip(l2s, scores, nimg)): - lab = np.argmax(batchlab[e]) - if l2 < bestl2[e] and compare(sc, lab): - bestl2[e] = l2 - bestscore[e] = np.argmax(sc) - if l2 < o_bestl2[e] and compare(sc, lab): - o_bestl2[e] = l2 - o_bestscore[e] = np.argmax(sc) - o_bestattack[e] = ii - - # adjust the constant as needed - for e in range(batch_size): - if compare(bestscore[e], np.argmax(batchlab[e])) and \ - bestscore[e] != -1: - # success, divide const by two - upper_bound[e] = min(upper_bound[e], CONST[e]) - if upper_bound[e] < 1e9: - CONST[e] = (lower_bound[e] + upper_bound[e]) / 2 - else: - # failure, either multiply by 10 if no solution found yet - # or do binary search with the known upper bound - lower_bound[e] = max(lower_bound[e], CONST[e]) - if upper_bound[e] < 1e9: - CONST[e] = (lower_bound[e] + upper_bound[e]) / 2 - else: - CONST[e] *= 10 - _logger.debug(" Successfully generated adversarial examples " + - "on {} of {} instances.".format( - sum(upper_bound < 1e9), batch_size)) - o_bestl2 = np.array(o_bestl2) - mean = np.mean(np.sqrt(o_bestl2[o_bestl2 < 1e9])) - _logger.debug(" Mean successful distortion: {:.4g}".format(mean)) - - # return the best solution found - o_bestl2 = np.array(o_bestl2) - return o_bestattack diff --git a/cleverhans/attacks/deep_fool.py b/cleverhans/attacks/deep_fool.py deleted file mode 100644 index 802ce3e37..000000000 --- a/cleverhans/attacks/deep_fool.py +++ /dev/null @@ -1,252 +0,0 @@ -"""The DeepFool attack - -""" -import copy -import logging -import warnings - -import numpy as np -import tensorflow as tf - -from cleverhans.attacks.attack import Attack -from cleverhans.model import Model, wrapper_warning_logits, CallableModelWrapper -from cleverhans import utils -from cleverhans import utils_tf - -np_dtype = np.dtype('float32') - -_logger = utils.create_logger("cleverhans.attacks.deep_fool") -_logger.setLevel(logging.INFO) - -class DeepFool(Attack): - """ - DeepFool is an untargeted & iterative attack which is based on an - iterative linearization of the classifier. The implementation here - is w.r.t. the L2 norm. - Paper link: "https://arxiv.org/pdf/1511.04599.pdf" - - :param model: cleverhans.model.Model - :param sess: tf.Session - :param dtypestr: dtype of the data - :param kwargs: passed through to super constructor - """ - - def __init__(self, model, sess, dtypestr='float32', **kwargs): - """ - Create a DeepFool instance. - """ - if not isinstance(model, Model): - wrapper_warning_logits() - model = CallableModelWrapper(model, 'logits') - - super(DeepFool, self).__init__(model, sess, dtypestr, **kwargs) - - self.structural_kwargs = [ - 'overshoot', 'max_iter', 'clip_max', 'clip_min', 'nb_candidate' - ] - - def generate(self, x, **kwargs): - """ - Generate symbolic graph for adversarial examples and return. - - :param x: The model's symbolic inputs. - :param kwargs: See `parse_params` - """ - assert self.sess is not None, \ - 'Cannot use `generate` when no `sess` was provided' - from cleverhans.utils_tf import jacobian_graph - - # Parse and save attack-specific parameters - assert self.parse_params(**kwargs) - - # Define graph wrt to this input placeholder - logits = self.model.get_logits(x) - self.nb_classes = logits.get_shape().as_list()[-1] - assert self.nb_candidate <= self.nb_classes, \ - 'nb_candidate should not be greater than nb_classes' - preds = tf.reshape( - tf.nn.top_k(logits, k=self.nb_candidate)[0], - [-1, self.nb_candidate]) - # grads will be the shape [batch_size, nb_candidate, image_size] - grads = tf.stack(jacobian_graph(preds, x, self.nb_candidate), axis=1) - - # Define graph - def deepfool_wrap(x_val): - """deepfool function for py_func""" - return deepfool_batch(self.sess, x, preds, logits, grads, x_val, - self.nb_candidate, self.overshoot, - self.max_iter, self.clip_min, self.clip_max, - self.nb_classes) - - wrap = tf.py_func(deepfool_wrap, [x], self.tf_dtype) - wrap.set_shape(x.get_shape()) - return wrap - - def parse_params(self, - nb_candidate=10, - overshoot=0.02, - max_iter=50, - clip_min=0., - clip_max=1., - **kwargs): - """ - :param nb_candidate: The number of classes to test against, i.e., - deepfool only consider nb_candidate classes when - attacking(thus accelerate speed). The nb_candidate - classes are chosen according to the prediction - confidence during implementation. - :param overshoot: A termination criterion to prevent vanishing updates - :param max_iter: Maximum number of iteration for deepfool - :param clip_min: Minimum component value for clipping - :param clip_max: Maximum component value for clipping - """ - self.nb_candidate = nb_candidate - self.overshoot = overshoot - self.max_iter = max_iter - self.clip_min = clip_min - self.clip_max = clip_max - if len(kwargs.keys()) > 0: - warnings.warn("kwargs is unused and will be removed on or after " - "2019-04-26.") - - return True - - -def deepfool_batch(sess, - x, - pred, - logits, - grads, - X, - nb_candidate, - overshoot, - max_iter, - clip_min, - clip_max, - nb_classes, - feed=None): - """ - Applies DeepFool to a batch of inputs - :param sess: TF session - :param x: The input placeholder - :param pred: The model's sorted symbolic output of logits, only the top - nb_candidate classes are contained - :param logits: The model's unnormalized output tensor (the input to - the softmax layer) - :param grads: Symbolic gradients of the top nb_candidate classes, procuded - from gradient_graph - :param X: Numpy array with sample inputs - :param nb_candidate: The number of classes to test against, i.e., - deepfool only consider nb_candidate classes when - attacking(thus accelerate speed). The nb_candidate - classes are chosen according to the prediction - confidence during implementation. - :param overshoot: A termination criterion to prevent vanishing updates - :param max_iter: Maximum number of iteration for DeepFool - :param clip_min: Minimum value for components of the example returned - :param clip_max: Maximum value for components of the example returned - :param nb_classes: Number of model output classes - :return: Adversarial examples - """ - X_adv = deepfool_attack( - sess, - x, - pred, - logits, - grads, - X, - nb_candidate, - overshoot, - max_iter, - clip_min, - clip_max, - feed=feed) - - return np.asarray(X_adv, dtype=np_dtype) - - -def deepfool_attack(sess, - x, - predictions, - logits, - grads, - sample, - nb_candidate, - overshoot, - max_iter, - clip_min, - clip_max, - feed=None): - """ - TensorFlow implementation of DeepFool. - Paper link: see https://arxiv.org/pdf/1511.04599.pdf - :param sess: TF session - :param x: The input placeholder - :param predictions: The model's sorted symbolic output of logits, only the - top nb_candidate classes are contained - :param logits: The model's unnormalized output tensor (the input to - the softmax layer) - :param grads: Symbolic gradients of the top nb_candidate classes, procuded - from gradient_graph - :param sample: Numpy array with sample input - :param nb_candidate: The number of classes to test against, i.e., - deepfool only consider nb_candidate classes when - attacking(thus accelerate speed). The nb_candidate - classes are chosen according to the prediction - confidence during implementation. - :param overshoot: A termination criterion to prevent vanishing updates - :param max_iter: Maximum number of iteration for DeepFool - :param clip_min: Minimum value for components of the example returned - :param clip_max: Maximum value for components of the example returned - :return: Adversarial examples - """ - adv_x = copy.copy(sample) - # Initialize the loop variables - iteration = 0 - current = utils_tf.model_argmax(sess, x, logits, adv_x, feed=feed) - if current.shape == (): - current = np.array([current]) - w = np.squeeze(np.zeros(sample.shape[1:])) # same shape as original image - r_tot = np.zeros(sample.shape) - original = current # use original label as the reference - - _logger.debug( - "Starting DeepFool attack up to %s iterations", max_iter) - # Repeat this main loop until we have achieved misclassification - while (np.any(current == original) and iteration < max_iter): - - if iteration % 5 == 0 and iteration > 0: - _logger.info("Attack result at iteration %s is %s", iteration, current) - gradients = sess.run(grads, feed_dict={x: adv_x}) - predictions_val = sess.run(predictions, feed_dict={x: adv_x}) - for idx in range(sample.shape[0]): - pert = np.inf - if current[idx] != original[idx]: - continue - for k in range(1, nb_candidate): - w_k = gradients[idx, k, ...] - gradients[idx, 0, ...] - f_k = predictions_val[idx, k] - predictions_val[idx, 0] - # adding value 0.00001 to prevent f_k = 0 - pert_k = (abs(f_k) + 0.00001) / np.linalg.norm(w_k.flatten()) - if pert_k < pert: - pert = pert_k - w = w_k - r_i = pert * w / np.linalg.norm(w) - r_tot[idx, ...] = r_tot[idx, ...] + r_i - - adv_x = np.clip(r_tot + sample, clip_min, clip_max) - current = utils_tf.model_argmax(sess, x, logits, adv_x, feed=feed) - if current.shape == (): - current = np.array([current]) - # Update loop variables - iteration = iteration + 1 - - # need more revision, including info like how many succeed - _logger.info("Attack result at iteration %s is %s", iteration, current) - _logger.info("%s out of %s become adversarial examples at iteration %s", - sum(current != original), - sample.shape[0], - iteration) - # need to clip this image into the given range - adv_x = np.clip((1 + overshoot) * r_tot + sample, clip_min, clip_max) - return adv_x diff --git a/cleverhans/attacks/elastic_net_method.py b/cleverhans/attacks/elastic_net_method.py deleted file mode 100644 index ad56f63d9..000000000 --- a/cleverhans/attacks/elastic_net_method.py +++ /dev/null @@ -1,526 +0,0 @@ -"""The ElasticNetMethod attack. -""" -# pylint: disable=missing-docstring -import logging - -import numpy as np -import tensorflow as tf - -from cleverhans.attacks.attack import Attack -from cleverhans.compat import reduce_sum, reduce_max -from cleverhans.model import Model, CallableModelWrapper, wrapper_warning_logits -from cleverhans import utils - -np_dtype = np.dtype('float32') -tf_dtype = tf.as_dtype('float32') - -_logger = utils.create_logger("cleverhans.attacks.elastic_net_method") -_logger.setLevel(logging.INFO) - - -def ZERO(): - return np.asarray(0., dtype=np_dtype) - - -class ElasticNetMethod(Attack): - """ - This attack features L1-oriented adversarial examples and includes - the C&W L2 attack as a special case (when beta is set to 0). - Adversarial examples attain similar performance to those - generated by the C&W L2 attack in the white-box case, - and more importantly, have improved transferability properties - and complement adversarial training. - Paper link: https://arxiv.org/abs/1709.04114 - - :param model: cleverhans.model.Model - :param sess: tf.Session - :param dtypestr: dtype of the data - :param kwargs: passed through to super constructor - """ - - def __init__(self, model, sess, dtypestr='float32', **kwargs): - """ - Note: the model parameter should be an instance of the - cleverhans.model.Model abstraction provided by CleverHans. - """ - if not isinstance(model, Model): - wrapper_warning_logits() - model = CallableModelWrapper(model, 'logits') - - super(ElasticNetMethod, self).__init__(model, sess, dtypestr, **kwargs) - - self.feedable_kwargs = ('y', 'y_target') - - self.structural_kwargs = [ - 'beta', 'decision_rule', 'batch_size', 'confidence', - 'targeted', 'learning_rate', 'binary_search_steps', - 'max_iterations', 'abort_early', 'initial_const', 'clip_min', - 'clip_max' - ] - - def generate(self, x, **kwargs): - """ - Return a tensor that constructs adversarial examples for the given - input. Generate uses tf.py_func in order to operate over tensors. - - :param x: (required) A tensor with the inputs. - :param kwargs: See `parse_params` - """ - assert self.sess is not None, \ - 'Cannot use `generate` when no `sess` was provided' - self.parse_params(**kwargs) - - labels, nb_classes = self.get_or_guess_labels(x, kwargs) - - attack = EAD(self.sess, self.model, self.beta, - self.decision_rule, self.batch_size, self.confidence, - 'y_target' in kwargs, self.learning_rate, - self.binary_search_steps, self.max_iterations, - self.abort_early, self.initial_const, self.clip_min, - self.clip_max, nb_classes, - x.get_shape().as_list()[1:]) - - def ead_wrap(x_val, y_val): - return np.array(attack.attack(x_val, y_val), dtype=self.np_dtype) - - wrap = tf.py_func(ead_wrap, [x, labels], self.tf_dtype) - wrap.set_shape(x.get_shape()) - - return wrap - - def parse_params(self, - y=None, - y_target=None, - beta=1e-2, - decision_rule='EN', - batch_size=1, - confidence=0, - learning_rate=1e-2, - binary_search_steps=9, - max_iterations=1000, - abort_early=False, - initial_const=1e-3, - clip_min=0, - clip_max=1): - """ - :param y: (optional) A tensor with the true labels for an untargeted - attack. If None (and y_target is None) then use the - original labels the classifier assigns. - :param y_target: (optional) A tensor with the target labels for a - targeted attack. - :param beta: Trades off L2 distortion with L1 distortion: higher - produces examples with lower L1 distortion, at the - cost of higher L2 (and typically Linf) distortion - :param decision_rule: EN or L1. Select final adversarial example from - all successful examples based on the least - elastic-net or L1 distortion criterion. - :param confidence: Confidence of adversarial examples: higher produces - examples with larger l2 distortion, but more - strongly classified as adversarial. - :param batch_size: Number of attacks to run simultaneously. - :param learning_rate: The learning rate for the attack algorithm. - Smaller values produce better results but are - slower to converge. - :param binary_search_steps: The number of times we perform binary - search to find the optimal tradeoff- - constant between norm of the perturbation - and confidence of the classification. Set - 'initial_const' to a large value and fix - this param to 1 for speed. - :param max_iterations: The maximum number of iterations. Setting this - to a larger value will produce lower distortion - results. Using only a few iterations requires - a larger learning rate, and will produce larger - distortion results. - :param abort_early: If true, allows early abort when the total - loss starts to increase (greatly speeds up attack, - but hurts performance, particularly on ImageNet) - :param initial_const: The initial tradeoff-constant to use to tune the - relative importance of size of the perturbation - and confidence of classification. - If binary_search_steps is large, the initial - constant is not important. A smaller value of - this constant gives lower distortion results. - For computational efficiency, fix - binary_search_steps to 1 and set this param - to a large value. - :param clip_min: (optional float) Minimum input component value - :param clip_max: (optional float) Maximum input component value - """ - - # ignore the y and y_target argument - self.beta = beta - self.decision_rule = decision_rule - self.batch_size = batch_size - self.confidence = confidence - self.learning_rate = learning_rate - self.binary_search_steps = binary_search_steps - self.max_iterations = max_iterations - self.abort_early = abort_early - self.initial_const = initial_const - self.clip_min = clip_min - self.clip_max = clip_max - - -class EAD(object): - def __init__(self, sess, model, beta, decision_rule, batch_size, - confidence, targeted, learning_rate, binary_search_steps, - max_iterations, abort_early, initial_const, clip_min, - clip_max, num_labels, shape): - """ - EAD Attack - - Return a tensor that constructs adversarial examples for the given - input. Generate uses tf.py_func in order to operate over tensors. - - :param sess: a TF session. - :param model: a cleverhans.model.Model object. - :param beta: Trades off L2 distortion with L1 distortion: higher - produces examples with lower L1 distortion, at the - cost of higher L2 (and typically Linf) distortion - :param decision_rule: EN or L1. Select final adversarial example from - all successful examples based on the least - elastic-net or L1 distortion criterion. - :param batch_size: Number of attacks to run simultaneously. - :param confidence: Confidence of adversarial examples: higher produces - examples with larger l2 distortion, but more - strongly classified as adversarial. - :param targeted: boolean controlling the behavior of the adversarial - examples produced. If set to False, they will be - misclassified in any wrong class. If set to True, - they will be misclassified in a chosen target class. - :param learning_rate: The learning rate for the attack algorithm. - Smaller values produce better results but are - slower to converge. - :param binary_search_steps: The number of times we perform binary - search to find the optimal tradeoff- - constant between norm of the perturbation - and confidence of the classification. Set - 'initial_const' to a large value and fix - this param to 1 for speed. - :param max_iterations: The maximum number of iterations. Setting this - to a larger value will produce lower distortion - results. Using only a few iterations requires - a larger learning rate, and will produce larger - distortion results. - :param abort_early: If true, allows early abort when the total - loss starts to increase (greatly speeds up attack, - but hurts performance, particularly on ImageNet) - :param initial_const: The initial tradeoff-constant to use to tune the - relative importance of size of the perturbation - and confidence of classification. - If binary_search_steps is large, the initial - constant is not important. A smaller value of - this constant gives lower distortion results. - For computational efficiency, fix - binary_search_steps to 1 and set this param - to a large value. - :param clip_min: (optional float) Minimum input component value. - :param clip_max: (optional float) Maximum input component value. - :param num_labels: the number of classes in the model's output. - :param shape: the shape of the model's input tensor. - """ - - self.sess = sess - self.TARGETED = targeted - self.LEARNING_RATE = learning_rate - self.MAX_ITERATIONS = max_iterations - self.BINARY_SEARCH_STEPS = binary_search_steps - self.ABORT_EARLY = abort_early - self.CONFIDENCE = confidence - self.initial_const = initial_const - self.batch_size = batch_size - self.clip_min = clip_min - self.clip_max = clip_max - self.model = model - self.decision_rule = decision_rule - - self.beta = beta - self.beta_t = tf.cast(self.beta, tf_dtype) - - self.repeat = binary_search_steps >= 10 - - self.shape = shape = tuple([batch_size] + list(shape)) - - # these are variables to be more efficient in sending data to tf - self.timg = tf.Variable(np.zeros(shape), dtype=tf_dtype, name='timg') - self.newimg = tf.Variable( - np.zeros(shape), dtype=tf_dtype, name='newimg') - self.slack = tf.Variable( - np.zeros(shape), dtype=tf_dtype, name='slack') - self.tlab = tf.Variable( - np.zeros((batch_size, num_labels)), dtype=tf_dtype, name='tlab') - self.const = tf.Variable( - np.zeros(batch_size), dtype=tf_dtype, name='const') - - # and here's what we use to assign them - self.assign_timg = tf.placeholder(tf_dtype, shape, name='assign_timg') - self.assign_newimg = tf.placeholder( - tf_dtype, shape, name='assign_newimg') - self.assign_slack = tf.placeholder( - tf_dtype, shape, name='assign_slack') - self.assign_tlab = tf.placeholder( - tf_dtype, (batch_size, num_labels), name='assign_tlab') - self.assign_const = tf.placeholder( - tf_dtype, [batch_size], name='assign_const') - - self.global_step = tf.Variable(0, trainable=False) - self.global_step_t = tf.cast(self.global_step, tf_dtype) - - # Fast Iterative Shrinkage Thresholding - self.zt = tf.divide(self.global_step_t, - self.global_step_t + tf.cast(3, tf_dtype)) - cond1 = tf.cast(tf.greater(tf.subtract(self.slack, self.timg), - self.beta_t), tf_dtype) - cond2 = tf.cast(tf.less_equal(tf.abs(tf.subtract(self.slack, - self.timg)), - self.beta_t), tf_dtype) - cond3 = tf.cast(tf.less(tf.subtract(self.slack, self.timg), - tf.negative(self.beta_t)), tf_dtype) - - upper = tf.minimum(tf.subtract(self.slack, self.beta_t), - tf.cast(self.clip_max, tf_dtype)) - lower = tf.maximum(tf.add(self.slack, self.beta_t), - tf.cast(self.clip_min, tf_dtype)) - - self.assign_newimg = tf.multiply(cond1, upper) - self.assign_newimg += tf.multiply(cond2, self.timg) - self.assign_newimg += tf.multiply(cond3, lower) - - self.assign_slack = self.assign_newimg - self.assign_slack += tf.multiply(self.zt, - self.assign_newimg - self.newimg) - - # -------------------------------- - self.setter = tf.assign(self.newimg, self.assign_newimg) - self.setter_y = tf.assign(self.slack, self.assign_slack) - - # prediction BEFORE-SOFTMAX of the model - self.output = model.get_logits(self.newimg) - self.output_y = model.get_logits(self.slack) - - # distance to the input data - self.l2dist = reduce_sum(tf.square(self.newimg-self.timg), - list(range(1, len(shape)))) - self.l2dist_y = reduce_sum(tf.square(self.slack-self.timg), - list(range(1, len(shape)))) - self.l1dist = reduce_sum(tf.abs(self.newimg-self.timg), - list(range(1, len(shape)))) - self.l1dist_y = reduce_sum(tf.abs(self.slack-self.timg), - list(range(1, len(shape)))) - self.elasticdist = self.l2dist + tf.multiply(self.l1dist, - self.beta_t) - self.elasticdist_y = self.l2dist_y + tf.multiply(self.l1dist_y, - self.beta_t) - if self.decision_rule == 'EN': - self.crit = self.elasticdist - self.crit_p = 'Elastic' - else: - self.crit = self.l1dist - self.crit_p = 'L1' - - # compute the probability of the label class versus the maximum other - real = reduce_sum((self.tlab) * self.output, 1) - real_y = reduce_sum((self.tlab) * self.output_y, 1) - other = reduce_max((1 - self.tlab) * self.output - - (self.tlab * 10000), 1) - other_y = reduce_max((1 - self.tlab) * self.output_y - - (self.tlab * 10000), 1) - - if self.TARGETED: - # if targeted, optimize for making the other class most likely - loss1 = tf.maximum(ZERO(), other - real + self.CONFIDENCE) - loss1_y = tf.maximum(ZERO(), other_y - real_y + self.CONFIDENCE) - else: - # if untargeted, optimize for making this class least likely. - loss1 = tf.maximum(ZERO(), real - other + self.CONFIDENCE) - loss1_y = tf.maximum(ZERO(), real_y - other_y + self.CONFIDENCE) - - # sum up the losses - self.loss21 = reduce_sum(self.l1dist) - self.loss21_y = reduce_sum(self.l1dist_y) - self.loss2 = reduce_sum(self.l2dist) - self.loss2_y = reduce_sum(self.l2dist_y) - self.loss1 = reduce_sum(self.const * loss1) - self.loss1_y = reduce_sum(self.const * loss1_y) - self.loss_opt = self.loss1_y + self.loss2_y - self.loss = self.loss1+self.loss2+tf.multiply(self.beta_t, self.loss21) - - self.learning_rate = tf.train.polynomial_decay( - self.LEARNING_RATE, - self.global_step, - self.MAX_ITERATIONS, - 0, - power=0.5) - - # Setup the optimizer and keep track of variables we're creating - start_vars = set(x.name for x in tf.global_variables()) - optimizer = tf.train.GradientDescentOptimizer(self.learning_rate) - self.train = optimizer.minimize(self.loss_opt, - var_list=[self.slack], - global_step=self.global_step) - end_vars = tf.global_variables() - new_vars = [x for x in end_vars if x.name not in start_vars] - - # these are the variables to initialize when we run - self.setup = [] - self.setup.append(self.timg.assign(self.assign_timg)) - self.setup.append(self.tlab.assign(self.assign_tlab)) - self.setup.append(self.const.assign(self.assign_const)) - - var_list = [self.global_step]+[self.slack]+[self.newimg]+new_vars - self.init = tf.variables_initializer(var_list=var_list) - - def attack(self, imgs, targets): - """ - Perform the EAD attack on the given instance for the given targets. - - If self.targeted is true, then the targets represents the target labels - If self.targeted is false, then targets are the original class labels - """ - - batch_size = self.batch_size - r = [] - for i in range(0, len(imgs) // batch_size): - _logger.debug( - ("Running EAD attack on instance %s of %s", - i * batch_size, len(imgs))) - r.extend( - self.attack_batch( - imgs[i * batch_size:(i + 1) * batch_size], - targets[i * batch_size:(i + 1) * batch_size])) - if len(imgs) % batch_size != 0: - last_elements = len(imgs) - (len(imgs) % batch_size) - _logger.debug( - ("Running EAD attack on instance %s of %s", - last_elements, len(imgs))) - temp_imgs = np.zeros((batch_size, ) + imgs.shape[2:]) - temp_targets = np.zeros((batch_size, ) + targets.shape[2:]) - temp_imgs[:(len(imgs) % batch_size)] = imgs[last_elements:] - temp_targets[:(len(imgs) % batch_size)] = targets[last_elements:] - temp_data = self.attack_batch(temp_imgs, temp_targets) - r.extend(temp_data[:(len(imgs) % batch_size)], - targets[last_elements:]) - return np.array(r) - - def attack_batch(self, imgs, labs): - """ - Run the attack on a batch of instance and labels. - """ - - def compare(x, y): - if not isinstance(x, (float, int, np.int64)): - x = np.copy(x) - if self.TARGETED: - x[y] -= self.CONFIDENCE - else: - x[y] += self.CONFIDENCE - x = np.argmax(x) - if self.TARGETED: - return x == y - else: - return x != y - - batch_size = self.batch_size - - imgs = np.clip(imgs, self.clip_min, self.clip_max) - - # set the lower and upper bounds accordingly - lower_bound = np.zeros(batch_size) - CONST = np.ones(batch_size) * self.initial_const - upper_bound = np.ones(batch_size) * 1e10 - - # placeholders for the best en, score, and instance attack found so far - o_bestdst = [1e10] * batch_size - o_bestscore = [-1] * batch_size - o_bestattack = np.copy(imgs) - - for outer_step in range(self.BINARY_SEARCH_STEPS): - # completely reset the optimizer's internal state. - self.sess.run(self.init) - batch = imgs[:batch_size] - batchlab = labs[:batch_size] - - bestdst = [1e10] * batch_size - bestscore = [-1] * batch_size - _logger.debug(" Binary search step %s of %s", - outer_step, self.BINARY_SEARCH_STEPS) - - # The last iteration (if we run many steps) repeat the search once. - if self.repeat and outer_step == self.BINARY_SEARCH_STEPS - 1: - CONST = upper_bound - - # set the variables so that we don't have to send them over again - self.sess.run( - self.setup, { - self.assign_timg: batch, - self.assign_tlab: batchlab, - self.assign_const: CONST - }) - self.sess.run(self.setter, {self.assign_newimg: batch}) - self.sess.run(self.setter_y, {self.assign_slack: batch}) - prev = 1e6 - for iteration in range(self.MAX_ITERATIONS): - # perform the attack - self.sess.run([self.train]) - self.sess.run([self.setter, self.setter_y]) - l, l2s, l1s, crit, scores, nimg = self.sess.run([self.loss, - self.l2dist, - self.l1dist, - self.crit, - self.output, - self.newimg]) - if iteration % ((self.MAX_ITERATIONS // 10) or 1) == 0: - _logger.debug((" Iteration {} of {}: loss={:.3g} " + - "l2={:.3g} l1={:.3g} f={:.3g}").format( - iteration, self.MAX_ITERATIONS, l, - np.mean(l2s), np.mean(l1s), - np.mean(scores))) - - # check if we should abort search if we're getting nowhere. - if self.ABORT_EARLY and \ - iteration % ((self.MAX_ITERATIONS // 10) or 1) == 0: - if l > prev * .9999: - msg = " Failed to make progress; stop early" - _logger.debug(msg) - break - prev = l - - # adjust the best result found so far - for e, (dst, sc, ii) in enumerate(zip(crit, scores, nimg)): - lab = np.argmax(batchlab[e]) - if dst < bestdst[e] and compare(sc, lab): - bestdst[e] = dst - bestscore[e] = np.argmax(sc) - if dst < o_bestdst[e] and compare(sc, lab): - o_bestdst[e] = dst - o_bestscore[e] = np.argmax(sc) - o_bestattack[e] = ii - - # adjust the constant as needed - for e in range(batch_size): - if compare(bestscore[e], np.argmax(batchlab[e])) and \ - bestscore[e] != -1: - # success, divide const by two - upper_bound[e] = min(upper_bound[e], CONST[e]) - if upper_bound[e] < 1e9: - CONST[e] = (lower_bound[e] + upper_bound[e]) / 2 - else: - # failure, either multiply by 10 if no solution found yet - # or do binary search with the known upper bound - lower_bound[e] = max(lower_bound[e], CONST[e]) - if upper_bound[e] < 1e9: - CONST[e] = (lower_bound[e] + upper_bound[e]) / 2 - else: - CONST[e] *= 10 - _logger.debug(" Successfully generated adversarial examples " + - "on {} of {} instances.".format( - sum(upper_bound < 1e9), batch_size)) - o_bestdst = np.array(o_bestdst) - mean = np.mean(np.sqrt(o_bestdst[o_bestdst < 1e9])) - _logger.debug(self.crit_p + - " Mean successful distortion: {:.4g}".format(mean)) - - # return the best solution found - o_bestdst = np.array(o_bestdst) - return o_bestattack diff --git a/cleverhans/attacks/fast_feature_adversaries.py b/cleverhans/attacks/fast_feature_adversaries.py deleted file mode 100644 index f5bc542c4..000000000 --- a/cleverhans/attacks/fast_feature_adversaries.py +++ /dev/null @@ -1,165 +0,0 @@ -""" -The FastFeatureAdversaries attack -""" -# pylint: disable=missing-docstring -import warnings - -import numpy as np -import tensorflow as tf - -from cleverhans.attacks.attack import Attack -from cleverhans.compat import reduce_sum -from cleverhans.model import Model -from cleverhans.utils_tf import clip_eta - - -class FastFeatureAdversaries(Attack): - """ - This is a fast implementation of "Feature Adversaries", an attack - against a target internal representation of a model. - "Feature adversaries" were originally introduced in (Sabour et al. 2016), - where the optimization was done using LBFGS. - Paper link: https://arxiv.org/abs/1511.05122 - - This implementation is similar to "Basic Iterative Method" - (Kurakin et al. 2016) but applied to the internal representations. - - :param model: cleverhans.model.Model - :param sess: optional tf.Session - :param dtypestr: dtype of the data - :param kwargs: passed through to super constructor - """ - - def __init__(self, model, sess=None, dtypestr='float32', **kwargs): - """ - Create a FastFeatureAdversaries instance. - """ - super(FastFeatureAdversaries, self).__init__(model, sess, dtypestr, - **kwargs) - self.feedable_kwargs = ('eps', 'eps_iter', 'clip_min', 'clip_max') - self.structural_kwargs = ['ord', 'nb_iter', 'layer'] - - assert isinstance(self.model, Model) - - def parse_params(self, - layer=None, - eps=0.3, - eps_iter=0.05, - nb_iter=10, - ord=np.inf, - clip_min=None, - clip_max=None, - **kwargs): - """ - Take in a dictionary of parameters and applies attack-specific checks - before saving them as attributes. - - Attack-specific parameters: - - :param layer: (required str) name of the layer to target. - :param eps: (optional float) maximum distortion of adversarial example - compared to original input - :param eps_iter: (optional float) step size for each attack iteration - :param nb_iter: (optional int) Number of attack iterations. - :param ord: (optional) Order of the norm (mimics Numpy). - Possible values: np.inf, 1 or 2. - :param clip_min: (optional float) Minimum input component value - :param clip_max: (optional float) Maximum input component value - """ - - # Save attack-specific parameters - self.layer = layer - self.eps = eps - self.eps_iter = eps_iter - self.nb_iter = nb_iter - self.ord = ord - self.clip_min = clip_min - self.clip_max = clip_max - - # Check if order of the norm is acceptable given current implementation - if self.ord not in [np.inf, 1, 2]: - raise ValueError("Norm order must be either np.inf, 1, or 2.") - if len(kwargs.keys()) > 0: - warnings.warn("kwargs is unused and will be removed on or after " - "2019-04-26.") - - return True - - def attack_single_step(self, x, eta, g_feat): - """ - TensorFlow implementation of the Fast Feature Gradient. This is a - single step attack similar to Fast Gradient Method that attacks an - internal representation. - - :param x: the input placeholder - :param eta: A tensor the same shape as x that holds the perturbation. - :param g_feat: model's internal tensor for guide - :return: a tensor for the adversarial example - """ - - adv_x = x + eta - a_feat = self.model.fprop(adv_x)[self.layer] - - # feat.shape = (batch, c) or (batch, w, h, c) - axis = list(range(1, len(a_feat.shape))) - - # Compute loss - # This is a targeted attack, hence the negative sign - loss = -reduce_sum(tf.square(a_feat - g_feat), axis) - - # Define gradient of loss wrt input - grad, = tf.gradients(loss, adv_x) - - # Multiply by constant epsilon - scaled_signed_grad = self.eps_iter * tf.sign(grad) - - # Add perturbation to original example to obtain adversarial example - adv_x = adv_x + scaled_signed_grad - - # If clipping is needed, - # reset all values outside of [clip_min, clip_max] - if (self.clip_min is not None) and (self.clip_max is not None): - adv_x = tf.clip_by_value(adv_x, self.clip_min, self.clip_max) - - adv_x = tf.stop_gradient(adv_x) - - eta = adv_x - x - eta = clip_eta(eta, self.ord, self.eps) - - return eta - - def generate(self, x, g, **kwargs): - """ - Generate symbolic graph for adversarial examples and return. - - :param x: The model's symbolic inputs. - :param g: The target value of the symbolic representation - :param kwargs: See `parse_params` - """ - - # Parse and save attack-specific parameters - assert self.parse_params(**kwargs) - - g_feat = self.model.fprop(g)[self.layer] - - # Initialize loop variables - eta = tf.random_uniform( - tf.shape(x), -self.eps, self.eps, dtype=self.tf_dtype) - eta = clip_eta(eta, self.ord, self.eps) - - def cond(i, _): - return tf.less(i, self.nb_iter) - - def body(i, e): - new_eta = self.attack_single_step(x, e, g_feat) - return i + 1, new_eta - - _, eta = tf.while_loop(cond, body, (tf.zeros([]), eta), back_prop=True, - maximum_iterations=self.nb_iter) - - # Define adversarial example (and clip if necessary) - adv_x = x + eta - if self.clip_min is not None and self.clip_max is not None: - adv_x = tf.clip_by_value(adv_x, self.clip_min, self.clip_max) - - return adv_x diff --git a/cleverhans/attacks/fast_gradient_method.py b/cleverhans/attacks/fast_gradient_method.py deleted file mode 100644 index b1a747b0c..000000000 --- a/cleverhans/attacks/fast_gradient_method.py +++ /dev/null @@ -1,265 +0,0 @@ -""" -The FastGradientMethod attack. -""" - -import warnings - -import numpy as np -import tensorflow as tf - -from cleverhans.attacks.attack import Attack -from cleverhans.compat import reduce_max, reduce_sum, softmax_cross_entropy_with_logits -from cleverhans import utils_tf - - -class FastGradientMethod(Attack): - """ - This attack was originally implemented by Goodfellow et al. (2014) with the - infinity norm (and is known as the "Fast Gradient Sign Method"). This - implementation extends the attack to other norms, and is therefore called - the Fast Gradient Method. - Paper link: https://arxiv.org/abs/1412.6572 - - :param model: cleverhans.model.Model - :param sess: optional tf.Session - :param dtypestr: dtype of the data - :param kwargs: passed through to super constructor - """ - - def __init__(self, model, sess=None, dtypestr='float32', **kwargs): - """ - Create a FastGradientMethod instance. - Note: the model parameter should be an instance of the - cleverhans.model.Model abstraction provided by CleverHans. - """ - - super(FastGradientMethod, self).__init__(model, sess, dtypestr, **kwargs) - self.feedable_kwargs = ('eps', 'y', 'y_target', 'clip_min', 'clip_max') - self.structural_kwargs = ['ord', 'sanity_checks', 'clip_grad', 'loss_fn'] - - def generate(self, x, **kwargs): - """ - Returns the graph for Fast Gradient Method adversarial examples. - - :param x: The model's symbolic inputs. - :param kwargs: See `parse_params` - """ - # Parse and save attack-specific parameters - assert self.parse_params(**kwargs) - - labels, _nb_classes = self.get_or_guess_labels(x, kwargs) - - return fgm( - x, - self.model.get_logits(x), - y=labels, - eps=self.eps, - ord=self.ord, - loss_fn=self.loss_fn, - clip_min=self.clip_min, - clip_max=self.clip_max, - clip_grad=self.clip_grad, - targeted=(self.y_target is not None), - sanity_checks=self.sanity_checks) - - def parse_params(self, - eps=0.3, - ord=np.inf, - loss_fn=softmax_cross_entropy_with_logits, - y=None, - y_target=None, - clip_min=None, - clip_max=None, - clip_grad=False, - sanity_checks=True, - **kwargs): - """ - Take in a dictionary of parameters and applies attack-specific checks - before saving them as attributes. - - Attack-specific parameters: - - :param eps: (optional float) attack step size (input variation) - :param ord: (optional) Order of the norm (mimics NumPy). - Possible values: np.inf, 1 or 2. - :param loss_fn: Loss function that takes (labels, logits) as arguments and returns loss - :param y: (optional) A tensor with the true labels. Only provide - this parameter if you'd like to use true labels when crafting - adversarial samples. Otherwise, model predictions are used as - labels to avoid the "label leaking" effect (explained in this - paper: https://arxiv.org/abs/1611.01236). Default is None. - Labels should be one-hot-encoded. - :param y_target: (optional) A tensor with the labels to target. Leave - y_target=None if y is also set. Labels should be - one-hot-encoded. - :param clip_min: (optional float) Minimum input component value - :param clip_max: (optional float) Maximum input component value - :param clip_grad: (optional bool) Ignore gradient components - at positions where the input is already at the boundary - of the domain, and the update step will get clipped out. - :param sanity_checks: bool, if True, include asserts - (Turn them off to use less runtime / memory or for unit tests that - intentionally pass strange input) - """ - # Save attack-specific parameters - - self.eps = eps - self.ord = ord - self.loss_fn = loss_fn - self.y = y - self.y_target = y_target - self.clip_min = clip_min - self.clip_max = clip_max - self.clip_grad = clip_grad - self.sanity_checks = sanity_checks - - if self.y is not None and self.y_target is not None: - raise ValueError("Must not set both y and y_target") - # Check if order of the norm is acceptable given current implementation - if self.ord not in [np.inf, int(1), int(2)]: - raise ValueError("Norm order must be either np.inf, 1, or 2.") - - if self.clip_grad and (self.clip_min is None or self.clip_max is None): - raise ValueError("Must set clip_min and clip_max if clip_grad is set") - - if len(kwargs.keys()) > 0: - warnings.warn("kwargs is unused and will be removed on or after " - "2019-04-26.") - - return True - - -def fgm(x, - logits, - y=None, - eps=0.3, - ord=np.inf, - loss_fn=softmax_cross_entropy_with_logits, - clip_min=None, - clip_max=None, - clip_grad=False, - targeted=False, - sanity_checks=True): - """ - TensorFlow implementation of the Fast Gradient Method. - :param x: the input placeholder - :param logits: output of model.get_logits - :param y: (optional) A placeholder for the true labels. If targeted - is true, then provide the target label. Otherwise, only provide - this parameter if you'd like to use true labels when crafting - adversarial samples. Otherwise, model predictions are used as - labels to avoid the "label leaking" effect (explained in this - paper: https://arxiv.org/abs/1611.01236). Default is None. - Labels should be one-hot-encoded. - :param eps: the epsilon (input variation parameter) - :param ord: (optional) Order of the norm (mimics NumPy). - Possible values: np.inf, 1 or 2. - :param loss_fn: Loss function that takes (labels, logits) as arguments and returns loss - :param clip_min: Minimum float value for adversarial example components - :param clip_max: Maximum float value for adversarial example components - :param clip_grad: (optional bool) Ignore gradient components - at positions where the input is already at the boundary - of the domain, and the update step will get clipped out. - :param targeted: Is the attack targeted or untargeted? Untargeted, the - default, will try to make the label incorrect. Targeted - will instead try to move in the direction of being more - like y. - :return: a tensor for the adversarial example - """ - - asserts = [] - - # If a data range was specified, check that the input was in that range - if clip_min is not None: - asserts.append(utils_tf.assert_greater_equal( - x, tf.cast(clip_min, x.dtype))) - - if clip_max is not None: - asserts.append(utils_tf.assert_less_equal(x, tf.cast(clip_max, x.dtype))) - - # Make sure the caller has not passed probs by accident - assert logits.op.type != 'Softmax' - - if y is None: - # Using model predictions as ground truth to avoid label leaking - preds_max = reduce_max(logits, 1, keepdims=True) - y = tf.to_float(tf.equal(logits, preds_max)) - y = tf.stop_gradient(y) - y = y / reduce_sum(y, 1, keepdims=True) - - # Compute loss - loss = loss_fn(labels=y, logits=logits) - if targeted: - loss = -loss - - # Define gradient of loss wrt input - grad, = tf.gradients(loss, x) - - if clip_grad: - grad = utils_tf.zero_out_clipped_grads(grad, x, clip_min, clip_max) - - optimal_perturbation = optimize_linear(grad, eps, ord) - - # Add perturbation to original example to obtain adversarial example - adv_x = x + optimal_perturbation - - # If clipping is needed, reset all values outside of [clip_min, clip_max] - if (clip_min is not None) or (clip_max is not None): - # We don't currently support one-sided clipping - assert clip_min is not None and clip_max is not None - adv_x = utils_tf.clip_by_value(adv_x, clip_min, clip_max) - - if sanity_checks: - with tf.control_dependencies(asserts): - adv_x = tf.identity(adv_x) - - return adv_x - - -def optimize_linear(grad, eps, ord=np.inf): - """ - Solves for the optimal input to a linear function under a norm constraint. - - Optimal_perturbation = argmax_{eta, ||eta||_{ord} < eps} dot(eta, grad) - - :param grad: tf tensor containing a batch of gradients - :param eps: float scalar specifying size of constraint region - :param ord: int specifying order of norm - :returns: - tf tensor containing optimal perturbation - """ - - # In Python 2, the `list` call in the following line is redundant / harmless. - # In Python 3, the `list` call is needed to convert the iterator returned by `range` into a list. - red_ind = list(range(1, len(grad.get_shape()))) - avoid_zero_div = 1e-12 - if ord == np.inf: - # Take sign of gradient - optimal_perturbation = tf.sign(grad) - # The following line should not change the numerical results. - # It applies only because `optimal_perturbation` is the output of - # a `sign` op, which has zero derivative anyway. - # It should not be applied for the other norms, where the - # perturbation has a non-zero derivative. - optimal_perturbation = tf.stop_gradient(optimal_perturbation) - elif ord == 1: - abs_grad = tf.abs(grad) - sign = tf.sign(grad) - max_abs_grad = tf.reduce_max(abs_grad, red_ind, keepdims=True) - tied_for_max = tf.to_float(tf.equal(abs_grad, max_abs_grad)) - num_ties = tf.reduce_sum(tied_for_max, red_ind, keepdims=True) - optimal_perturbation = sign * tied_for_max / num_ties - elif ord == 2: - square = tf.maximum(avoid_zero_div, - reduce_sum(tf.square(grad), - reduction_indices=red_ind, - keepdims=True)) - optimal_perturbation = grad / tf.sqrt(square) - else: - raise NotImplementedError("Only L-inf, L1 and L2 norms are " - "currently implemented.") - - # Scale perturbation to be the solution for the norm=eps rather than - # norm=1 problem - scaled_perturbation = utils_tf.mul(eps, optimal_perturbation) - return scaled_perturbation diff --git a/cleverhans/attacks/hop_skip_jump_attack.py b/cleverhans/attacks/hop_skip_jump_attack.py deleted file mode 100644 index e1b830b25..000000000 --- a/cleverhans/attacks/hop_skip_jump_attack.py +++ /dev/null @@ -1,543 +0,0 @@ -""" Boundary Attack++ -""" -import logging -import numpy as np -import tensorflow as tf -from warnings import warn -from cleverhans.attacks import Attack -from cleverhans.model import CallableModelWrapper, Model, wrapper_warning_logits -from cleverhans import utils, utils_tf - -np_dtype = np.dtype('float32') -tf_dtype = tf.as_dtype('float32') - -_logger = utils.create_logger("cleverhans.attacks.hop_skip_jump_attack") -_logger.setLevel(logging.INFO) - - -class HopSkipJumpAttack(Attack): - """ - HopSkipJumpAttack was originally proposed by Chen, Jordan and Wainwright. - It is a decision-based attack that requires access to output - labels of a model alone. - Paper link: https://arxiv.org/abs/1904.02144 - At a high level, this attack is an iterative attack composed of three - steps: Binary search to approach the boundary; gradient estimation; - stepsize search. HopSkipJumpAttack requires fewer model queries than - Boundary Attack which was based on rejective sampling. - :param model: cleverhans.model.Model - :param sess: tf.Session - :param dtypestr: dtype of the data - :param kwargs: passed through to super constructor. - see parse_params for details. - """ - - def __init__(self, model, sess, dtypestr='float32', **kwargs): - """ - Note: the model parameter should be an instance of the - cleverhans.model.Model abstraction provided by CleverHans. - """ - if not isinstance(model, Model): - wrapper_warning_logits() - model = CallableModelWrapper(model, 'logits') - - super(HopSkipJumpAttack, self).__init__(model, sess, - dtypestr, **kwargs) - - self.feedable_kwargs = ('y_target', 'image_target') - - self.structural_kwargs = [ - 'stepsize_search', - 'clip_min', - 'clip_max', - 'constraint', - 'num_iterations', - 'initial_num_evals', - 'max_num_evals', - 'batch_size', - 'verbose', - 'gamma', - ] - - def generate(self, x, **kwargs): - """ - Return a tensor that constructs adversarial examples for the given - input. Generate uses tf.py_func in order to operate over tensors. - :param x: A tensor with the inputs. - :param kwargs: See `parse_params` - """ - self.parse_params(**kwargs) - shape = [int(i) for i in x.get_shape().as_list()[1:]] - - assert self.sess is not None, \ - 'Cannot use `generate` when no `sess` was provided' - _check_first_dimension(x, 'input') - if self.y_target is not None: - _check_first_dimension(self.y_target, 'y_target') - assert self.image_target is not None, \ - 'Require a target image for targeted attack.' - _check_first_dimension(self.image_target, 'image_target') - - # Set shape and d. - self.shape = shape - self.d = int(np.prod(shape)) - - # Set binary search threshold. - if self.constraint == 'l2': - self.theta = self.gamma / (np.sqrt(self.d) * self.d) - else: - self.theta = self.gamma / (self.d * self.d) - - # Construct input placeholder and output for decision function. - self.input_ph = tf.placeholder( - tf_dtype, [None] + list(self.shape), name='input_image') - self.logits = self.model.get_logits(self.input_ph) - - def hsja_wrap(x, target_label, target_image): - """ Wrapper to use tensors as input and output. """ - return np.array(self._hsja(x, target_label, target_image), - dtype=self.np_dtype) - - if self.y_target is not None: - # targeted attack that requires target label and image. - wrap = tf.py_func(hsja_wrap, - [x[0], self.y_target[0], self.image_target[0]], - self.tf_dtype) - else: - if self.image_target is not None: - # untargeted attack with an initialized image. - wrap = tf.py_func(lambda x, target_image: hsja_wrap(x, - None, target_image), - [x[0], self.image_target[0]], - self.tf_dtype) - else: - # untargeted attack without an initialized image. - wrap = tf.py_func(lambda x: hsja_wrap(x, None, None), - [x[0]], - self.tf_dtype) - - wrap.set_shape(x.get_shape()) - - return wrap - - def generate_np(self, x, **kwargs): - """ - Generate adversarial images in a for loop. - :param y: An array of shape (n, nb_classes) for true labels. - :param y_target: An array of shape (n, nb_classes) for target labels. - Required for targeted attack. - :param image_target: An array of shape (n, **image shape) for initial - target images. Required for targeted attack. - - See parse_params for other kwargs. - - """ - - x_adv = [] - - if 'image_target' in kwargs and kwargs['image_target'] is not None: - image_target = np.copy(kwargs['image_target']) - else: - image_target = None - if 'y_target' in kwargs and kwargs['y_target'] is not None: - y_target = np.copy(kwargs['y_target']) - else: - y_target = None - - for i, x_single in enumerate(x): - img = np.expand_dims(x_single, axis=0) - if image_target is not None: - single_img_target = np.expand_dims(image_target[i], axis=0) - kwargs['image_target'] = single_img_target - if y_target is not None: - single_y_target = np.expand_dims(y_target[i], axis=0) - kwargs['y_target'] = single_y_target - - adv_img = super(HopSkipJumpAttack, - self).generate_np(img, **kwargs) - x_adv.append(adv_img) - - return np.concatenate(x_adv, axis=0) - - def parse_params(self, - y_target=None, - image_target=None, - initial_num_evals=100, - max_num_evals=10000, - stepsize_search='geometric_progression', - num_iterations=64, - gamma=1.0, - constraint='l2', - batch_size=128, - verbose=True, - clip_min=0, - clip_max=1): - """ - :param y: A tensor of shape (1, nb_classes) for true labels. - :param y_target: A tensor of shape (1, nb_classes) for target labels. - Required for targeted attack. - :param image_target: A tensor of shape (1, **image shape) for initial - target images. Required for targeted attack. - :param initial_num_evals: initial number of evaluations for - gradient estimation. - :param max_num_evals: maximum number of evaluations for gradient estimation. - :param stepsize_search: How to search for stepsize; choices are - 'geometric_progression', 'grid_search'. - 'geometric progression' initializes the stepsize - by ||x_t - x||_p / sqrt(iteration), and keep - decreasing by half until reaching the target - side of the boundary. 'grid_search' chooses the - optimal epsilon over a grid, in the scale of - ||x_t - x||_p. - :param num_iterations: The number of iterations. - :param gamma: The binary search threshold theta is gamma / d^{3/2} for - l2 attack and gamma / d^2 for linf attack. - :param constraint: The distance to optimize; choices are 'l2', 'linf'. - :param batch_size: batch_size for model prediction. - :param verbose: (boolean) Whether distance at each step is printed. - :param clip_min: (optional float) Minimum input component value - :param clip_max: (optional float) Maximum input component value - """ - - # ignore the y and y_target argument - self.y_target = y_target - self.image_target = image_target - self.initial_num_evals = initial_num_evals - self.max_num_evals = max_num_evals - self.stepsize_search = stepsize_search - self.num_iterations = num_iterations - self.gamma = gamma - self.constraint = constraint - self.batch_size = batch_size - self.clip_min = clip_min - self.clip_max = clip_max - self.verbose = verbose - - def _hsja(self, sample, target_label, target_image): - """ - Main algorithm for HopSkipJumpAttack. - - Return a tensor that constructs adversarial examples for the given - input. Generate uses tf.py_func in order to operate over tensors. - - :param sample: input image. Without the batchsize dimension. - :param target_label: integer for targeted attack, - None for nontargeted attack. Without the batchsize dimension. - :param target_image: an array with the same size as sample, or None. - Without the batchsize dimension. - - - Output: - perturbed image. - - """ - - # Original label required for untargeted attack. - if target_label is None: - original_label = np.argmax( - self.sess.run(self.logits, feed_dict={self.input_ph: sample[None]}) - ) - else: - target_label = np.argmax(target_label) - - def decision_function(images): - """ - Decision function output 1 on the desired side of the boundary, - 0 otherwise. - """ - images = clip_image(images, self.clip_min, self.clip_max) - prob = [] - for i in range(0, len(images), self.batch_size): - batch = images[i:i+self.batch_size] - prob_i = self.sess.run(self.logits, feed_dict={self.input_ph: batch}) - prob.append(prob_i) - prob = np.concatenate(prob, axis=0) - if target_label is None: - return np.argmax(prob, axis=1) != original_label - else: - return np.argmax(prob, axis=1) == target_label - - # Initialize. - if target_image is None: - perturbed = initialize(decision_function, sample, self.shape, - self.clip_min, self.clip_max) - else: - perturbed = target_image - - # Project the initialization to the boundary. - perturbed, dist_post_update = binary_search_batch(sample, - np.expand_dims(perturbed, 0), - decision_function, - self.shape, - self.constraint, - self.theta) - - dist = compute_distance(perturbed, sample, self.constraint) - - for j in np.arange(self.num_iterations): - current_iteration = j + 1 - - # Choose delta. - delta = select_delta(dist_post_update, current_iteration, - self.clip_max, self.clip_min, self.d, - self.theta, self.constraint) - - # Choose number of evaluations. - num_evals = int(min([self.initial_num_evals * np.sqrt(j+1), - self.max_num_evals])) - - # approximate gradient. - gradf = approximate_gradient(decision_function, perturbed, num_evals, - delta, self.constraint, self.shape, - self.clip_min, self.clip_max) - if self.constraint == 'linf': - update = np.sign(gradf) - else: - update = gradf - - # search step size. - if self.stepsize_search == 'geometric_progression': - # find step size. - epsilon = geometric_progression_for_stepsize(perturbed, - update, dist, decision_function, current_iteration) - - # Update the sample. - perturbed = clip_image(perturbed + epsilon * update, - self.clip_min, self.clip_max) - - # Binary search to return to the boundary. - perturbed, dist_post_update = binary_search_batch(sample, - perturbed[None], - decision_function, - self.shape, - self.constraint, - self.theta) - - elif self.stepsize_search == 'grid_search': - # Grid search for stepsize. - epsilons = np.logspace(-4, 0, num=20, endpoint=True) * dist - epsilons_shape = [20] + len(self.shape) * [1] - perturbeds = perturbed + epsilons.reshape(epsilons_shape) * update - perturbeds = clip_image(perturbeds, self.clip_min, self.clip_max) - idx_perturbed = decision_function(perturbeds) - - if np.sum(idx_perturbed) > 0: - # Select the perturbation that yields the minimum distance # after binary search. - perturbed, dist_post_update = binary_search_batch(sample, - perturbeds[idx_perturbed], - decision_function, - self.shape, - self.constraint, - self.theta) - - # compute new distance. - dist = compute_distance(perturbed, sample, self.constraint) - if self.verbose: - print('iteration: {:d}, {:s} distance {:.4E}'.format( - j+1, self.constraint, dist)) - - perturbed = np.expand_dims(perturbed, 0) - return perturbed - - -def BoundaryAttackPlusPlus(model, sess, dtypestr='float32', **kwargs): - """ - A previous name used for HopSkipJumpAttack. - """ - warn("BoundaryAttackPlusPlus will be removed after 2019-12-08; use HopSkipJumpAttack.") - return HopSkipJumpAttack(model, sess, dtypestr, **kwargs) - -def _check_first_dimension(x, tensor_name): - message = "Tensor {} should have batch_size of 1.".format(tensor_name) - if x.get_shape().as_list()[0] is None: - check_batch = utils_tf.assert_equal(tf.shape(x)[0], 1, message=message) - with tf.control_dependencies([check_batch]): - x = tf.identity(x) - elif x.get_shape().as_list()[0] != 1: - raise ValueError(message) - - -def clip_image(image, clip_min, clip_max): - """ Clip an image, or an image batch, with upper and lower threshold. """ - return np.minimum(np.maximum(clip_min, image), clip_max) - - -def compute_distance(x_ori, x_pert, constraint='l2'): - """ Compute the distance between two images. """ - if constraint == 'l2': - dist = np.linalg.norm(x_ori - x_pert) - elif constraint == 'linf': - dist = np.max(abs(x_ori - x_pert)) - return dist - -def approximate_gradient(decision_function, sample, num_evals, - delta, constraint, shape, clip_min, clip_max): - """ Gradient direction estimation """ - # Generate random vectors. - noise_shape = [num_evals] + list(shape) - if constraint == 'l2': - rv = np.random.randn(*noise_shape) - elif constraint == 'linf': - rv = np.random.uniform(low=-1, high=1, size=noise_shape) - - axis = tuple(range(1, 1 + len(shape))) - rv = rv / np.sqrt(np.sum(rv ** 2, axis=axis, keepdims=True)) - perturbed = sample + delta * rv - perturbed = clip_image(perturbed, clip_min, clip_max) - rv = (perturbed - sample) / delta - - # query the model. - decisions = decision_function(perturbed) - decision_shape = [len(decisions)] + [1] * len(shape) - fval = 2 * decisions.astype(np_dtype).reshape(decision_shape) - 1.0 - - # Baseline subtraction (when fval differs) - if np.mean(fval) == 1.0: # label changes. - gradf = np.mean(rv, axis=0) - elif np.mean(fval) == -1.0: # label not change. - gradf = - np.mean(rv, axis=0) - else: - fval = fval - np.mean(fval) - gradf = np.mean(fval * rv, axis=0) - - # Get the gradient direction. - gradf = gradf / np.linalg.norm(gradf) - - return gradf - - -def project(original_image, perturbed_images, alphas, shape, constraint): - """ Projection onto given l2 / linf balls in a batch. """ - alphas_shape = [len(alphas)] + [1] * len(shape) - alphas = alphas.reshape(alphas_shape) - if constraint == 'l2': - projected = (1-alphas) * original_image + alphas * perturbed_images - elif constraint == 'linf': - projected = clip_image( - perturbed_images, - original_image - alphas, - original_image + alphas - ) - return projected - - -def binary_search_batch(original_image, perturbed_images, decision_function, - shape, constraint, theta): - """ Binary search to approach the boundary. """ - - # Compute distance between each of perturbed image and original image. - dists_post_update = np.array([ - compute_distance( - original_image, - perturbed_image, - constraint - ) - for perturbed_image in perturbed_images]) - - # Choose upper thresholds in binary searchs based on constraint. - if constraint == 'linf': - highs = dists_post_update - # Stopping criteria. - thresholds = np.minimum(dists_post_update * theta, theta) - else: - highs = np.ones(len(perturbed_images)) - thresholds = theta - - lows = np.zeros(len(perturbed_images)) - - while np.max((highs - lows) / thresholds) > 1: - # projection to mids. - mids = (highs + lows) / 2.0 - mid_images = project(original_image, perturbed_images, - mids, shape, constraint) - - # Update highs and lows based on model decisions. - decisions = decision_function(mid_images) - lows = np.where(decisions == 0, mids, lows) - highs = np.where(decisions == 1, mids, highs) - - out_images = project(original_image, perturbed_images, - highs, shape, constraint) - - # Compute distance of the output image to select the best choice. - # (only used when stepsize_search is grid_search.) - dists = np.array([ - compute_distance( - original_image, - out_image, - constraint - ) - for out_image in out_images]) - idx = np.argmin(dists) - - dist = dists_post_update[idx] - out_image = out_images[idx] - return out_image, dist - - -def initialize(decision_function, sample, shape, clip_min, clip_max): - """ - Efficient Implementation of BlendedUniformNoiseAttack in Foolbox. - """ - success = 0 - num_evals = 0 - - # Find a misclassified random noise. - while True: - random_noise = np.random.uniform(clip_min, clip_max, size=shape) - success = decision_function(random_noise[None])[0] - if success: - break - num_evals += 1 - message = "Initialization failed! Try to use a misclassified image as `target_image`" - assert num_evals < 1e4, message - - # Binary search to minimize l2 distance to original image. - low = 0.0 - high = 1.0 - while high - low > 0.001: - mid = (high + low) / 2.0 - blended = (1 - mid) * sample + mid * random_noise - success = decision_function(blended[None])[0] - if success: - high = mid - else: - low = mid - - initialization = (1 - high) * sample + high * random_noise - return initialization - - -def geometric_progression_for_stepsize(x, update, dist, decision_function, - current_iteration): - """ Geometric progression to search for stepsize. - Keep decreasing stepsize by half until reaching - the desired side of the boundary. - """ - epsilon = dist / np.sqrt(current_iteration) - while True: - updated = x + epsilon * update - success = decision_function(updated[None])[0] - if success: - break - else: - epsilon = epsilon / 2.0 - - return epsilon - - -def select_delta(dist_post_update, current_iteration, - clip_max, clip_min, d, theta, constraint): - """ - Choose the delta at the scale of distance - between x and perturbed sample. - """ - if current_iteration == 1: - delta = 0.1 * (clip_max - clip_min) - else: - if constraint == 'l2': - delta = np.sqrt(d) * theta * dist_post_update - elif constraint == 'linf': - delta = d * theta * dist_post_update - - return delta diff --git a/cleverhans/attacks/lbfgs.py b/cleverhans/attacks/lbfgs.py deleted file mode 100644 index 04c2f11e1..000000000 --- a/cleverhans/attacks/lbfgs.py +++ /dev/null @@ -1,281 +0,0 @@ -"""The LBFGS attack -""" - -import numpy as np -import tensorflow as tf - -from cleverhans.attacks.attack import Attack -from cleverhans.compat import reduce_sum, softmax_cross_entropy_with_logits -from cleverhans.model import CallableModelWrapper, Model, wrapper_warning -from cleverhans import utils -from cleverhans import utils_tf - -_logger = utils.create_logger("cleverhans.attacks.lbfgs") -tf_dtype = tf.as_dtype('float32') - - -class LBFGS(Attack): - """ - LBFGS is the first adversarial attack for convolutional neural networks, - and is a target & iterative attack. - Paper link: "https://arxiv.org/pdf/1312.6199.pdf" - :param model: cleverhans.model.Model - :param sess: tf.Session - :param dtypestr: dtype of the data - :param kwargs: passed through to super constructor - """ - - def __init__(self, model, sess, dtypestr='float32', **kwargs): - if not isinstance(model, Model): - wrapper_warning() - model = CallableModelWrapper(model, 'probs') - - super(LBFGS, self).__init__(model, sess, dtypestr, **kwargs) - - self.feedable_kwargs = ('y_target',) - self.structural_kwargs = [ - 'batch_size', 'binary_search_steps', 'max_iterations', - 'initial_const', 'clip_min', 'clip_max' - ] - - def generate(self, x, **kwargs): - """ - Return a tensor that constructs adversarial examples for the given - input. Generate uses tf.py_func in order to operate over tensors. - :param x: (required) A tensor with the inputs. - :param kwargs: See `parse_params` - """ - assert self.sess is not None, \ - 'Cannot use `generate` when no `sess` was provided' - self.parse_params(**kwargs) - - if self.y_target is None: - self.y_target, nb_classes = self.get_or_guess_labels(x, kwargs) - self.targeted_attack = False - else: - _, nb_classes = self.get_or_guess_labels(x, kwargs) - self.targeted_attack = True - - attack = LBFGS_impl( - self.sess, x, self.model.get_logits(x), - self.y_target, self.targeted_attack, - self.binary_search_steps, self.max_iterations, self.initial_const, - self.clip_min, self.clip_max, nb_classes, self.batch_size) - - def lbfgs_wrap(x_val, y_val): - """ - Wrapper creating TensorFlow interface for use with py_func - """ - return np.array(attack.attack(x_val, y_val), dtype=self.np_dtype) - - wrap = tf.py_func(lbfgs_wrap, [x, self.y_target], self.tf_dtype) - wrap.set_shape(x.get_shape()) - - return wrap - - def parse_params(self, - y_target=None, - batch_size=1, - binary_search_steps=5, - max_iterations=1000, - initial_const=1e-2, - clip_min=0, - clip_max=1): - """ - :param y_target: (optional) A tensor with the one-hot target labels. - :param batch_size: The number of inputs to include in a batch and - process simultaneously. - :param binary_search_steps: The number of times we perform binary - search to find the optimal tradeoff- - constant between norm of the purturbation - and cross-entropy loss of classification. - :param max_iterations: The maximum number of iterations. - :param initial_const: The initial tradeoff-constant to use to tune the - relative importance of size of the perturbation - and cross-entropy loss of the classification. - :param clip_min: (optional float) Minimum input component value - :param clip_max: (optional float) Maximum input component value - """ - self.y_target = y_target - self.batch_size = batch_size - self.binary_search_steps = binary_search_steps - self.max_iterations = max_iterations - self.initial_const = initial_const - self.clip_min = clip_min - self.clip_max = clip_max - - -class LBFGS_impl(object): - """ - Return a tensor that constructs adversarial examples for the given - input. Generate uses tf.py_func in order to operate over tensors. - :param sess: a TF session. - :param x: A tensor with the inputs. - :param logits: A tensor with model's output logits. - :param targeted_label: A tensor with the target labels. - :param binary_search_steps: The number of times we perform binary - search to find the optimal tradeoff- - constant between norm of the purturbation - and cross-entropy loss of classification. - :param max_iterations: The maximum number of iterations. - :param initial_const: The initial tradeoff-constant to use to tune the - relative importance of size of the purturbation - and cross-entropy loss of the classification. - :param clip_min: Minimum input component value - :param clip_max: Maximum input component value - :param num_labels: The number of classes in the model's output. - :param batch_size: Number of attacks to run simultaneously. - """ - - def __init__(self, sess, x, logits, targeted_label, targeted_attack, - binary_search_steps, max_iterations, initial_const, clip_min, - clip_max, nb_classes, batch_size): - self.sess = sess - self.x = x - self.logits = logits - assert logits.op.type != 'Softmax' - self.targeted_label = targeted_label - self.targeted_attack = targeted_attack - self.binary_search_steps = binary_search_steps - self.max_iterations = max_iterations - self.initial_const = initial_const - self.clip_min = clip_min - self.clip_max = clip_max - self.batch_size = batch_size - - self.repeat = self.binary_search_steps >= 10 - self.shape = tuple([self.batch_size] + - list(self.x.get_shape().as_list()[1:])) - self.ori_img = tf.Variable( - np.zeros(self.shape), dtype=tf_dtype, name='ori_img') - self.const = tf.Variable( - np.zeros(self.batch_size), dtype=tf_dtype, name='const') - - self.score = softmax_cross_entropy_with_logits( - labels=self.targeted_label, logits=self.logits) - self.l2dist = reduce_sum(tf.square(self.x - self.ori_img)) - # small self.const will result small adversarial perturbation - # targeted attack aims at minimize loss against target label - # untargeted attack aims at maximize loss against True label - if self.targeted_attack: - self.loss = reduce_sum(self.score * self.const) + self.l2dist - else: - self.loss = -reduce_sum(self.score * self.const) + self.l2dist - self.grad, = tf.gradients(self.loss, self.x) - - def attack(self, x_val, targets): - """ - Perform the attack on the given instance for the given targets. - """ - - def lbfgs_objective(adv_x, self, targets, oimgs, CONST): - """ returns the function value and the gradient for fmin_l_bfgs_b """ - loss = self.sess.run( - self.loss, - feed_dict={ - self.x: adv_x.reshape(oimgs.shape), - self.targeted_label: targets, - self.ori_img: oimgs, - self.const: CONST - }) - grad = self.sess.run( - self.grad, - feed_dict={ - self.x: adv_x.reshape(oimgs.shape), - self.targeted_label: targets, - self.ori_img: oimgs, - self.const: CONST - }) - return loss, grad.flatten().astype(float) - - def attack_success(out, target, targeted_attack): - """ returns attack result """ - if targeted_attack: - return out == target - else: - return out != target - - # begin the main part for the attack - from scipy.optimize import fmin_l_bfgs_b - oimgs = np.clip(x_val, self.clip_min, self.clip_max) - CONST = np.ones(self.batch_size) * self.initial_const - - # set the lower and upper bounds accordingly - lower_bound = np.zeros(self.batch_size) - upper_bound = np.ones(self.batch_size) * 1e10 - - # set the box constraints for the optimization function - clip_min = self.clip_min * np.ones(oimgs.shape[:]) - clip_max = self.clip_max * np.ones(oimgs.shape[:]) - clip_bound = list(zip(clip_min.flatten(), clip_max.flatten())) - - # placeholders for the best l2 and instance attack found so far - o_bestl2 = [1e10] * self.batch_size - o_bestattack = np.copy(oimgs) - - for outer_step in range(self.binary_search_steps): - _logger.debug(" Binary search step %s of %s", - outer_step, self.binary_search_steps) - - # The last iteration (if we run many steps) repeat the search once. - if self.repeat and outer_step == self.binary_search_steps - 1: - CONST = upper_bound - - # optimization function - adv_x, _, __ = fmin_l_bfgs_b( - lbfgs_objective, - oimgs.flatten().astype(float), - args=(self, targets, oimgs, CONST), - bounds=clip_bound, - maxiter=self.max_iterations, - iprint=0) - - adv_x = adv_x.reshape(oimgs.shape) - assert np.amax(adv_x) <= self.clip_max and \ - np.amin(adv_x) >= self.clip_min, \ - 'fmin_l_bfgs_b returns are invalid' - - # adjust the best result (i.e., the adversarial example with the - # smallest perturbation in terms of L_2 norm) found so far - preds = np.atleast_1d( - utils_tf.model_argmax(self.sess, self.x, self.logits, - adv_x)) - _logger.debug("predicted labels are %s", preds) - - l2s = np.zeros(self.batch_size) - for i in range(self.batch_size): - l2s[i] = np.sum(np.square(adv_x[i] - oimgs[i])) - - for e, (l2, pred, ii) in enumerate(zip(l2s, preds, adv_x)): - if l2 < o_bestl2[e] and attack_success(pred, np.argmax(targets[e]), - self.targeted_attack): - o_bestl2[e] = l2 - o_bestattack[e] = ii - - # adjust the constant as needed - for e in range(self.batch_size): - if attack_success(preds[e], np.argmax(targets[e]), - self.targeted_attack): - # success, divide const by two - upper_bound[e] = min(upper_bound[e], CONST[e]) - if upper_bound[e] < 1e9: - CONST[e] = (lower_bound[e] + upper_bound[e]) / 2 - else: - # failure, either multiply by 10 if no solution found yet - # or do binary search with the known upper bound - lower_bound[e] = max(lower_bound[e], CONST[e]) - if upper_bound[e] < 1e9: - CONST[e] = (lower_bound[e] + upper_bound[e]) / 2 - else: - CONST[e] *= 10 - - _logger.debug(" Successfully generated adversarial examples " - "on %s of %s instances.", - sum(upper_bound < 1e9), self.batch_size) - o_bestl2 = np.array(o_bestl2) - mean = np.mean(np.sqrt(o_bestl2[o_bestl2 < 1e9])) - _logger.debug(" Mean successful distortion: {:.4g}".format(mean)) - - # return the best solution found - o_bestl2 = np.array(o_bestl2) - return o_bestattack diff --git a/cleverhans/attacks/madry_et_al.py b/cleverhans/attacks/madry_et_al.py deleted file mode 100644 index 2ece2239b..000000000 --- a/cleverhans/attacks/madry_et_al.py +++ /dev/null @@ -1,16 +0,0 @@ -""" -The MadryEtAl attack -""" - -from cleverhans.attacks.projected_gradient_descent import ProjectedGradientDescent - - -class MadryEtAl(ProjectedGradientDescent): - """ - The attack from Madry et al 2017 - """ - def __init__(self, model, sess=None, dtypestr='float32', **kwargs): - super(MadryEtAl, self).__init__(model, sess=sess, - dtypestr=dtypestr, - default_rand_init=True, - **kwargs) diff --git a/cleverhans/attacks/max_confidence.py b/cleverhans/attacks/max_confidence.py deleted file mode 100644 index 46b23d252..000000000 --- a/cleverhans/attacks/max_confidence.py +++ /dev/null @@ -1,121 +0,0 @@ -"""The MaxConfidence attack. -""" -import warnings - -import tensorflow as tf - -from cleverhans.attacks.attack import Attack -from cleverhans.attacks.projected_gradient_descent import ProjectedGradientDescent -from cleverhans.model import Model - - -class MaxConfidence(Attack): - """ - The MaxConfidence attack. - - An attack designed for use against models that use confidence thresholding - as a defense. - If the underlying optimizer is optimal, this attack procedure gives the - optimal failure rate for every confidence threshold t > 0.5. - - Publication: https://openreview.net/forum?id=H1g0piA9tQ - - :param model: cleverhans.model.Model - :param sess: optional tf.session.Session - :param base_attacker: cleverhans.attacks.Attack - """ - - def __init__(self, model, sess=None, base_attacker=None): - if not isinstance(model, Model): - raise TypeError("Model must be cleverhans.model.Model, got " + - str(type(model))) - - super(MaxConfidence, self).__init__(model, sess) - if base_attacker is None: - self.base_attacker = ProjectedGradientDescent(model, sess=sess) - else: - self.base_attacker = base_attacker - self.structural_kwargs = self.base_attacker.structural_kwargs - self.feedable_kwargs = self.base_attacker.feedable_kwargs - - def generate(self, x, **kwargs): - """ - Generate symbolic graph for adversarial examples and return. - - :param x: The model's symbolic inputs. - :param kwargs: Keyword arguments for the base attacker - """ - - assert self.parse_params(**kwargs) - labels, _nb_classes = self.get_or_guess_labels(x, kwargs) - adv_x = self.attack(x, labels) - - return adv_x - - def parse_params(self, y=None, nb_classes=10, **kwargs): - self.y = y - self.nb_classes = nb_classes - self.params = kwargs - if len(kwargs.keys()) > 0: - warnings.warn("kwargs is unused and will be removed on or after " - "2019-04-26.") - return True - - def attack(self, x, true_y): - """ - Runs the untargeted attack. - :param x: The input - :param true_y: The correct label for `x`. This attack aims to produce misclassification. - """ - adv_x_cls = [] - prob_cls = [] - m = tf.shape(x)[0] - true_y_idx = tf.argmax(true_y, axis=1) - - expanded_x = tf.concat([x] * self.nb_classes, axis=0) - target_ys = [tf.to_float(tf.one_hot(tf.ones(m, dtype=tf.int32) * cls, - self.nb_classes)) - for cls in range(self.nb_classes)] - target_y = tf.concat(target_ys, axis=0) - adv_x_cls = self.attack_class(expanded_x, target_y) - expanded_all_probs = self.model.get_probs(adv_x_cls) - - adv_x_list = tf.split(adv_x_cls, self.nb_classes) - all_probs_list = tf.split(expanded_all_probs, self.nb_classes) - - for cls in range(self.nb_classes): - target_y = target_ys[cls] - all_probs = all_probs_list[cls] - # We don't actually care whether we hit the target class. - # We care about the probability of the most likely wrong class - cur_prob_cls = tf.reduce_max(all_probs - true_y, axis=1) - # Knock out examples that are correctly classified. - # This is not needed to be optimal for t >= 0.5, but may as well do it - # to get better failure rate at lower thresholds. - chosen_cls = tf.argmax(all_probs, axis=1) - eligible = tf.to_float(tf.not_equal(true_y_idx, chosen_cls)) - cur_prob_cls = cur_prob_cls * eligible - prob_cls.append(cur_prob_cls) - - probs = tf.concat([tf.expand_dims(e, 1) for e in prob_cls], axis=1) - # Don't need to censor here because we knocked out the true class above - # probs = probs - true_y - most_confident = tf.argmax(probs, axis=1) - fused_mask = tf.one_hot(most_confident, self.nb_classes) - masks = tf.split(fused_mask, num_or_size_splits=self.nb_classes, axis=1) - shape = [m] + [1] * (len(x.get_shape()) - 1) - reshaped_masks = [tf.reshape(mask, shape) for mask in masks] - out = sum(adv_x * rmask for adv_x, - rmask in zip(adv_x_list, reshaped_masks)) - return out - - def attack_class(self, x, target_y): - """ - Run the attack on a specific target class. - :param x: tf Tensor. The input example. - :param target_y: tf Tensor. The attacker's desired target class. - Returns: - A targeted adversarial example, intended to be classified as the target class. - """ - adv = self.base_attacker.generate(x, y_target=target_y, **self.params) - return adv diff --git a/cleverhans/attacks/momentum_iterative_method.py b/cleverhans/attacks/momentum_iterative_method.py deleted file mode 100644 index 7e1b2a356..000000000 --- a/cleverhans/attacks/momentum_iterative_method.py +++ /dev/null @@ -1,180 +0,0 @@ -"""The MomentumIterativeMethod attack. -""" - -import warnings - -import numpy as np -import tensorflow as tf - -from cleverhans.attacks.attack import Attack -from cleverhans.attacks.fast_gradient_method import optimize_linear -from cleverhans.compat import reduce_sum, reduce_mean, softmax_cross_entropy_with_logits -from cleverhans import utils_tf - - -class MomentumIterativeMethod(Attack): - """ - The Momentum Iterative Method (Dong et al. 2017). This method won - the first places in NIPS 2017 Non-targeted Adversarial Attacks and - Targeted Adversarial Attacks. The original paper used hard labels - for this attack; no label smoothing. - Paper link: https://arxiv.org/pdf/1710.06081.pdf - - :param model: cleverhans.model.Model - :param sess: optional tf.Session - :param dtypestr: dtype of the data - :param kwargs: passed through to super constructor - """ - - def __init__(self, model, sess=None, dtypestr='float32', **kwargs): - """ - Create a MomentumIterativeMethod instance. - Note: the model parameter should be an instance of the - cleverhans.model.Model abstraction provided by CleverHans. - """ - - super(MomentumIterativeMethod, self).__init__(model, sess, dtypestr, - **kwargs) - self.feedable_kwargs = ('eps', 'eps_iter', 'y', 'y_target', 'clip_min', - 'clip_max') - self.structural_kwargs = [ - 'ord', 'nb_iter', 'decay_factor', 'sanity_checks', 'clip_grad'] - - def generate(self, x, **kwargs): - """ - Generate symbolic graph for adversarial examples and return. - - :param x: The model's symbolic inputs. - :param kwargs: Keyword arguments. See `parse_params` for documentation. - """ - # Parse and save attack-specific parameters - assert self.parse_params(**kwargs) - - asserts = [] - - # If a data range was specified, check that the input was in that range - if self.clip_min is not None: - asserts.append(utils_tf.assert_greater_equal(x, - tf.cast(self.clip_min, - x.dtype))) - - if self.clip_max is not None: - asserts.append(utils_tf.assert_less_equal(x, - tf.cast(self.clip_max, - x.dtype))) - - # Initialize loop variables - momentum = tf.zeros_like(x) - adv_x = x - - # Fix labels to the first model predictions for loss computation - y, _nb_classes = self.get_or_guess_labels(x, kwargs) - y = y / reduce_sum(y, 1, keepdims=True) - targeted = (self.y_target is not None) - - def cond(i, _, __): - """Iterate until number of iterations completed""" - return tf.less(i, self.nb_iter) - - def body(i, ax, m): - """Do a momentum step""" - logits = self.model.get_logits(ax) - loss = softmax_cross_entropy_with_logits(labels=y, logits=logits) - if targeted: - loss = -loss - - # Define gradient of loss wrt input - grad, = tf.gradients(loss, ax) - - # Normalize current gradient and add it to the accumulated gradient - red_ind = list(range(1, len(grad.get_shape()))) - avoid_zero_div = tf.cast(1e-12, grad.dtype) - grad = grad / tf.maximum( - avoid_zero_div, - reduce_mean(tf.abs(grad), red_ind, keepdims=True)) - m = self.decay_factor * m + grad - - optimal_perturbation = optimize_linear(m, self.eps_iter, self.ord) - if self.ord == 1: - raise NotImplementedError("This attack hasn't been tested for ord=1." - "It's not clear that FGM makes a good inner " - "loop step for iterative optimization since " - "it updates just one coordinate at a time.") - - # Update and clip adversarial example in current iteration - ax = ax + optimal_perturbation - ax = x + utils_tf.clip_eta(ax - x, self.ord, self.eps) - - if self.clip_min is not None and self.clip_max is not None: - ax = utils_tf.clip_by_value(ax, self.clip_min, self.clip_max) - - ax = tf.stop_gradient(ax) - - return i + 1, ax, m - - _, adv_x, _ = tf.while_loop( - cond, body, (tf.zeros([]), adv_x, momentum), back_prop=True, - maximum_iterations=self.nb_iter) - - if self.sanity_checks: - with tf.control_dependencies(asserts): - adv_x = tf.identity(adv_x) - - return adv_x - - def parse_params(self, - eps=0.3, - eps_iter=0.06, - nb_iter=10, - y=None, - ord=np.inf, - decay_factor=1.0, - clip_min=None, - clip_max=None, - y_target=None, - sanity_checks=True, - **kwargs): - """ - Take in a dictionary of parameters and applies attack-specific checks - before saving them as attributes. - - Attack-specific parameters: - - :param eps: (optional float) maximum distortion of adversarial example - compared to original input - :param eps_iter: (optional float) step size for each attack iteration - :param nb_iter: (optional int) Number of attack iterations. - :param y: (optional) A tensor with the true labels. - :param y_target: (optional) A tensor with the labels to target. Leave - y_target=None if y is also set. Labels should be - one-hot-encoded. - :param ord: (optional) Order of the norm (mimics Numpy). - Possible values: np.inf, 1 or 2. - :param decay_factor: (optional) Decay factor for the momentum term. - :param clip_min: (optional float) Minimum input component value - :param clip_max: (optional float) Maximum input component value - """ - - # Save attack-specific parameters - self.eps = eps - self.eps_iter = eps_iter - self.nb_iter = nb_iter - self.y = y - self.y_target = y_target - self.ord = ord - self.decay_factor = decay_factor - self.clip_min = clip_min - self.clip_max = clip_max - self.sanity_checks = sanity_checks - - if self.y is not None and self.y_target is not None: - raise ValueError("Must not set both y and y_target") - # Check if order of the norm is acceptable given current implementation - if self.ord not in [np.inf, 1, 2]: - raise ValueError("Norm order must be either np.inf, 1, or 2.") - - if len(kwargs.keys()) > 0: - warnings.warn("kwargs is unused and will be removed on or after " - "2019-04-26.") - - return True diff --git a/cleverhans/attacks/noise.py b/cleverhans/attacks/noise.py deleted file mode 100644 index 113bf4cec..000000000 --- a/cleverhans/attacks/noise.py +++ /dev/null @@ -1,92 +0,0 @@ -"""The Noise attack - -""" -import warnings - -import numpy as np -import tensorflow as tf - -from cleverhans.attacks.attack import Attack - - -class Noise(Attack): - """ - A weak attack that just picks a random point in the attacker's action space. - When combined with an attack bundling function, this can be used to implement - random search. - - References: - https://arxiv.org/abs/1802.00420 recommends random search to help identify - gradient masking. - https://openreview.net/forum?id=H1g0piA9tQ recommends using noise as part - of an attack bundling recipe combining many different optimizers to yield - a stronger optimizer. - - :param model: cleverhans.model.Model - :param sess: optional tf.Session - :param dtypestr: dtype of the data - :param kwargs: passed through to super constructor - """ - - def __init__(self, model, sess=None, dtypestr='float32', - **kwargs): - - super(Noise, self).__init__(model, sess=sess, dtypestr=dtypestr, **kwargs) - self.feedable_kwargs = ('eps', 'clip_min', 'clip_max') - self.structural_kwargs = ['ord'] - - def generate(self, x, **kwargs): - """ - Generate symbolic graph for adversarial examples and return. - - :param x: The model's symbolic inputs. - :param kwargs: See `parse_params` - """ - # Parse and save attack-specific parameters - assert self.parse_params(**kwargs) - - if self.ord != np.inf: - raise NotImplementedError(self.ord) - eta = tf.random_uniform(tf.shape(x), -self.eps, self.eps, - dtype=self.tf_dtype) - adv_x = x + eta - if self.clip_min is not None or self.clip_max is not None: - assert self.clip_min is not None and self.clip_max is not None - adv_x = tf.clip_by_value(adv_x, self.clip_min, self.clip_max) - - return adv_x - - def parse_params(self, - eps=0.3, - ord=np.inf, - clip_min=None, - clip_max=None, - **kwargs): - """ - Take in a dictionary of parameters and applies attack-specific checks - before saving them as attributes. - - Attack-specific parameters: - - :param eps: (optional float) maximum distortion of adversarial example - compared to original input - :param ord: (optional) Order of the norm (mimics Numpy). - Possible values: np.inf - :param clip_min: (optional float) Minimum input component value - :param clip_max: (optional float) Maximum input component value - """ - - # Save attack-specific parameters - self.eps = eps - self.ord = ord - self.clip_min = clip_min - self.clip_max = clip_max - - # Check if order of the norm is acceptable given current implementation - if self.ord not in [np.inf]: - raise ValueError("Norm order must be np.inf") - if len(kwargs.keys()) > 0: - warnings.warn("kwargs is unused and will be removed on or after " - "2019-04-26.") - - return True diff --git a/cleverhans/attacks/projected_gradient_descent.py b/cleverhans/attacks/projected_gradient_descent.py deleted file mode 100644 index a5dda0c18..000000000 --- a/cleverhans/attacks/projected_gradient_descent.py +++ /dev/null @@ -1,259 +0,0 @@ -""" -The ProjectedGradientDescent attack. -""" - -import warnings - -import numpy as np -import tensorflow as tf - -from cleverhans.attacks.attack import Attack -from cleverhans.attacks.fast_gradient_method import FastGradientMethod -from cleverhans import utils_tf -from cleverhans.compat import softmax_cross_entropy_with_logits -from cleverhans.utils_tf import clip_eta, random_lp_vector - - -class ProjectedGradientDescent(Attack): - """ - This class implements either the Basic Iterative Method - (Kurakin et al. 2016) when rand_init is set to 0. or the - Madry et al. (2017) method when rand_minmax is larger than 0. - Paper link (Kurakin et al. 2016): https://arxiv.org/pdf/1607.02533.pdf - Paper link (Madry et al. 2017): https://arxiv.org/pdf/1706.06083.pdf - - :param model: cleverhans.model.Model - :param sess: optional tf.Session - :param dtypestr: dtype of the data - :param default_rand_init: whether to use random initialization by default - :param kwargs: passed through to super constructor - """ - - FGM_CLASS = FastGradientMethod - - def __init__(self, model, sess=None, dtypestr='float32', - default_rand_init=True, **kwargs): - """ - Create a ProjectedGradientDescent instance. - Note: the model parameter should be an instance of the - cleverhans.model.Model abstraction provided by CleverHans. - """ - - super(ProjectedGradientDescent, self).__init__(model, sess=sess, - dtypestr=dtypestr, **kwargs) - self.feedable_kwargs = ('eps', 'eps_iter', 'y', 'y_target', 'clip_min', - 'clip_max') - self.structural_kwargs = ['ord', 'nb_iter', 'rand_init', 'clip_grad', - 'sanity_checks', 'loss_fn'] - self.default_rand_init = default_rand_init - - def generate(self, x, **kwargs): - """ - Generate symbolic graph for adversarial examples and return. - - :param x: The model's symbolic inputs. - :param kwargs: See `parse_params` - """ - # Parse and save attack-specific parameters - assert self.parse_params(**kwargs) - - asserts = [] - - # If a data range was specified, check that the input was in that range - if self.clip_min is not None: - asserts.append(utils_tf.assert_greater_equal(x, - tf.cast(self.clip_min, - x.dtype))) - - if self.clip_max is not None: - asserts.append(utils_tf.assert_less_equal(x, - tf.cast(self.clip_max, - x.dtype))) - - # Initialize loop variables - if self.rand_init: - eta = random_lp_vector(tf.shape(x), self.ord, - tf.cast(self.rand_init_eps, x.dtype), - dtype=x.dtype) - else: - eta = tf.zeros(tf.shape(x)) - - # Clip eta - eta = clip_eta(eta, self.ord, self.eps) - adv_x = x + eta - if self.clip_min is not None or self.clip_max is not None: - adv_x = utils_tf.clip_by_value(adv_x, self.clip_min, self.clip_max) - - if self.y_target is not None: - y = self.y_target - targeted = True - elif self.y is not None: - y = self.y - targeted = False - else: - model_preds = self.model.get_probs(x) - preds_max = tf.reduce_max(model_preds, 1, keepdims=True) - y = tf.to_float(tf.equal(model_preds, preds_max)) - y = tf.stop_gradient(y) - targeted = False - del model_preds - - y_kwarg = 'y_target' if targeted else 'y' - - fgm_params = { - 'eps': self.eps_iter, - y_kwarg: y, - 'ord': self.ord, - 'loss_fn': self.loss_fn, - 'clip_min': self.clip_min, - 'clip_max': self.clip_max, - 'clip_grad': self.clip_grad - } - if self.ord == 1: - raise NotImplementedError("FGM is not a good inner loop step for PGD " - " when ord=1, because ord=1 FGM changes only " - " one pixel at a time. Use the SparseL1Descent " - " attack instead, which allows fine-grained " - " control over the sparsity of the gradient " - " updates.") - - # Use getattr() to avoid errors in eager execution attacks - FGM = self.FGM_CLASS( - self.model, - sess=getattr(self, 'sess', None), - dtypestr=self.dtypestr) - - def cond(i, _): - """Iterate until requested number of iterations is completed""" - return tf.less(i, self.nb_iter) - - def body(i, adv_x): - """Do a projected gradient step""" - adv_x = FGM.generate(adv_x, **fgm_params) - - # Clipping perturbation eta to self.ord norm ball - eta = adv_x - x - eta = clip_eta(eta, self.ord, self.eps) - adv_x = x + eta - - # Redo the clipping. - # FGM already did it, but subtracting and re-adding eta can add some - # small numerical error. - if self.clip_min is not None or self.clip_max is not None: - adv_x = utils_tf.clip_by_value(adv_x, self.clip_min, self.clip_max) - - return i + 1, adv_x - - _, adv_x = tf.while_loop(cond, body, (tf.zeros([]), adv_x), back_prop=True, - maximum_iterations=self.nb_iter) - - # Asserts run only on CPU. - # When multi-GPU eval code tries to force all PGD ops onto GPU, this - # can cause an error. - common_dtype = tf.float32 - asserts.append(utils_tf.assert_less_equal(tf.cast(self.eps_iter, - dtype=common_dtype), - tf.cast(self.eps, dtype=common_dtype))) - if self.ord == np.inf and self.clip_min is not None: - # The 1e-6 is needed to compensate for numerical error. - # Without the 1e-6 this fails when e.g. eps=.2, clip_min=.5, - # clip_max=.7 - asserts.append(utils_tf.assert_less_equal(tf.cast(self.eps, x.dtype), - 1e-6 + tf.cast(self.clip_max, - x.dtype) - - tf.cast(self.clip_min, - x.dtype))) - - if self.sanity_checks: - with tf.control_dependencies(asserts): - adv_x = tf.identity(adv_x) - - return adv_x - - def parse_params(self, - eps=0.3, - eps_iter=0.05, - nb_iter=10, - y=None, - ord=np.inf, - loss_fn=softmax_cross_entropy_with_logits, - clip_min=None, - clip_max=None, - y_target=None, - rand_init=None, - rand_init_eps=None, - clip_grad=False, - sanity_checks=True, - **kwargs): - """ - Take in a dictionary of parameters and applies attack-specific checks - before saving them as attributes. - - Attack-specific parameters: - - :param eps: (optional float) maximum distortion of adversarial example - compared to original input - :param eps_iter: (optional float) step size for each attack iteration - :param nb_iter: (optional int) Number of attack iterations. - :param y: (optional) A tensor with the true labels. - :param y_target: (optional) A tensor with the labels to target. Leave - y_target=None if y is also set. Labels should be - one-hot-encoded. - :param ord: (optional) Order of the norm (mimics Numpy). - Possible values: np.inf, 1 or 2. - :param loss_fn: Loss function that takes (labels, logits) as arguments and returns loss - :param clip_min: (optional float) Minimum input component value - :param clip_max: (optional float) Maximum input component value - :param rand_init: (optional) Start the gradient descent from a point chosen - uniformly at random in the norm ball of radius - rand_init_eps - :param rand_init_eps: (optional float) size of the norm ball from which - the initial starting point is chosen. Defaults to eps - :param clip_grad: (optional bool) Ignore gradient components at positions - where the input is already at the boundary of the domain, - and the update step will get clipped out. - :param sanity_checks: bool Insert tf asserts checking values - (Some tests need to run with no sanity checks because the - tests intentionally configure the attack strangely) - """ - - # Save attack-specific parameters - self.eps = eps - if rand_init is None: - rand_init = self.default_rand_init - self.rand_init = rand_init - if rand_init_eps is None: - rand_init_eps = self.eps - self.rand_init_eps = rand_init_eps - - self.eps_iter = eps_iter - self.nb_iter = nb_iter - self.y = y - self.y_target = y_target - self.ord = ord - self.loss_fn = loss_fn - self.clip_min = clip_min - self.clip_max = clip_max - self.clip_grad = clip_grad - - if isinstance(eps, float) and isinstance(eps_iter, float): - # If these are both known at compile time, we can check before anything - # is run. If they are tf, we can't check them yet. - assert eps_iter <= eps, (eps_iter, eps) - - if self.y is not None and self.y_target is not None: - raise ValueError("Must not set both y and y_target") - # Check if order of the norm is acceptable given current implementation - if self.ord not in [np.inf, 1, 2]: - raise ValueError("Norm order must be either np.inf, 1, or 2.") - - if self.clip_grad and (self.clip_min is None or self.clip_max is None): - raise ValueError("Must set clip_min and clip_max if clip_grad is set") - - self.sanity_checks = sanity_checks - - if len(kwargs.keys()) > 0: - warnings.warn("kwargs is unused and will be removed on or after " - "2019-04-26.") - - return True diff --git a/cleverhans/attacks/saliency_map_method.py b/cleverhans/attacks/saliency_map_method.py deleted file mode 100644 index 69a943b13..000000000 --- a/cleverhans/attacks/saliency_map_method.py +++ /dev/null @@ -1,281 +0,0 @@ -"""The SalienceMapMethod attack -""" -# pylint: disable=missing-docstring -import warnings - -import numpy as np -from six.moves import xrange -import tensorflow as tf - -from cleverhans.attacks.attack import Attack -from cleverhans.compat import reduce_sum, reduce_max, reduce_any - -tf_dtype = tf.as_dtype('float32') - - -class SaliencyMapMethod(Attack): - """ - The Jacobian-based Saliency Map Method (Papernot et al. 2016). - Paper link: https://arxiv.org/pdf/1511.07528.pdf - - :param model: cleverhans.model.Model - :param sess: optional tf.Session - :param dtypestr: dtype of the data - :param kwargs: passed through to super constructor - - :note: When not using symbolic implementation in `generate`, `sess` should - be provided - """ - - def __init__(self, model, sess=None, dtypestr='float32', **kwargs): - """ - Create a SaliencyMapMethod instance. - Note: the model parameter should be an instance of the - cleverhans.model.Model abstraction provided by CleverHans. - """ - - super(SaliencyMapMethod, self).__init__(model, sess, dtypestr, **kwargs) - - self.feedable_kwargs = ('y_target',) - self.structural_kwargs = [ - 'theta', 'gamma', 'clip_max', 'clip_min', 'symbolic_impl' - ] - - def generate(self, x, **kwargs): - """ - Generate symbolic graph for adversarial examples and return. - - :param x: The model's symbolic inputs. - :param kwargs: See `parse_params` - """ - # Parse and save attack-specific parameters - assert self.parse_params(**kwargs) - - if self.symbolic_impl: - # Create random targets if y_target not provided - if self.y_target is None: - from random import randint - - def random_targets(gt): - result = gt.copy() - nb_s = gt.shape[0] - nb_classes = gt.shape[1] - - for i in range(nb_s): - result[i, :] = np.roll(result[i, :], - randint(1, nb_classes - 1)) - - return result - - labels, nb_classes = self.get_or_guess_labels(x, kwargs) - self.y_target = tf.py_func(random_targets, [labels], - self.tf_dtype) - self.y_target.set_shape([None, nb_classes]) - - x_adv = jsma_symbolic( - x, - model=self.model, - y_target=self.y_target, - theta=self.theta, - gamma=self.gamma, - clip_min=self.clip_min, - clip_max=self.clip_max) - else: - raise NotImplementedError("The jsma_batch function has been removed." - " The symbolic_impl argument to SaliencyMapMethod will be removed" - " on 2019-07-18 or after. Any code that depends on the non-symbolic" - " implementation of the JSMA should be revised. Consider using" - " SaliencyMapMethod.generate_np() instead.") - - return x_adv - - def parse_params(self, - theta=1., - gamma=1., - clip_min=0., - clip_max=1., - y_target=None, - symbolic_impl=True, - **kwargs): - """ - Take in a dictionary of parameters and applies attack-specific checks - before saving them as attributes. - - Attack-specific parameters: - - :param theta: (optional float) Perturbation introduced to modified - components (can be positive or negative) - :param gamma: (optional float) Maximum percentage of perturbed features - :param clip_min: (optional float) Minimum component value for clipping - :param clip_max: (optional float) Maximum component value for clipping - :param y_target: (optional) Target tensor if the attack is targeted - """ - self.theta = theta - self.gamma = gamma - self.clip_min = clip_min - self.clip_max = clip_max - self.y_target = y_target - self.symbolic_impl = symbolic_impl - - if len(kwargs.keys()) > 0: - warnings.warn("kwargs is unused and will be removed on or after " - "2019-04-26.") - - return True - - -def jsma_batch(*args, **kwargs): - raise NotImplementedError( - "The jsma_batch function has been removed. Any code that depends on it should be revised.") - - -def jsma_symbolic(x, y_target, model, theta, gamma, clip_min, clip_max): - """ - TensorFlow implementation of the JSMA (see https://arxiv.org/abs/1511.07528 - for details about the algorithm design choices). - - :param x: the input placeholder - :param y_target: the target tensor - :param model: a cleverhans.model.Model object. - :param theta: delta for each feature adjustment - :param gamma: a float between 0 - 1 indicating the maximum distortion - percentage - :param clip_min: minimum value for components of the example returned - :param clip_max: maximum value for components of the example returned - :return: a tensor for the adversarial example - """ - - nb_classes = int(y_target.shape[-1].value) - nb_features = int(np.product(x.shape[1:]).value) - - if x.dtype == tf.float32 and y_target.dtype == tf.int64: - y_target = tf.cast(y_target, tf.int32) - - if x.dtype == tf.float32 and y_target.dtype == tf.float64: - warnings.warn("Downcasting labels---this should be harmless unless" - " they are smoothed") - y_target = tf.cast(y_target, tf.float32) - - max_iters = np.floor(nb_features * gamma / 2) - increase = bool(theta > 0) - - tmp = np.ones((nb_features, nb_features), int) - np.fill_diagonal(tmp, 0) - zero_diagonal = tf.constant(tmp, tf_dtype) - - # Compute our initial search domain. We optimize the initial search domain - # by removing all features that are already at their maximum values (if - # increasing input features---otherwise, at their minimum value). - if increase: - search_domain = tf.reshape( - tf.cast(x < clip_max, tf_dtype), [-1, nb_features]) - else: - search_domain = tf.reshape( - tf.cast(x > clip_min, tf_dtype), [-1, nb_features]) - - # Loop variables - # x_in: the tensor that holds the latest adversarial outputs that are in - # progress. - # y_in: the tensor for target labels - # domain_in: the tensor that holds the latest search domain - # cond_in: the boolean tensor to show if more iteration is needed for - # generating adversarial samples - def condition(x_in, y_in, domain_in, i_in, cond_in): - # Repeat the loop until we have achieved misclassification or - # reaches the maximum iterations - return tf.logical_and(tf.less(i_in, max_iters), cond_in) - - # Same loop variables as above - def body(x_in, y_in, domain_in, i_in, cond_in): - # Create graph for model logits and predictions - logits = model.get_logits(x_in) - preds = tf.nn.softmax(logits) - preds_onehot = tf.one_hot(tf.argmax(preds, axis=1), depth=nb_classes) - - # create the Jacobian graph - list_derivatives = [] - for class_ind in xrange(nb_classes): - derivatives = tf.gradients(logits[:, class_ind], x_in) - list_derivatives.append(derivatives[0]) - grads = tf.reshape( - tf.stack(list_derivatives), shape=[nb_classes, -1, nb_features]) - - # Compute the Jacobian components - # To help with the computation later, reshape the target_class - # and other_class to [nb_classes, -1, 1]. - # The last dimention is added to allow broadcasting later. - target_class = tf.reshape( - tf.transpose(y_in, perm=[1, 0]), shape=[nb_classes, -1, 1]) - other_classes = tf.cast(tf.not_equal(target_class, 1), tf_dtype) - - grads_target = reduce_sum(grads * target_class, axis=0) - grads_other = reduce_sum(grads * other_classes, axis=0) - - # Remove the already-used input features from the search space - # Subtract 2 times the maximum value from those value so that - # they won't be picked later - increase_coef = (4 * int(increase) - 2) \ - * tf.cast(tf.equal(domain_in, 0), tf_dtype) - - target_tmp = grads_target - target_tmp -= increase_coef \ - * reduce_max(tf.abs(grads_target), axis=1, keepdims=True) - target_sum = tf.reshape(target_tmp, shape=[-1, nb_features, 1]) \ - + tf.reshape(target_tmp, shape=[-1, 1, nb_features]) - - other_tmp = grads_other - other_tmp += increase_coef \ - * reduce_max(tf.abs(grads_other), axis=1, keepdims=True) - other_sum = tf.reshape(other_tmp, shape=[-1, nb_features, 1]) \ - + tf.reshape(other_tmp, shape=[-1, 1, nb_features]) - - # Create a mask to only keep features that match conditions - if increase: - scores_mask = ((target_sum > 0) & (other_sum < 0)) - else: - scores_mask = ((target_sum < 0) & (other_sum > 0)) - - # Create a 2D numpy array of scores for each pair of candidate features - scores = tf.cast(scores_mask, tf_dtype) \ - * (-target_sum * other_sum) * zero_diagonal - - # Extract the best two pixels - best = tf.argmax( - tf.reshape(scores, shape=[-1, nb_features * nb_features]), axis=1) - - p1 = tf.mod(best, nb_features) - p2 = tf.floordiv(best, nb_features) - p1_one_hot = tf.one_hot(p1, depth=nb_features) - p2_one_hot = tf.one_hot(p2, depth=nb_features) - - # Check if more modification is needed for each sample - mod_not_done = tf.equal(reduce_sum(y_in * preds_onehot, axis=1), 0) - cond = mod_not_done & (reduce_sum(domain_in, axis=1) >= 2) - - # Update the search domain - cond_float = tf.reshape(tf.cast(cond, tf_dtype), shape=[-1, 1]) - to_mod = (p1_one_hot + p2_one_hot) * cond_float - - domain_out = domain_in - to_mod - - # Apply the modification to the images - to_mod_reshape = tf.reshape( - to_mod, shape=([-1] + x_in.shape[1:].as_list())) - if increase: - x_out = tf.minimum(clip_max, x_in + to_mod_reshape * theta) - else: - x_out = tf.maximum(clip_min, x_in - to_mod_reshape * theta) - - # Increase the iterator, and check if all misclassifications are done - i_out = tf.add(i_in, 1) - cond_out = reduce_any(cond) - - return x_out, y_in, domain_out, i_out, cond_out - - # Run loop to do JSMA - x_adv, _, _, _, _ = tf.while_loop( - condition, - body, [x, y_target, search_domain, 0, True], - parallel_iterations=1) - - return x_adv diff --git a/cleverhans/attacks/semantic.py b/cleverhans/attacks/semantic.py deleted file mode 100644 index 27f8f5bb2..000000000 --- a/cleverhans/attacks/semantic.py +++ /dev/null @@ -1,39 +0,0 @@ -"""Semantic adversarial examples -""" - -from cleverhans.attacks.attack import Attack - - -class Semantic(Attack): - """ - Semantic adversarial examples - - https://arxiv.org/abs/1703.06857 - - Note: data must either be centered (so that the negative image can be - made by simple negation) or must be in the interval [-1, 1] - - :param model: cleverhans.model.Model - :param center: bool - If True, assumes data has 0 mean so the negative image is just negation. - If False, assumes data is in the interval [0, max_val] - :param max_val: float - Maximum value allowed in the input data - :param sess: optional tf.Session - :param dtypestr: dtype of data - :param kwargs: passed through to the super constructor - """ - - def __init__(self, model, center, max_val=1., sess=None, dtypestr='float32', - **kwargs): - super(Semantic, self).__init__(model, sess, dtypestr, **kwargs) - self.center = center - self.max_val = max_val - if hasattr(model, 'dataset_factory'): - if 'center' in model.dataset_factory.kwargs: - assert center == model.dataset_factory.kwargs['center'] - - def generate(self, x, **kwargs): - if self.center: - return -x - return self.max_val - x diff --git a/cleverhans/attacks/sparse_l1_descent.py b/cleverhans/attacks/sparse_l1_descent.py deleted file mode 100644 index 569cfd9cb..000000000 --- a/cleverhans/attacks/sparse_l1_descent.py +++ /dev/null @@ -1,346 +0,0 @@ -""" -The SparseL1Descent attack. -""" - -import warnings -from distutils.version import LooseVersion - -import tensorflow as tf - -from cleverhans.attacks.attack import Attack -from cleverhans import utils_tf -from cleverhans.utils_tf import clip_eta, random_lp_vector -from cleverhans.compat import reduce_max, reduce_sum, \ - softmax_cross_entropy_with_logits - - -class SparseL1Descent(Attack): - """ - This class implements a variant of Projected Gradient Descent for the l1-norm - (Tramer and Boneh 2019). The l1-norm case is more tricky than the l-inf and l2 - cases covered by the ProjectedGradientDescent class, because the steepest - descent direction for the l1-norm is too sparse (it updates a single - coordinate in the adversarial perturbation in each step). This attack has an - additional parameter that controls the sparsity of the update step. For - moderately sparse update steps, the attack vastly outperforms Projected - Steepest Descent and is competitive with other attacks targeted at the l1-norm - such as the ElasticNetMethod attack (which is much more computationally - expensive). - Paper link (Tramer and Boneh 2019): https://arxiv.org/pdf/1904.13000.pdf - - :param model: cleverhans.model.Model - :param sess: optional tf.Session - :param dtypestr: dtype of the data - :param kwargs: passed through to super constructor - """ - - def __init__(self, model, sess=None, dtypestr='float32', **kwargs): - """ - Create a SparseL1Descent instance. - Note: the model parameter should be an instance of the - cleverhans.model.Model abstraction provided by CleverHans. - """ - - super(SparseL1Descent, self).__init__(model, sess=sess, - dtypestr=dtypestr, **kwargs) - self.feedable_kwargs = ('eps', 'eps_iter', 'y', 'y_target', 'clip_min', - 'clip_max', 'grad_sparsity') - self.structural_kwargs = ['nb_iter', 'rand_init', 'clip_grad', - 'sanity_checks'] - - def generate(self, x, **kwargs): - """ - Generate symbolic graph for adversarial examples and return. - - :param x: The model's symbolic inputs. - :param kwargs: See `parse_params` - """ - # Parse and save attack-specific parameters - assert self.parse_params(**kwargs) - - asserts = [] - - # If a data range was specified, check that the input was in that range - if self.clip_min is not None: - asserts.append(utils_tf.assert_greater_equal(x, - tf.cast(self.clip_min, - x.dtype))) - - if self.clip_max is not None: - asserts.append(utils_tf.assert_less_equal(x, - tf.cast(self.clip_max, - x.dtype))) - - # Initialize loop variables - if self.rand_init: - eta = random_lp_vector(tf.shape(x), ord=1, - eps=tf.cast(self.eps, x.dtype), dtype=x.dtype) - else: - eta = tf.zeros(tf.shape(x)) - - # Clip eta - eta = clip_eta(eta, ord=1, eps=self.eps) - adv_x = x + eta - if self.clip_min is not None or self.clip_max is not None: - adv_x = utils_tf.clip_by_value(adv_x, self.clip_min, self.clip_max) - - if self.y_target is not None: - y = self.y_target - targeted = True - elif self.y is not None: - y = self.y - targeted = False - else: - model_preds = self.model.get_probs(x) - preds_max = tf.reduce_max(model_preds, 1, keepdims=True) - y = tf.to_float(tf.equal(model_preds, preds_max)) - y = tf.stop_gradient(y) - targeted = False - del model_preds - - y_kwarg = 'y_target' if targeted else 'y' - - def cond(i, _): - """Iterate until requested number of iterations is completed""" - return tf.less(i, self.nb_iter) - - def body(i, adv_x): - """Do a projected gradient step""" - - labels, _ = self.get_or_guess_labels(adv_x, {y_kwarg: y}) - logits = self.model.get_logits(adv_x) - - adv_x = sparse_l1_descent(adv_x, - logits, - y=labels, - eps=self.eps_iter, - q=self.grad_sparsity, - clip_min=self.clip_min, - clip_max=self.clip_max, - clip_grad=self.clip_grad, - targeted=(self.y_target is not None), - sanity_checks=self.sanity_checks) - - # Clipping perturbation eta to the l1-ball - eta = adv_x - x - eta = clip_eta(eta, ord=1, eps=self.eps) - adv_x = x + eta - - # Redo the clipping. - # Subtracting and re-adding eta can add some small numerical error. - if self.clip_min is not None or self.clip_max is not None: - adv_x = utils_tf.clip_by_value(adv_x, self.clip_min, self.clip_max) - - return i + 1, adv_x - - _, adv_x = tf.while_loop(cond, body, (tf.zeros([]), adv_x), back_prop=True, - maximum_iterations=self.nb_iter) - - # Asserts run only on CPU. - # When multi-GPU eval code tries to force all PGD ops onto GPU, this - # can cause an error. - common_dtype = tf.float32 - asserts.append(utils_tf.assert_less_equal(tf.cast(self.eps_iter, - dtype=common_dtype), - tf.cast(self.eps, dtype=common_dtype))) - - if self.sanity_checks: - with tf.control_dependencies(asserts): - adv_x = tf.identity(adv_x) - - return adv_x - - def parse_params(self, - eps=10.0, - eps_iter=1.0, - nb_iter=20, - y=None, - clip_min=None, - clip_max=None, - y_target=None, - rand_init=False, - clip_grad=False, - grad_sparsity=99, - sanity_checks=True, - **kwargs): - """ - Take in a dictionary of parameters and applies attack-specific checks - before saving them as attributes. - - Attack-specific parameters: - - :param eps: (optional float) maximum distortion of adversarial example - compared to original input - :param eps_iter: (optional float) step size for each attack iteration - :param nb_iter: (optional int) Number of attack iterations. - :param y: (optional) A tensor with the true labels. - :param y_target: (optional) A tensor with the labels to target. Leave - y_target=None if y is also set. Labels should be - one-hot-encoded. - :param clip_min: (optional float) Minimum input component value - :param clip_max: (optional float) Maximum input component value - :param clip_grad: (optional bool) Ignore gradient components - at positions where the input is already at the boundary - of the domain, and the update step will get clipped out. - :param grad_sparsity (optional) Relative sparsity of the gradient update - step, in percent. Only gradient values larger - than this percentile are retained. This parameter can - be a scalar, or a vector of the same length as the - input batch dimension. - :param sanity_checks: bool Insert tf asserts checking values - (Some tests need to run with no sanity checks because the - tests intentionally configure the attack strangely) - """ - - # Save attack-specific parameters - self.eps = eps - self.rand_init = rand_init - self.eps_iter = eps_iter - self.nb_iter = nb_iter - self.y = y - self.y_target = y_target - self.clip_min = clip_min - self.clip_max = clip_max - self.clip_grad = clip_grad - self.grad_sparsity = grad_sparsity - - if isinstance(eps, float) and isinstance(eps_iter, float): - # If these are both known at compile time, we can check before anything - # is run. If they are tf, we can't check them yet. - assert eps_iter <= eps, (eps_iter, eps) - - if self.y is not None and self.y_target is not None: - raise ValueError("Must not set both y and y_target") - - if self.clip_grad and (self.clip_min is None or self.clip_max is None): - raise ValueError("Must set clip_min and clip_max if clip_grad is set") - - # The grad_sparsity argument governs the sparsity of the gradient - # update. It indicates the percentile value above which gradient entries - # are retained. It can be specified as a scalar or as a 1-dimensional - # vector of the same size as the input's batch dimension. - if isinstance(self.grad_sparsity, int) or \ - isinstance(self.grad_sparsity, float): - if not 0 < self.grad_sparsity < 100: - raise ValueError("grad_sparsity should be in (0, 100)") - else: - self.grad_sparsity = tf.convert_to_tensor(self.grad_sparsity) - if len(self.grad_sparsity.shape) > 1: - raise ValueError("grad_sparsity should either be a scalar or a vector") - - - self.sanity_checks = sanity_checks - - if len(kwargs.keys()) > 0: - warnings.warn("kwargs is unused and will be removed on or after " - "2019-04-26.") - - return True - - -def sparse_l1_descent(x, - logits, - y=None, - eps=1.0, - q=99, - clip_min=None, - clip_max=None, - clip_grad=False, - targeted=False, - sanity_checks=True): - """ - TensorFlow implementation of the Dense L1 Descent Method. - :param x: the input placeholder - :param logits: output of model.get_logits - :param y: (optional) A placeholder for the true labels. If targeted - is true, then provide the target label. Otherwise, only provide - this parameter if you'd like to use true labels when crafting - adversarial samples. Otherwise, model predictions are used as - labels to avoid the "label leaking" effect (explained in this - paper: https://arxiv.org/abs/1611.01236). Default is None. - Labels should be one-hot-encoded. - :param eps: the epsilon (input variation parameter) - :param q: the percentile above which gradient values are retained. Either a - scalar or a vector of same length as the input batch dimension. - :param clip_min: Minimum float value for adversarial example components - :param clip_max: Maximum float value for adversarial example components - :param clip_grad: (optional bool) Ignore gradient components - at positions where the input is already at the boundary - of the domain, and the update step will get clipped out. - :param targeted: Is the attack targeted or untargeted? Untargeted, the - default, will try to make the label incorrect. Targeted - will instead try to move in the direction of being more - like y. - :return: a tensor for the adversarial example - """ - - asserts = [] - - # If a data range was specified, check that the input was in that range - if clip_min is not None: - asserts.append(utils_tf.assert_greater_equal( - x, tf.cast(clip_min, x.dtype))) - - if clip_max is not None: - asserts.append(utils_tf.assert_less_equal(x, tf.cast(clip_max, x.dtype))) - - # Make sure the caller has not passed probs by accident - assert logits.op.type != 'Softmax' - - if y is None: - # Using model predictions as ground truth to avoid label leaking - preds_max = reduce_max(logits, 1, keepdims=True) - y = tf.to_float(tf.equal(logits, preds_max)) - y = tf.stop_gradient(y) - y = y / reduce_sum(y, 1, keepdims=True) - - # Compute loss - loss = softmax_cross_entropy_with_logits(labels=y, logits=logits) - if targeted: - loss = -loss - - # Define gradient of loss wrt input - grad, = tf.gradients(loss, x) - - if clip_grad: - grad = utils_tf.zero_out_clipped_grads(grad, x, clip_min, clip_max) - - red_ind = list(range(1, len(grad.get_shape()))) - dim = tf.reduce_prod(tf.shape(x)[1:]) - - abs_grad = tf.reshape(tf.abs(grad), (-1, dim)) - - # if q is a scalar, broadcast it to a vector of same length as the batch dim - q = tf.cast(tf.broadcast_to(q, tf.shape(x)[0:1]), tf.float32) - k = tf.cast(tf.floor(q / 100 * tf.cast(dim, tf.float32)), tf.int32) - - # `tf.sort` is much faster than `tf.contrib.distributions.percentile`. - # For TF <= 1.12, use `tf.nn.top_k` as `tf.sort` is not implemented. - if LooseVersion(tf.__version__) <= LooseVersion('1.12.0'): - # `tf.sort` is only available in TF 1.13 onwards - sorted_grad = -tf.nn.top_k(-abs_grad, k=dim, sorted=True)[0] - else: - sorted_grad = tf.sort(abs_grad, axis=-1) - - idx = tf.stack((tf.range(tf.shape(abs_grad)[0]), k), -1) - percentiles = tf.gather_nd(sorted_grad, idx) - tied_for_max = tf.greater_equal(abs_grad, tf.expand_dims(percentiles, -1)) - tied_for_max = tf.reshape(tf.cast(tied_for_max, x.dtype), tf.shape(grad)) - num_ties = tf.reduce_sum(tied_for_max, red_ind, keepdims=True) - - optimal_perturbation = tf.sign(grad) * tied_for_max / num_ties - - # Add perturbation to original example to obtain adversarial example - adv_x = x + utils_tf.mul(eps, optimal_perturbation) - - # If clipping is needed, reset all values outside of [clip_min, clip_max] - if (clip_min is not None) or (clip_max is not None): - # We don't currently support one-sided clipping - assert clip_min is not None and clip_max is not None - adv_x = utils_tf.clip_by_value(adv_x, clip_min, clip_max) - - if sanity_checks: - with tf.control_dependencies(asserts): - adv_x = tf.identity(adv_x) - - return adv_x diff --git a/cleverhans/attacks/spatial_transformation_method.py b/cleverhans/attacks/spatial_transformation_method.py deleted file mode 100644 index 411f450e2..000000000 --- a/cleverhans/attacks/spatial_transformation_method.py +++ /dev/null @@ -1,106 +0,0 @@ -"""The SpatialTransformationMethod attack -""" -import warnings - -from cleverhans.attacks.attack import Attack - - -class SpatialTransformationMethod(Attack): - """ - Spatial transformation attack - """ - - def __init__(self, model, sess=None, dtypestr='float32', **kwargs): - """ - Create a SpatialTransformationMethod instance. - Note: the model parameter should be an instance of the - cleverhans.model.Model abstraction provided by CleverHans. - - :param model: cleverhans.model.Model - :param sess: optional tf.Session - :param dtypestr: dtype of the data - :param kwargs: passed through to super constructor - """ - - super(SpatialTransformationMethod, self).__init__( - model, sess, dtypestr, **kwargs) - self.feedable_kwargs = ('n_samples', 'dx_min', 'dx_max', 'n_dxs', 'dy_min', - 'dy_max', 'n_dys', 'angle_min', 'angle_max', - 'n_angles', 'black_border_size') - - def generate(self, x, **kwargs): - """ - Generate symbolic graph for adversarial examples and return. - :param x: The model's symbolic inputs. - :param kwargs: See `parse_params` - """ - # Parse and save attack-specific parameters - assert self.parse_params(**kwargs) - - from cleverhans.attacks_tf import spm - - labels, _ = self.get_or_guess_labels(x, kwargs) - - return spm( - x, - self.model, - y=labels, - n_samples=self.n_samples, - dx_min=self.dx_min, dx_max=self.dx_max, n_dxs=self.n_dxs, - dy_min=self.dy_min, dy_max=self.dy_max, n_dys=self.n_dys, - angle_min=self.angle_min, angle_max=self.angle_max, - n_angles=self.n_angles, black_border_size=self.black_border_size) - - def parse_params(self, - n_samples=None, - dx_min=-0.1, - dx_max=0.1, - n_dxs=2, - dy_min=-0.1, - dy_max=0.1, - n_dys=2, - angle_min=-30, - angle_max=30, - n_angles=6, - black_border_size=0, - **kwargs): - """ - Take in a dictionary of parameters and applies attack-specific checks - before saving them as attributes. - :param n_samples: (optional) The number of transformations sampled to - construct the attack. Set it to None to run - full grid attack. - :param dx_min: (optional float) Minimum translation ratio along x-axis. - :param dx_max: (optional float) Maximum translation ratio along x-axis. - :param n_dxs: (optional int) Number of discretized translation ratios - along x-axis. - :param dy_min: (optional float) Minimum translation ratio along y-axis. - :param dy_max: (optional float) Maximum translation ratio along y-axis. - :param n_dys: (optional int) Number of discretized translation ratios - along y-axis. - :param angle_min: (optional float) Largest counter-clockwise rotation - angle. - :param angle_max: (optional float) Largest clockwise rotation angle. - :param n_angles: (optional int) Number of discretized angles. - :param black_border_size: (optional int) size of the black border in pixels. - """ - self.n_samples = n_samples - self.dx_min = dx_min - self.dx_max = dx_max - self.n_dxs = n_dxs - self.dy_min = dy_min - self.dy_max = dy_max - self.n_dys = n_dys - self.angle_min = angle_min - self.angle_max = angle_max - self.n_angles = n_angles - self.black_border_size = black_border_size - - if self.dx_min < -1 or self.dy_min < -1 or \ - self.dx_max > 1 or self.dy_max > 1: - raise ValueError("The value of translation must be bounded " - "within [-1, 1]") - if len(kwargs.keys()) > 0: - warnings.warn("kwargs is unused and will be removed on or after " - "2019-04-26.") - return True diff --git a/cleverhans/attacks/spsa.py b/cleverhans/attacks/spsa.py deleted file mode 100644 index 7d635dc69..000000000 --- a/cleverhans/attacks/spsa.py +++ /dev/null @@ -1,759 +0,0 @@ -"""The SPSA attack -""" -# pylint: disable=missing-docstring -import warnings - -import numpy as np -from six.moves import xrange -import tensorflow as tf -import tensorflow_addons as tfa - -from cleverhans.attacks.attack import Attack -from cleverhans.compat import reduce_mean, reduce_sum, reduce_max -from cleverhans.model import Model -from cleverhans import utils_tf - -tf_dtype = tf.as_dtype('float32') - - -class SPSA(Attack): - """ - This implements the SPSA adversary, as in https://arxiv.org/abs/1802.05666 - (Uesato et al. 2018). SPSA is a gradient-free optimization method, which - is useful when the model is non-differentiable, or more generally, the - gradients do not point in useful directions. - - :param model: cleverhans.model.Model - :param sess: optional tf.Session - :param dtypestr: dtype of the data - :param kwargs: passed through to super constructor - """ - - DEFAULT_SPSA_SAMPLES = 128 - DEFAULT_SPSA_ITERS = 1 - DEFAULT_DELTA = 0.01 - DEFAULT_LEARNING_RATE = 0.01 - - def __init__(self, model, sess=None, dtypestr='float32', **kwargs): - super(SPSA, self).__init__(model, sess, dtypestr, **kwargs) - - self.feedable_kwargs = ('eps', 'clip_min', 'clip_max', 'y', 'y_target') - self.structural_kwargs = [ - 'nb_iter', - 'spsa_samples', - 'spsa_iters', - 'early_stop_loss_threshold', - 'is_debug', - 'is_targeted', - ] - - assert isinstance(self.model, Model) - - def generate(self, - x, - y=None, - y_target=None, - eps=None, - clip_min=None, - clip_max=None, - nb_iter=None, - is_targeted=None, - early_stop_loss_threshold=None, - learning_rate=DEFAULT_LEARNING_RATE, - delta=DEFAULT_DELTA, - spsa_samples=DEFAULT_SPSA_SAMPLES, - batch_size=None, - spsa_iters=DEFAULT_SPSA_ITERS, - is_debug=False, - epsilon=None, - num_steps=None): - """ - Generate symbolic graph for adversarial examples. - - :param x: The model's symbolic inputs. Must be a batch of size 1. - :param y: A Tensor or None. The index of the correct label. - :param y_target: A Tensor or None. The index of the target label in a - targeted attack. - :param eps: The size of the maximum perturbation, measured in the - L-infinity norm. - :param clip_min: If specified, the minimum input value - :param clip_max: If specified, the maximum input value - :param nb_iter: The number of optimization steps. - :param early_stop_loss_threshold: A float or None. If specified, the - attack will end as soon as the loss - is below `early_stop_loss_threshold`. - :param learning_rate: Learning rate of ADAM optimizer. - :param delta: Perturbation size used for SPSA approximation. - :param spsa_samples: Number of inputs to evaluate at a single time. - The true batch size (the number of evaluated - inputs for each update) is `spsa_samples * - spsa_iters` - :param batch_size: Deprecated param that is an alias for spsa_samples - :param spsa_iters: Number of model evaluations before performing an - update, where each evaluation is on `spsa_samples` - different inputs. - :param is_debug: If True, print the adversarial loss after each update. - :param epsilon: Deprecated alias for `eps` - :param num_steps: Deprecated alias for `nb_iter`. - :param is_targeted: Deprecated argument. Ignored. - """ - - if epsilon is not None: - if eps is not None: - raise ValueError("Should not specify both eps and its deprecated " - "alias, epsilon") - warnings.warn("`epsilon` is deprecated. Switch to `eps`. `epsilon` may " - "be removed on or after 2019-04-15.") - eps = epsilon - del epsilon - - if num_steps is not None: - if nb_iter is not None: - raise ValueError("Should not specify both nb_iter and its deprecated " - "alias, num_steps") - warnings.warn("`num_steps` is deprecated. Switch to `nb_iter`. " - "`num_steps` may be removed on or after 2019-04-15.") - nb_iter = num_steps - del num_steps - assert nb_iter is not None - - if (y is not None) + (y_target is not None) != 1: - raise ValueError("Must specify exactly one of y (untargeted attack, " - "cause the input not to be classified as this true " - "label) and y_target (targeted attack, cause the " - "input to be classified as this target label).") - - if is_targeted is not None: - warnings.warn("`is_targeted` is deprecated. Simply do not specify it." - " It may become an error to specify it on or after " - "2019-04-15.") - assert is_targeted == y_target is not None - - is_targeted = y_target is not None - - if x.get_shape().as_list()[0] is None: - check_batch = utils_tf.assert_equal(tf.shape(x)[0], 1) - with tf.control_dependencies([check_batch]): - x = tf.identity(x) - elif x.get_shape().as_list()[0] != 1: - raise ValueError("For SPSA, input tensor x must have batch_size of 1.") - - if batch_size is not None: - warnings.warn( - 'The "batch_size" argument to SPSA is deprecated, and will ' - 'be removed on 2019-03-17. ' - 'Please use spsa_samples instead.') - spsa_samples = batch_size - - optimizer = SPSAAdam( - lr=learning_rate, - delta=delta, - num_samples=spsa_samples, - num_iters=spsa_iters) - - def loss_fn(x, label): - """ - Margin logit loss, with correct sign for targeted vs untargeted loss. - """ - logits = self.model.get_logits(x) - loss_multiplier = 1 if is_targeted else -1 - return loss_multiplier * margin_logit_loss( - logits, label, - nb_classes=self.model.nb_classes or logits.get_shape()[-1]) - - y_attack = y_target if is_targeted else y - adv_x = projected_optimization( - loss_fn, - x, - y_attack, - eps, - num_steps=nb_iter, - optimizer=optimizer, - early_stop_loss_threshold=early_stop_loss_threshold, - is_debug=is_debug, - clip_min=clip_min, - clip_max=clip_max - ) - return adv_x - - def generate_np(self, x_val, **kwargs): - if "epsilon" in kwargs: - warnings.warn("Using deprecated argument: see `generate`") - assert "eps" not in kwargs - kwargs["eps"] = kwargs["epsilon"] - del kwargs["epsilon"] - assert "eps" in kwargs - - if "num_steps" in kwargs: - warnings.warn("Using deprecated argument: see `generate`") - assert "nb_iter" not in kwargs - kwargs["nb_iter"] = kwargs["num_steps"] - del kwargs["num_steps"] - - if 'y' in kwargs and kwargs['y'] is not None: - assert kwargs['y'].dtype in [np.int32, np.int64] - if 'y_target' in kwargs and kwargs['y_target'] is not None: - assert kwargs['y_target'].dtype in [np.int32, np.int64] - - # Call self.generate() sequentially for each image in the batch - x_adv = [] - batch_size = x_val.shape[0] - y = kwargs.pop('y', [None] * batch_size) - assert len(x_val) == len(y), '# of images and labels should match' - for x_single, y_single in zip(x_val, y): - x = np.expand_dims(x_single, axis=0) - adv_img = super(SPSA, self).generate_np(x, y=y_single, **kwargs) - x_adv.append(adv_img) - return np.concatenate(x_adv, axis=0) - - -def _project_perturbation(perturbation, epsilon, input_image, clip_min=None, - clip_max=None): - """Project `perturbation` onto L-infinity ball of radius `epsilon`. - Also project into hypercube such that the resulting adversarial example - is between clip_min and clip_max, if applicable. - """ - - if clip_min is None or clip_max is None: - raise NotImplementedError("_project_perturbation currently has clipping " - "hard-coded in.") - - # Ensure inputs are in the correct range - with tf.control_dependencies([ - utils_tf.assert_less_equal(input_image, - tf.cast(clip_max, input_image.dtype)), - utils_tf.assert_greater_equal(input_image, - tf.cast(clip_min, input_image.dtype)) - ]): - clipped_perturbation = utils_tf.clip_by_value( - perturbation, -epsilon, epsilon) - new_image = utils_tf.clip_by_value( - input_image + clipped_perturbation, clip_min, clip_max) - return new_image - input_image - - -class TensorOptimizer(object): - """Optimizer for Tensors rather than tf.Variables. - - TensorOptimizers implement optimizers where the values being optimized - are ordinary Tensors, rather than Variables. TF Variables can have strange - behaviors when being assigned multiple times within a single sess.run() - call, particularly in Distributed TF, so this avoids thinking about those - issues. These are helper classes for the `projected_optimization` - method. Apart from not using Variables, they follow an interface very - similar to tf.Optimizer. - """ - - def _compute_gradients(self, loss_fn, x, unused_optim_state): - """Compute a new value of `x` to minimize `loss_fn`. - - Args: - loss_fn: a callable that takes `x`, a batch of images, and returns - a batch of loss values. `x` will be optimized to minimize - `loss_fn(x)`. - x: A list of Tensors, the values to be updated. This is analogous - to the `var_list` argument in standard TF Optimizer. - unused_optim_state: A (possibly nested) dict, containing any state - info needed for the optimizer. - - Returns: - new_x: A list of Tensors, the same length as `x`, which are updated - new_optim_state: A dict, with the same structure as `optim_state`, - which have been updated. - """ - - # Assumes `x` is a list, - # and contains a tensor representing a batch of images - assert len(x) == 1 and isinstance(x, list), \ - 'x should be a list and contain only one image tensor' - x = x[0] - loss = reduce_mean(loss_fn(x), axis=0) - return tf.gradients(loss, x) - - def _apply_gradients(self, grads, x, optim_state): - """ - Given a gradient, make one optimization step. - - :param grads: list of tensors, same length as `x`, containing the corresponding gradients - :param x: list of tensors to update - :param optim_state: dict - - Returns: - new_x: list of tensors, updated version of `x` - new_optim_state: dict, updated version of `optim_state` - """ - raise NotImplementedError( - "_apply_gradients should be defined in each subclass") - - def minimize(self, loss_fn, x, optim_state): - """ - Analogous to tf.Optimizer.minimize - - :param loss_fn: tf Tensor, representing the loss to minimize - :param x: list of Tensor, analogous to tf.Optimizer's var_list - :param optim_state: A possibly nested dict, containing any optimizer state. - - Returns: - new_x: list of Tensor, updated version of `x` - new_optim_state: dict, updated version of `optim_state` - """ - grads = self._compute_gradients(loss_fn, x, optim_state) - return self._apply_gradients(grads, x, optim_state) - - def init_state(self, x): - """Returns the initial state of the optimizer. - - Args: - x: A list of Tensors, which will be optimized. - - Returns: - A dictionary, representing the initial state of the optimizer. - """ - raise NotImplementedError( - "init_state should be defined in each subclass") - - -class TensorGradientDescent(TensorOptimizer): - """Vanilla Gradient Descent TensorOptimizer.""" - - def __init__(self, lr): - self._lr = lr - - def init_state(self, x): - return {} - - def _apply_gradients(self, grads, x, optim_state): - new_x = [None] * len(x) - for i in xrange(len(x)): - new_x[i] = x[i] - self._lr * grads[i] - return new_x, optim_state - - -class TensorAdam(TensorOptimizer): - """The Adam optimizer defined in https://arxiv.org/abs/1412.6980.""" - - def __init__(self, lr=0.001, beta1=0.9, beta2=0.999, epsilon=1e-9): - self._lr = lr - self._beta1 = beta1 - self._beta2 = beta2 - self._epsilon = epsilon - - def init_state(self, x): - """ - Initialize t, m, and u - """ - optim_state = {} - optim_state["t"] = 0. - optim_state["m"] = [tf.zeros_like(v) for v in x] - optim_state["u"] = [tf.zeros_like(v) for v in x] - return optim_state - - def _apply_gradients(self, grads, x, optim_state): - """Refer to parent class documentation.""" - new_x = [None] * len(x) - new_optim_state = { - "t": optim_state["t"] + 1., - "m": [None] * len(x), - "u": [None] * len(x) - } - t = new_optim_state["t"] - for i in xrange(len(x)): - g = grads[i] - m_old = optim_state["m"][i] - u_old = optim_state["u"][i] - new_optim_state["m"][i] = ( - self._beta1 * m_old + (1. - self._beta1) * g) - new_optim_state["u"][i] = ( - self._beta2 * u_old + (1. - self._beta2) * g * g) - m_hat = new_optim_state["m"][i] / (1. - tf.pow(self._beta1, t)) - u_hat = new_optim_state["u"][i] / (1. - tf.pow(self._beta2, t)) - new_x[i] = ( - x[i] - self._lr * m_hat / (tf.sqrt(u_hat) + self._epsilon)) - return new_x, new_optim_state - - -class SPSAAdam(TensorAdam): - """Optimizer for gradient-free attacks in https://arxiv.org/abs/1802.05666. - - Gradients estimates are computed using Simultaneous Perturbation Stochastic - Approximation (SPSA), combined with the ADAM update rule. - """ - - def __init__(self, - lr=0.01, - delta=0.01, - num_samples=128, - num_iters=1, - compare_to_analytic_grad=False): - super(SPSAAdam, self).__init__(lr=lr) - assert num_samples % 2 == 0, "number of samples must be even" - self._delta = delta - self._num_samples = num_samples // 2 # Since we mirror +/- delta later - self._num_iters = num_iters - self._compare_to_analytic_grad = compare_to_analytic_grad - - def _get_delta(self, x, delta): - x_shape = x.get_shape().as_list() - delta_x = delta * tf.sign( - tf.random_uniform( - [self._num_samples] + x_shape[1:], - minval=-1., - maxval=1., - dtype=tf_dtype)) - return delta_x - - def _compute_gradients(self, loss_fn, x, unused_optim_state): - """Compute gradient estimates using SPSA.""" - # Assumes `x` is a list, containing a [1, H, W, C] image - # If static batch dimension is None, tf.reshape to batch size 1 - # so that static shape can be inferred - assert len(x) == 1 - static_x_shape = x[0].get_shape().as_list() - if static_x_shape[0] is None: - x[0] = tf.reshape(x[0], [1] + static_x_shape[1:]) - assert x[0].get_shape().as_list()[0] == 1 - x = x[0] - x_shape = x.get_shape().as_list() - - def body(i, grad_array): - delta = self._delta - delta_x = self._get_delta(x, delta) - delta_x = tf.concat([delta_x, -delta_x], axis=0) - loss_vals = tf.reshape( - loss_fn(x + delta_x), - [2 * self._num_samples] + [1] * (len(x_shape) - 1)) - avg_grad = reduce_mean(loss_vals * delta_x, axis=0) / delta - avg_grad = tf.expand_dims(avg_grad, axis=0) - new_grad_array = grad_array.write(i, avg_grad) - return i + 1, new_grad_array - - def cond(i, _): - return i < self._num_iters - - _, all_grads = tf.while_loop( - cond, - body, - loop_vars=[ - 0, tf.TensorArray(size=self._num_iters, dtype=tf_dtype) - ], - back_prop=False, - parallel_iterations=1) - avg_grad = reduce_sum(all_grads.stack(), axis=0) - return [avg_grad] - - -def margin_logit_loss(model_logits, label, nb_classes=10, num_classes=None): - """Computes difference between logit for `label` and next highest logit. - - The loss is high when `label` is unlikely (targeted by default). - This follows the same interface as `loss_fn` for TensorOptimizer and - projected_optimization, i.e. it returns a batch of loss values. - """ - if num_classes is not None: - warnings.warn("`num_classes` is depreciated. Switch to `nb_classes`." - " `num_classes` may be removed on or after 2019-04-23.") - nb_classes = num_classes - del num_classes - if 'int' in str(label.dtype): - logit_mask = tf.one_hot(label, depth=nb_classes, axis=-1) - else: - logit_mask = label - if 'int' in str(logit_mask.dtype): - logit_mask = tf.to_float(logit_mask) - try: - label_logits = reduce_sum(logit_mask * model_logits, axis=-1) - except TypeError: - raise TypeError("Could not take row-wise dot product between " - "logit mask, of dtype " + str(logit_mask.dtype) - + " and model_logits, of dtype " - + str(model_logits.dtype)) - logits_with_target_label_neg_inf = model_logits - logit_mask * 99999 - highest_nonlabel_logits = reduce_max( - logits_with_target_label_neg_inf, axis=-1) - loss = highest_nonlabel_logits - label_logits - return loss - - -def _apply_black_border(x, border_size): - orig_height = x.get_shape().as_list()[1] - orig_width = x.get_shape().as_list()[2] - x = tf.image.resize_images(x, (orig_width - 2*border_size, - orig_height - 2*border_size)) - - return tf.pad(x, [[0, 0], - [border_size, border_size], - [border_size, border_size], - [0, 0]], 'CONSTANT') - - -def _apply_transformation(inputs): - x, trans = inputs[0], inputs[1] - dx, dy, angle = trans[0], trans[1], trans[2] - height = x.get_shape().as_list()[1] - width = x.get_shape().as_list()[2] - - # Pad the image to prevent two-step rotation / translation from truncating - # corners - max_dist_from_center = np.sqrt(height**2+width**2) / 2 - min_edge_from_center = float(np.min([height, width])) / 2 - padding = np.ceil(max_dist_from_center - - min_edge_from_center).astype(np.int32) - x = tf.pad(x, [[0, 0], - [padding, padding], - [padding, padding], - [0, 0]], - 'CONSTANT') - - # Apply rotation - angle *= np.pi / 180 - x = tfa.image.rotate(x, angle, interpolation='BILINEAR') - - # Apply translation - dx_in_px = -dx * height - dy_in_px = -dy * width - translation = tf.convert_to_tensor([dx_in_px, dy_in_px]) - - try: - x = tfa.image.translate(x, translation, interpolation='BILINEAR') - except AttributeError as e: - print("WARNING: SpatialAttack requires tf 1.6 or higher") - raise e - x = tfa.image.translate(x, translation, interpolation='BILINEAR') - return tf.image.resize_image_with_crop_or_pad(x, height, width) - - -def spm(x, model, y=None, n_samples=None, dx_min=-0.1, - dx_max=0.1, n_dxs=5, dy_min=-0.1, dy_max=0.1, n_dys=5, - angle_min=-30, angle_max=30, n_angles=31, black_border_size=0): - """ - TensorFlow implementation of the Spatial Transformation Method. - :return: a tensor for the adversarial example - """ - if y is None: - preds = model.get_probs(x) - # Using model predictions as ground truth to avoid label leaking - preds_max = reduce_max(preds, 1, keepdims=True) - y = tf.to_float(tf.equal(preds, preds_max)) - y = tf.stop_gradient(y) - del preds - y = y / reduce_sum(y, 1, keepdims=True) - - # Define the range of transformations - dxs = np.linspace(dx_min, dx_max, n_dxs) - dys = np.linspace(dy_min, dy_max, n_dys) - angles = np.linspace(angle_min, angle_max, n_angles) - - if n_samples is None: - import itertools - transforms = list(itertools.product(*[dxs, dys, angles])) - else: - sampled_dxs = np.random.choice(dxs, n_samples) - sampled_dys = np.random.choice(dys, n_samples) - sampled_angles = np.random.choice(angles, n_samples) - transforms = zip(sampled_dxs, sampled_dys, sampled_angles) - transformed_ims = parallel_apply_transformations( - x, transforms, black_border_size) - - def _compute_xent(x): - preds = model.get_logits(x) - return tf.nn.softmax_cross_entropy_with_logits_v2( - labels=y, logits=preds) - - all_xents = tf.map_fn( - _compute_xent, - transformed_ims, - parallel_iterations=1) # Must be 1 to avoid keras race conditions - - # Return the adv_x with worst accuracy - - # all_xents is n_total_samples x batch_size (SB) - all_xents = tf.stack(all_xents) # SB - - # We want the worst case sample, with the largest xent_loss - worst_sample_idx = tf.argmax(all_xents, axis=0) # B - - batch_size = tf.shape(x)[0] - keys = tf.stack([ - tf.range(batch_size, dtype=tf.int32), - tf.cast(worst_sample_idx, tf.int32) - ], axis=1) - transformed_ims_bshwc = tf.einsum('sbhwc->bshwc', transformed_ims) - after_lookup = tf.gather_nd(transformed_ims_bshwc, keys) # BHWC - return after_lookup - - -def parallel_apply_transformations(x, transforms, black_border_size=0): - """ - Apply image transformations in parallel. - :param transforms: TODO - :param black_border_size: int, size of black border to apply - Returns: - Transformed images - """ - transforms = tf.convert_to_tensor(transforms, dtype=tf.float32) - x = _apply_black_border(x, black_border_size) - - num_transforms = transforms.get_shape().as_list()[0] - im_shape = x.get_shape().as_list()[1:] - - # Pass a copy of x and a transformation to each iteration of the map_fn - # callable - tiled_x = tf.reshape( - tf.tile(x, [num_transforms, 1, 1, 1]), - [num_transforms, -1] + im_shape) - elems = [tiled_x, transforms] - transformed_ims = tf.map_fn( - _apply_transformation, - elems, - dtype=tf.float32, - parallel_iterations=1, # Must be 1 to avoid keras race conditions - ) - return transformed_ims - - -def projected_optimization(loss_fn, - input_image, - label, - epsilon, - num_steps, - clip_min=None, - clip_max=None, - optimizer=TensorAdam(), - project_perturbation=_project_perturbation, - early_stop_loss_threshold=None, - is_debug=False): - """Generic projected optimization, generalized to work with approximate - gradients. Used for e.g. the SPSA attack. - - Args: - :param loss_fn: A callable which takes `input_image` and `label` as - arguments, and returns a batch of loss values. Same - interface as TensorOptimizer. - :param input_image: Tensor, a batch of images - :param label: Tensor, a batch of labels - :param epsilon: float, the L-infinity norm of the maximum allowable - perturbation - :param num_steps: int, the number of steps of gradient descent - :param clip_min: float, minimum pixel value - :param clip_max: float, maximum pixel value - :param optimizer: A `TensorOptimizer` object - :param project_perturbation: A function, which will be used to enforce - some constraint. It should have the same - signature as `_project_perturbation`. - :param early_stop_loss_threshold: A float or None. If specified, the attack will end if the loss is below - `early_stop_loss_threshold`. - Enabling this option can have several different effects: - - Setting the threshold to 0. guarantees that if a successful attack is found, it is returned. - This increases the attack success rate, because without early stopping the optimizer can accidentally - bounce back to a point where the attack fails. - - Early stopping can make the attack run faster because it may run for fewer steps. - - Early stopping can make the attack run slower because the loss must be calculated at each step. - The loss is not calculated as part of the normal SPSA optimization procedure. - For most reasonable choices of hyperparameters, early stopping makes the attack much faster because - it decreases the number of steps dramatically. - :param is_debug: A bool. If True, print debug info for attack progress. - - Returns: - adversarial version of `input_image`, with L-infinity difference less than - epsilon, which tries to minimize loss_fn. - - Note that this function is not intended as an Attack by itself. Rather, it - is designed as a helper function which you can use to write your own attack - methods. The method uses a tf.while_loop to optimize a loss function in - a single sess.run() call. - """ - assert num_steps is not None - if is_debug: - with tf.device("/cpu:0"): - input_image = tf.Print( - input_image, [], - "Starting PGD attack with epsilon: %s" % epsilon) - - init_perturbation = tf.random_uniform( - tf.shape(input_image), - minval=tf.cast(-epsilon, input_image.dtype), - maxval=tf.cast(epsilon, input_image.dtype), - dtype=input_image.dtype) - init_perturbation = project_perturbation(init_perturbation, epsilon, - input_image, clip_min=clip_min, - clip_max=clip_max) - init_optim_state = optimizer.init_state([init_perturbation]) - nest = tf.nest - - def loop_body(i, perturbation, flat_optim_state): - """Update perturbation to input image.""" - optim_state = nest.pack_sequence_as( - structure=init_optim_state, flat_sequence=flat_optim_state) - - def wrapped_loss_fn(x): - return loss_fn(input_image + x, label) - - new_perturbation_list, new_optim_state = optimizer.minimize( - wrapped_loss_fn, [perturbation], optim_state) - projected_perturbation = project_perturbation(new_perturbation_list[0], - epsilon, input_image, - clip_min=clip_min, - clip_max=clip_max) - - # Be careful with this bool. A value of 0. is a valid threshold but evaluates to False, so we must explicitly - # check whether the value is None. - early_stop = early_stop_loss_threshold is not None - compute_loss = is_debug or early_stop - # Don't waste time building the loss graph if we're not going to use it - if compute_loss: - # NOTE: this step is not actually redundant with the optimizer step. - # SPSA calculates the loss at randomly perturbed points but doesn't calculate the loss at the current point. - loss = reduce_mean(wrapped_loss_fn(projected_perturbation), axis=0) - - if is_debug: - with tf.device("/cpu:0"): - loss = tf.Print(loss, [loss], "Total batch loss") - - if early_stop: - i = tf.cond(tf.less(loss, early_stop_loss_threshold), - lambda: float(num_steps), lambda: i) - - return i + 1, projected_perturbation, nest.flatten(new_optim_state) - - def cond(i, *_): - return tf.less(i, num_steps) - - flat_init_optim_state = nest.flatten(init_optim_state) - _, final_perturbation, _ = tf.while_loop( - cond, - loop_body, - loop_vars=(tf.constant(0.), init_perturbation, flat_init_optim_state), - parallel_iterations=1, - back_prop=False, - maximum_iterations=num_steps) - if project_perturbation is _project_perturbation: - # TODO: this assert looks totally wrong. - # Not bothering to fix it now because it's only an assert. - # 1) Multiplying by 1.1 gives a huge margin of error. This should probably - # take the difference and allow a tolerance of 1e-6 or something like - # that. - # 2) I think it should probably check the *absolute value* of - # final_perturbation - perturbation_max = epsilon * 1.1 - check_diff = utils_tf.assert_less_equal( - final_perturbation, - tf.cast(perturbation_max, final_perturbation.dtype), - message="final_perturbation must change no pixel by more than " - "%s" % perturbation_max) - else: - # TODO: let caller pass in a check_diff function as well as - # project_perturbation - check_diff = tf.no_op() - - if clip_min is None or clip_max is None: - raise NotImplementedError("This function only supports clipping for now") - check_range = [utils_tf.assert_less_equal(input_image, - tf.cast(clip_max, - input_image.dtype)), - utils_tf.assert_greater_equal(input_image, - tf.cast(clip_min, - input_image.dtype))] - - with tf.control_dependencies([check_diff] + check_range): - adversarial_image = input_image + final_perturbation - return tf.stop_gradient(adversarial_image) diff --git a/cleverhans/attacks/virtual_adversarial_method.py b/cleverhans/attacks/virtual_adversarial_method.py deleted file mode 100644 index 70aab3cde..000000000 --- a/cleverhans/attacks/virtual_adversarial_method.py +++ /dev/null @@ -1,145 +0,0 @@ -"""The VirtualAdversarialMethod attack - -""" - -import warnings - -import tensorflow as tf - -from cleverhans.attacks.attack import Attack -from cleverhans.model import Model, CallableModelWrapper -from cleverhans.model import wrapper_warning_logits -from cleverhans import utils_tf - -tf_dtype = tf.as_dtype('float32') - -class VirtualAdversarialMethod(Attack): - """ - This attack was originally proposed by Miyato et al. (2016) and was used - for virtual adversarial training. - Paper link: https://arxiv.org/abs/1507.00677 - - :param model: cleverhans.model.Model - :param sess: optional tf.Session - :param dtypestr: dtype of the data - :param kwargs: passed through to super constructor - """ - - def __init__(self, model, sess=None, dtypestr='float32', **kwargs): - """ - Note: the model parameter should be an instance of the - cleverhans.model.Model abstraction provided by CleverHans. - """ - if not isinstance(model, Model): - wrapper_warning_logits() - model = CallableModelWrapper(model, 'logits') - - super(VirtualAdversarialMethod, self).__init__(model, sess, dtypestr, - **kwargs) - - self.feedable_kwargs = ('eps', 'xi', 'clip_min', 'clip_max') - self.structural_kwargs = ['num_iterations'] - - def generate(self, x, **kwargs): - """ - Generate symbolic graph for adversarial examples and return. - - :param x: The model's symbolic inputs. - :param kwargs: See `parse_params` - """ - # Parse and save attack-specific parameters - assert self.parse_params(**kwargs) - - return vatm( - self.model, - x, - self.model.get_logits(x), - eps=self.eps, - num_iterations=self.num_iterations, - xi=self.xi, - clip_min=self.clip_min, - clip_max=self.clip_max) - - def parse_params(self, - eps=2.0, - nb_iter=None, - xi=1e-6, - clip_min=None, - clip_max=None, - num_iterations=None, - **kwargs): - """ - Take in a dictionary of parameters and applies attack-specific checks - before saving them as attributes. - - Attack-specific parameters: - - :param eps: (optional float )the epsilon (input variation parameter) - :param nb_iter: (optional) the number of iterations - Defaults to 1 if not specified - :param xi: (optional float) the finite difference parameter - :param clip_min: (optional float) Minimum input component value - :param clip_max: (optional float) Maximum input component value - :param num_iterations: Deprecated alias for `nb_iter` - """ - # Save attack-specific parameters - self.eps = eps - if num_iterations is not None: - warnings.warn("`num_iterations` is deprecated. Switch to `nb_iter`." - " The old name will be removed on or after 2019-04-26.") - # Note: when we remove the deprecated alias, we can put the default - # value of 1 for nb_iter back in the method signature - assert nb_iter is None - nb_iter = num_iterations - del num_iterations - if nb_iter is None: - nb_iter = 1 - self.num_iterations = nb_iter - self.xi = xi - self.clip_min = clip_min - self.clip_max = clip_max - if len(kwargs.keys()) > 0: - warnings.warn("kwargs is unused and will be removed on or after " - "2019-04-26.") - return True - - -def vatm(model, - x, - logits, - eps, - num_iterations=1, - xi=1e-6, - clip_min=None, - clip_max=None, - scope=None): - """ - Tensorflow implementation of the perturbation method used for virtual - adversarial training: https://arxiv.org/abs/1507.00677 - :param model: the model which returns the network unnormalized logits - :param x: the input placeholder - :param logits: the model's unnormalized output tensor (the input to - the softmax layer) - :param eps: the epsilon (input variation parameter) - :param num_iterations: the number of iterations - :param xi: the finite difference parameter - :param clip_min: optional parameter that can be used to set a minimum - value for components of the example returned - :param clip_max: optional parameter that can be used to set a maximum - value for components of the example returned - :param seed: the seed for random generator - :return: a tensor for the adversarial example - """ - with tf.name_scope(scope, "virtual_adversarial_perturbation"): - d = tf.random_normal(tf.shape(x), dtype=tf_dtype) - for _ in range(num_iterations): - d = xi * utils_tf.l2_batch_normalize(d) - logits_d = model.get_logits(x + d) - kl = utils_tf.kl_with_logits(logits, logits_d) - Hd = tf.gradients(kl, d)[0] - d = tf.stop_gradient(Hd) - d = eps * utils_tf.l2_batch_normalize(d) - adv_x = x + d - if (clip_min is not None) and (clip_max is not None): - adv_x = tf.clip_by_value(adv_x, clip_min, clip_max) - return adv_x diff --git a/cleverhans/attacks_tf.py b/cleverhans/attacks_tf.py deleted file mode 100644 index c16fcd8f9..000000000 --- a/cleverhans/attacks_tf.py +++ /dev/null @@ -1,195 +0,0 @@ -# pylint: disable=missing-docstring -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - -import warnings - -import numpy as np - -from cleverhans.attacks.carlini_wagner_l2 import CWL2 as CarliniWagnerL2 # pylint: disable=unused-import -from cleverhans.attacks.deep_fool import deepfool_batch, deepfool_attack # pylint: disable=unused-import -from cleverhans.attacks.elastic_net_method import EAD as ElasticNetMethod # pylint: disable=unused-import -from cleverhans.attacks.lbfgs import LBFGS_impl as LBFGS_attack # pylint: disable=unused-import -from cleverhans.attacks.saliency_map_method import jsma_symbolic # pylint: disable=unused-import -from cleverhans.attacks.spsa import TensorOptimizer, TensorGradientDescent, TensorAdam # pylint: disable=unused-import -from cleverhans.attacks.spsa import SPSAAdam, margin_logit_loss, _apply_black_border # pylint: disable=unused-import -from cleverhans.attacks.spsa import _apply_transformation, spm, parallel_apply_transformations # pylint: disable=unused-import -from cleverhans.attacks.virtual_adversarial_method import vatm # pylint: disable=unused-import -from cleverhans.utils_tf import jacobian_graph, jacobian_augmentation # pylint: disable=unused-import -from cleverhans import utils - -np_dtype = np.dtype('float32') - -_logger = utils.create_logger("cleverhans.attacks.tf") - -warnings.warn("attacks_tf is deprecated and will be removed on 2019-07-18" - " or after. Code should import functions from their new locations directly.") - - -def fgsm(x, predictions, eps=0.3, clip_min=None, clip_max=None): - warnings.warn("This function is deprecated and will be removed on or after " - "2019-04-09. Switch to cleverhans.attacks.FastGradientMethod.") - return fgm( - x, - predictions, - y=None, - eps=eps, - ord=np.inf, - clip_min=clip_min, - clip_max=clip_max) - - -def fgm(x, preds, *args, **kwargs): - if preds.op.type == 'Softmax': - logits, = preds.op.inputs - else: - raise TypeError("Unclear how to get logits") - warnings.warn("This function is deprecated. Switch to passing *logits* to" - " cleverhans.attacks.fgm") - from cleverhans.attacks import fgm as logits_fgm - return logits_fgm(x, logits, *args, **kwargs) - - -def apply_perturbations(i, j, X, increase, theta, clip_min, clip_max): - """ - TensorFlow implementation for apply perturbations to input features based - on saliency maps - :param i: index of first selected feature - :param j: index of second selected feature - :param X: a matrix containing our input features for our sample - :param increase: boolean; true if we are increasing pixels, false otherwise - :param theta: delta for each feature adjustment - :param clip_min: mininum value for a feature in our sample - :param clip_max: maximum value for a feature in our sample - : return: a perturbed input feature matrix for a target class - """ - warnings.warn( - "This function is dead code and will be removed on or after 2019-07-18") - - # perturb our input sample - if increase: - X[0, i] = np.minimum(clip_max, X[0, i] + theta) - X[0, j] = np.minimum(clip_max, X[0, j] + theta) - else: - X[0, i] = np.maximum(clip_min, X[0, i] - theta) - X[0, j] = np.maximum(clip_min, X[0, j] - theta) - - return X - - -def saliency_map(grads_target, grads_other, search_domain, increase): - """ - TensorFlow implementation for computing saliency maps - :param grads_target: a matrix containing forward derivatives for the - target class - :param grads_other: a matrix where every element is the sum of forward - derivatives over all non-target classes at that index - :param search_domain: the set of input indices that we are considering - :param increase: boolean; true if we are increasing pixels, false otherwise - :return: (i, j, search_domain) the two input indices selected and the - updated search domain - """ - warnings.warn( - "This function is dead code and will be removed on or after 2019-07-18") - - # Compute the size of the input (the number of features) - nf = len(grads_target) - - # Remove the already-used input features from the search space - invalid = list(set(range(nf)) - search_domain) - increase_coef = (2 * int(increase) - 1) - grads_target[invalid] = -increase_coef * np.max(np.abs(grads_target)) - grads_other[invalid] = increase_coef * np.max(np.abs(grads_other)) - - # Create a 2D numpy array of the sum of grads_target and grads_other - target_sum = grads_target.reshape((1, nf)) + grads_target.reshape((nf, 1)) - other_sum = grads_other.reshape((1, nf)) + grads_other.reshape((nf, 1)) - - # Create a mask to only keep features that match saliency map conditions - if increase: - scores_mask = ((target_sum > 0) & (other_sum < 0)) - else: - scores_mask = ((target_sum < 0) & (other_sum > 0)) - - # Create a 2D numpy array of the scores for each pair of candidate features - scores = scores_mask * (-target_sum * other_sum) - - # A pixel can only be selected (and changed) once - np.fill_diagonal(scores, 0) - - # Extract the best two pixels - best = np.argmax(scores) - p1, p2 = best % nf, best // nf - - # Remove used pixels from our search domain - search_domain.discard(p1) - search_domain.discard(p2) - - return p1, p2, search_domain - - -def jacobian(sess, x, grads, target, X, nb_features, nb_classes, feed=None): - """ - TensorFlow implementation of the foward derivative / Jacobian - :param x: the input placeholder - :param grads: the list of TF gradients returned by jacobian_graph() - :param target: the target misclassification class - :param X: numpy array with sample input - :param nb_features: the number of features in the input - :return: matrix of forward derivatives flattened into vectors - """ - warnings.warn( - "This function is dead code and will be removed on or after 2019-07-18") - - # Prepare feeding dictionary for all gradient computations - feed_dict = {x: X} - if feed is not None: - feed_dict.update(feed) - - # Initialize a numpy array to hold the Jacobian component values - jacobian_val = np.zeros((nb_classes, nb_features), dtype=np_dtype) - - # Compute the gradients for all classes - for class_ind, grad in enumerate(grads): - run_grad = sess.run(grad, feed_dict) - jacobian_val[class_ind] = np.reshape(run_grad, (1, nb_features)) - - # Sum over all classes different from the target class to prepare for - # saliency map computation in the next step of the attack - other_classes = utils.other_classes(nb_classes, target) - grad_others = np.sum(jacobian_val[other_classes, :], axis=0) - - return jacobian_val[target], grad_others - - -class UnrolledOptimizer(TensorOptimizer): - def __init__(self, *args, **kwargs): - warnings.warn("UnrolledOptimizer has been renamed to TensorOptimizer." - " The old name may be removed on or after 2019-04-25.") - super(UnrolledOptimizer, self).__init__(*args, **kwargs) - - -class UnrolledGradientDescent(TensorGradientDescent): - def __init__(self, *args, **kwargs): - warnings.warn("UnrolledGradientDescent has been renamed to " - "TensorGradientDescent." - " The old name may be removed on or after 2019-04-25.") - super(UnrolledGradientDescent, self).__init__(*args, **kwargs) - - -class UnrolledAdam(TensorAdam): - def __init__(self, *args, **kwargs): - warnings.warn("UnrolledAdam has been renamed to TensorAdam." - " The old name may be removed on or after 2019-04-25.") - super(UnrolledAdam, self).__init__(*args, **kwargs) - - -def pgd_attack(*args, **kwargs): - warnings.warn("cleverhans.attacks_tf.pgd_attack has been renamed to " - "cleverhans.attacks.projected_optimization. " - "Please switch to the new name. The current name will " - "become unsupport on or after 2019-04-24.") - from cleverhans.attacks import projected_optimization - return projected_optimization(*args, **kwargs) diff --git a/cleverhans/attacks_tfe.py b/cleverhans/attacks_tfe.py deleted file mode 100644 index 76ca2f545..000000000 --- a/cleverhans/attacks_tfe.py +++ /dev/null @@ -1,183 +0,0 @@ -""" -Attacks for TensorFlow Eager -""" -from distutils.version import LooseVersion - -import numpy as np -import tensorflow as tf - -from cleverhans import attacks -from cleverhans import utils -from cleverhans.model import CallableModelWrapper, wrapper_warning -from cleverhans.model import Model -from cleverhans.loss import LossCrossEntropy - -_logger = utils.create_logger("cleverhans.attacks_tfe") - - -if LooseVersion(tf.__version__) < LooseVersion('1.8.0'): - error_msg = ('For eager execution', - 'use Tensorflow version greather than 1.8.0.') - raise ValueError(error_msg) - - -class Attack(attacks.Attack): - """ - Abstract base class for all eager attack classes. - :param model: An instance of the cleverhans.model.Model class. - :param back: The backend to use. Inherited from AttackBase class. - :param dtypestr: datatype of the input data samples and crafted - adversarial attacks. - """ - - def __init__(self, model, dtypestr='float32'): - super(Attack, self).__init__(model, dtypestr=dtypestr) - # Validate the input arguments. - if dtypestr != 'float32' and dtypestr != 'float64': - raise ValueError("Unexpected input for argument dtypestr.") - self.tf_dtype = tf.as_dtype(dtypestr) - self.np_dtype = np.dtype(dtypestr) - - if not isinstance(model, Model): - raise ValueError("The model argument should be an instance of" - " the cleverhans.model.Model class.") - # Prepare attributes - self.model = model - self.dtypestr = dtypestr - - def construct_graph(self, **kwargs): - """ - Constructs the graph required to run the attacks. - Is inherited from the attack class, is overloaded - to raise an error. - """ - error = "This method is not required for eager execution." - raise AttributeError(error) - - def generate_np(self, x_val, **kwargs): - """ - Generate adversarial examples and return them as a NumPy array. - - :param x_val: A NumPy array with the original inputs. - :param **kwargs: optional parameters used by child classes. - :return: A NumPy array holding the adversarial examples. - """ - tfe = tf.contrib.eager - x = tfe.Variable(x_val) - adv_x = self.generate(x, **kwargs) - return adv_x.numpy() - - def construct_variables(self, kwargs): - """ - Construct the inputs to the attack graph. - Is inherited from the attack class, is overloaded - to raise an error. - """ - error = "This method is not required for eager execution." - raise AttributeError(error) - - -class FastGradientMethod(Attack, attacks.FastGradientMethod): - """ - Inherited class from Attack and cleverhans.attacks.FastGradientMethod. - - This attack was originally implemented by Goodfellow et al. (2015) with the - infinity norm (and is known as the "Fast Gradient Sign Method"). This - implementation extends the attack to other norms, and is therefore called - the Fast Gradient Method. - Paper link: https://arxiv.org/abs/1412.6572 - """ - - def __init__(self, model, dtypestr='float32', **kwargs): - """ - Creates a FastGradientMethod instance in eager execution. - :model: cleverhans.model.Model - :dtypestr: datatype in the string format. - """ - del kwargs - if not isinstance(model, Model): - wrapper_warning() - model = CallableModelWrapper(model, 'probs') - - super(FastGradientMethod, self).__init__(model, dtypestr) - - def generate(self, x, **kwargs): - """ - Generates the adversarial sample for the given input. - :param x: The model's inputs. - :param eps: (optional float) attack step size (input variation) - :param ord: (optional) Order of the norm (mimics NumPy). - Possible values: np.inf, 1 or 2. - :param y: (optional) A tf variable` with the model labels. Only provide - this parameter if you'd like to use true labels when crafting - adversarial samples. Otherwise, model predictions are used as - labels to avoid the "label leaking" effect (explained in this - paper: https://arxiv.org/abs/1611.01236). Default is None. - Labels should be one-hot-encoded. - :param y_target: (optional) A tf variable` with the labels to target. - Leave y_target=None if y is also set. - Labels should be one-hot-encoded. - :param clip_min: (optional float) Minimum input component value - :param clip_max: (optional float) Maximum input component value - """ - # Parse and save attack-specific parameters - assert self.parse_params(**kwargs) - labels, _nb_classes = self.get_or_guess_labels(x, kwargs) - return self.fgm(x, labels=labels, targeted=(self.y_target is not None)) - - def fgm(self, x, labels, targeted=False): - """ - TensorFlow Eager implementation of the Fast Gradient Method. - :param x: the input variable - :param targeted: Is the attack targeted or untargeted? Untargeted, the - default, will try to make the label incorrect. - Targeted will instead try to move in the direction - of being more like y. - :return: a tensor for the adversarial example - """ - # Compute loss - with tf.GradientTape() as tape: - # input should be watched because it may be - # combination of trainable and non-trainable variables - tape.watch(x) - loss_obj = LossCrossEntropy(self.model, smoothing=0.) - loss = loss_obj.fprop(x=x, y=labels) - if targeted: - loss = -loss - - # Define gradient of loss wrt input - grad = tape.gradient(loss, x) - optimal_perturbation = attacks.optimize_linear(grad, self.eps, self.ord) - - # Add perturbation to original example to obtain adversarial example - adv_x = x + optimal_perturbation - - # If clipping is needed - # reset all values outside of [clip_min, clip_max] - if (self.clip_min is not None) and (self.clip_max is not None): - adv_x = tf.clip_by_value(adv_x, self.clip_min, self.clip_max) - return adv_x - - -class BasicIterativeMethod(Attack, attacks.BasicIterativeMethod): - """ - Inherited class from Attack and cleverhans.attacks.BasicIterativeMethod. - - The Basic Iterative Method (Kurakin et al. 2016). The original paper used - hard labels for this attack; no label smoothing. - Paper link: https://arxiv.org/pdf/1607.02533.pdf - """ - - FGM_CLASS = FastGradientMethod - - def __init__(self, model, dtypestr='float32'): - """ - Creates a BasicIterativeMethod instance in eager execution. - :param model: cleverhans.model.Model - :param dtypestr: datatype in the string format. - """ - if not isinstance(model, Model): - wrapper_warning() - model = CallableModelWrapper(model, 'probs') - - super(BasicIterativeMethod, self).__init__(model, dtypestr) diff --git a/cleverhans/augmentation.py b/cleverhans/augmentation.py deleted file mode 100644 index fa23a0e28..000000000 --- a/cleverhans/augmentation.py +++ /dev/null @@ -1,58 +0,0 @@ -""" -Dataset augmentation functionality - -NOTE: This module is much more free to change than many other modules -in CleverHans. CleverHans is very conservative about changes to any -code that affects the output of benchmark tests (attacks, evaluation -methods, etc.). This module provides *dataset augmentation* code for -building models to be benchmarked, not *benchmarks,* and -thus is free to change rapidly to provide better speed, accuracy, -etc. -""" - -import tensorflow as tf - -# Convenient renaming of existing function -random_horizontal_flip = tf.image.random_flip_left_right - - -def random_shift(x, pad=(4, 4), mode='REFLECT'): - """Pad a single image and then crop to the original size with a random - offset.""" - assert mode in 'REFLECT SYMMETRIC CONSTANT'.split() - assert x.get_shape().ndims == 3 - xp = tf.pad(x, [[pad[0], pad[0]], [pad[1], pad[1]], [0, 0]], mode) - return tf.random_crop(xp, tf.shape(x)) - - -def batch_augment(x, func, device='/CPU:0'): - """ - Apply dataset augmentation to a batch of exmaples. - :param x: Tensor representing a batch of examples. - :param func: Callable implementing dataset augmentation, operating on - a single image. - :param device: String specifying which device to use. - """ - with tf.device(device): - return tf.map_fn(func, x) - - -def random_crop_and_flip(x, pad_rows=4, pad_cols=4): - """Augment a batch by randomly cropping and horizontally flipping it.""" - rows = tf.shape(x)[1] - cols = tf.shape(x)[2] - channels = x.get_shape()[3] - - def _rand_crop_img(img): - """Randomly crop an individual image""" - return tf.random_crop(img, [rows, cols, channels]) - - # Some of these ops are only on CPU. - # This function will often be called with the device set to GPU. - # We need to set it to CPU temporarily to avoid an exception. - with tf.device('/CPU:0'): - x = tf.image.resize_image_with_crop_or_pad(x, rows + pad_rows, - cols + pad_cols) - x = tf.map_fn(_rand_crop_img, x) - x = tf.image.random_flip_left_right(x) - return x diff --git a/cleverhans/canary.py b/cleverhans/canary.py deleted file mode 100644 index 66fc40c69..000000000 --- a/cleverhans/canary.py +++ /dev/null @@ -1,75 +0,0 @@ -""" -Canary code that dies if the underlying hardware / drivers aren't working right. -""" -import time - -import numpy as np -import tensorflow as tf -from cleverhans.utils_tf import infer_devices - -last_run = None - - -def run_canary(): - """ - Runs some code that will crash if the GPUs / GPU driver are suffering from - a common bug. This helps to prevent contaminating results in the rest of - the library with incorrect calculations. - """ - - # Note: please do not edit this function unless you have access to a machine - # with GPUs suffering from the bug and can verify that the canary still - # crashes after your edits. Due to the transient nature of the GPU bug it is - # not possible to unit test the canary in our continuous integration system. - - global last_run - current = time.time() - if last_run is None or current - last_run > 3600: - last_run = current - else: - # Run the canary at most once per hour - return - - # Try very hard not to let the canary affect the graph for the rest of the - # python process - canary_graph = tf.Graph() - with canary_graph.as_default(): - devices = infer_devices() - num_devices = len(devices) - if num_devices < 3: - # We have never observed GPU failure when less than 3 GPUs were used - return - - v = np.random.RandomState([2018, 10, 16]).randn(2, 2) - # Try very hard not to let this Variable end up in any collections used - # by the rest of the python process - w = tf.Variable(v, trainable=False, collections=[]) - loss = tf.reduce_sum(tf.square(w)) - - grads = [] - for device in devices: - with tf.device(device): - grad, = tf.gradients(loss, w) - grads.append(grad) - - sess = tf.Session() - sess.run(tf.variables_initializer([w])) - grads = sess.run(grads) - first = grads[0] - for grad in grads[1:]: - if not np.allclose(first, grad): - first_string = str(first) - grad_string = str(grad) - raise RuntimeError("Something is wrong with your GPUs or GPU driver." - "%(num_devices)d different GPUS were asked to " - "calculate the same 2x2 gradient. One returned " - "%(first_string)s and another returned " - "%(grad_string)s. This can usually be fixed by " - "rebooting the machine." % - {"num_devices" : num_devices, - "first_string" : first_string, - "grad_string" : grad_string}) - sess.close() - -if __name__ == "__main__": - run_canary() diff --git a/cleverhans/compat.py b/cleverhans/compat.py deleted file mode 100644 index be6c149ab..000000000 --- a/cleverhans/compat.py +++ /dev/null @@ -1,81 +0,0 @@ -""" -Wrapper functions for writing code that is compatible with many versions -of TensorFlow. -""" -import warnings -import tensorflow as tf -# The following 2 imports are not used in this module. They are imported so that users of cleverhans.compat can -# get access to device_lib, app, and flags. A pylint bug makes these imports cause errors when using python3+tf1.8. -# Doing the sanitized import here once makes it possible to do "from cleverhans.compat import flags" throughout the -# library without needing to repeat the pylint boilerplate. -from tensorflow.python.client import device_lib # pylint: disable=no-name-in-module,unused-import -from tensorflow.python.platform import app, flags # pylint: disable=no-name-in-module,unused-import - -def _wrap(f): - """ - Wraps a callable `f` in a function that warns that the function is deprecated. - """ - def wrapper(*args, **kwargs): - """ - Issues a deprecation warning and passes through the arguments. - """ - warnings.warn(str(f) + " is deprecated. Switch to calling the equivalent function in tensorflow. " - " This function was originally needed as a compatibility layer for old versions of tensorflow, " - " but support for those versions has now been dropped.") - return f(*args, **kwargs) - return wrapper - -reduce_sum = _wrap(tf.reduce_sum) -reduce_max = _wrap(tf.reduce_max) -reduce_min = _wrap(tf.reduce_min) -reduce_mean = _wrap(tf.reduce_mean) -reduce_prod = _wrap(tf.reduce_prod) -reduce_any = _wrap(tf.reduce_any) - -def reduce_function(op_func, input_tensor, axis=None, keepdims=None, - name=None, reduction_indices=None): - """ - This function used to be needed to support tf 1.4 and early, but support for tf 1.4 and earlier is now dropped. - :param op_func: expects the function to handle eg: tf.reduce_sum. - :param input_tensor: The tensor to reduce. Should have numeric type. - :param axis: The dimensions to reduce. If None (the default), - reduces all dimensions. Must be in the range - [-rank(input_tensor), rank(input_tensor)). - :param keepdims: If true, retains reduced dimensions with length 1. - :param name: A name for the operation (optional). - :param reduction_indices: The old (deprecated) name for axis. - :return: outputs same value as op_func. - """ - - warnings.warn("`reduce_function` is deprecated and may be removed on or after 2019-09-08.") - - out = op_func(input_tensor, axis=axis, keepdims=keepdims, name=name, reduction_indices=reduction_indices) - - return out - -def softmax_cross_entropy_with_logits(sentinel=None, - labels=None, - logits=None, - dim=-1): - """ - Wrapper around tf.nn.softmax_cross_entropy_with_logits_v2 to handle - deprecated warning - """ - # Make sure that all arguments were passed as named arguments. - if sentinel is not None: - name = "softmax_cross_entropy_with_logits" - raise ValueError("Only call `%s` with " - "named arguments (labels=..., logits=..., ...)" - % name) - if labels is None or logits is None: - raise ValueError("Both labels and logits must be provided.") - - try: - f = tf.nn.softmax_cross_entropy_with_logits_v2 - except AttributeError: - raise RuntimeError("This version of TensorFlow is no longer supported. See cleverhans/README.md") - - labels = tf.stop_gradient(labels) - loss = f(labels=labels, logits=logits, dim=dim) - - return loss diff --git a/cleverhans/confidence_report.py b/cleverhans/confidence_report.py deleted file mode 100644 index 02877eb92..000000000 --- a/cleverhans/confidence_report.py +++ /dev/null @@ -1,404 +0,0 @@ -"""Functionality for making confidence reports. - -A confidence report is a dictionary. -Each dictionary key is the name of a type of data: - clean : Clean data - bundled : bundled adversarial examples -Each value in the dictionary contains an array of bools indicating whether -the model got each example correct and an array containing the confidence -that the model assigned to each prediction. -""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -from collections import OrderedDict -import logging -import time -import warnings - -import numpy as np -import six -import tensorflow as tf - -from cleverhans.attacks import MaxConfidence -from cleverhans.attacks import Semantic -from cleverhans.evaluation import correctness_and_confidence -from cleverhans.evaluation import run_attack -from cleverhans.utils import set_log_level -from cleverhans.serial import load, save -from cleverhans.utils_tf import infer_devices - -# Defaults. Imported elsewhere so that command line script defaults match -# function defaults. -TRAIN_START = 0 -TRAIN_END = 60000 -TEST_START = 0 -TEST_END = 10000 -WHICH_SET = 'test' -RECIPE = 'basic_max_confidence_recipe' -REPORT_PATH = None -# Used for `make_confidence_report` but not `make_confidence_report_bundled` -devices = infer_devices() -num_devices = len(devices) -BATCH_SIZE = 128 * num_devices -MC_BATCH_SIZE = 16 * num_devices -NB_ITER = 40 -BASE_EPS_ITER = None # Differs by dataset -SAVE_ADVX = 1 - - -class ConfidenceReport(OrderedDict): - """ - A data structure reporting how much confidence a model assigned to its - predictions on each example and whether those predictions were correct. - This class is just a dictionary with some type checks. - It maps string data type names (like "clean" for clean data or "Semantic" - for semantic adversarial examples) to ConfidenceReportEntry instances. - - :param iterable: optional iterable containing (key, value) tuples - """ - - def __init__(self, iterable=None): - super(ConfidenceReport, self).__init__() - # This field tracks whether the report is completed. - # It's important e.g. for reports that are made by bundlers and repeatedly - # written to disk during the process. This field makes it possible to tell - # whether a report on disk is complete or whether the bundling process - # got killed (e.g. due to VM migration) - self.completed = False - if iterable is not None: - # pickle sometimes wants to use this interface to unpickle the OrderedDict - for key, value in iterable: - self[key] = value - - def __setitem__(self, key, value): - assert isinstance(key, six.string_types) - if not isinstance(value, ConfidenceReportEntry): - raise TypeError("`value` must be a ConfidenceReportEntry, but got " + str(value) + " of type " + str(type(value))) - super(ConfidenceReport, self).__setitem__(key, value) - -class ConfidenceReportEntry(object): - """ - A data structure reporting how much confidence a model assigned to its - predictions on each example and whether those predictions were correct. - - :param correctness: ndarray, one bool per example indicating whether it was - correct - :param confidence: ndarray, one floating point value per example reporting - the probability assigned to the prediction for that example - """ - def __init__(self, correctness, confidence): - assert isinstance(correctness, np.ndarray) - assert isinstance(correctness, np.ndarray) - assert correctness.ndim == 1 - assert confidence.ndim == 1 - assert correctness.dtype == np.bool, correctness.dtype - assert np.issubdtype(confidence.dtype, np.floating) - assert correctness.shape == confidence.shape - assert confidence.min() >= 0. - assert confidence.max() <= 1. - self.correctness = correctness - self.confidence = confidence - - def __getitem__(self, key): - warnings.warn("Dictionary confidence report entries are deprecated. " - "Switch to accessing the appropriate field of " - "ConfidenceReportEntry. " - "Dictionary-style access will be removed on or after " - "2019-04-24.") - assert key in ['correctness', 'confidence'] - return self.__dict__[key] - - def __setitem__(self, key, value): - warnings.warn("Dictionary confidence report entries are deprecated." - "Switch to accessing the appropriate field of " - "ConfidenceReportEntry. " - "Dictionary-style access will be removed on or after " - "2019-04-24.") - assert key in ['correctness', 'confidence'] - self.__dict__[key] = value - - -def make_confidence_report_bundled(filepath, train_start=TRAIN_START, - train_end=TRAIN_END, test_start=TEST_START, - test_end=TEST_END, which_set=WHICH_SET, - recipe=RECIPE, report_path=REPORT_PATH, - nb_iter=NB_ITER, base_eps=None, - base_eps_iter=None, base_eps_iter_small=None, - batch_size=BATCH_SIZE): - """ - Load a saved model, gather its predictions, and save a confidence report. - :param filepath: path to model to evaluate - :param train_start: index of first training set example to use - :param train_end: index of last training set example to use - :param test_start: index of first test set example to use - :param test_end: index of last test set example to use - :param which_set: 'train' or 'test' - :param nb_iter: int, number of iterations of attack algorithm - (note that different recipes will use this differently, - for example many will run two attacks, one with nb_iter - iterations and one with 25X more) - :param base_eps: float, epsilon parameter for threat model, on a scale of [0, 1]. - Inferred from the dataset if not specified. - :param base_eps_iter: float, a step size used in different ways by different recipes. - Typically the step size for a PGD attack. - Inferred from the dataset if not specified. - :param base_eps_iter_small: float, a second step size for a more fine-grained attack. - Inferred from the dataset if not specified. - :param batch_size: int, batch size - """ - # Avoid circular import - from cleverhans import attack_bundling - if callable(recipe): - run_recipe = recipe - else: - run_recipe = getattr(attack_bundling, recipe) - - # Set logging level to see debug information - set_log_level(logging.INFO) - - # Create TF session - sess = tf.Session() - - assert filepath.endswith('.joblib') - if report_path is None: - report_path = filepath[:-len('.joblib')] + "_bundled_report.joblib" - - with sess.as_default(): - model = load(filepath) - assert len(model.get_params()) > 0 - factory = model.dataset_factory - factory.kwargs['train_start'] = train_start - factory.kwargs['train_end'] = train_end - factory.kwargs['test_start'] = test_start - factory.kwargs['test_end'] = test_end - dataset = factory() - - center = dataset.kwargs['center'] - if 'max_val' in factory.kwargs: - max_value = factory.kwargs['max_val'] - elif hasattr(dataset, 'max_val'): - max_value = dataset.max_val - else: - raise AttributeError("Can't find max_value specification") - min_value = 0. - center * max_value - value_range = max_value - min_value - - if 'CIFAR' in str(factory.cls): - if base_eps is None: - base_eps = 8. / 255. - if base_eps_iter is None: - base_eps_iter = 2. / 255. - if base_eps_iter_small is None: - base_eps_iter_small = 1. / 255. - elif 'MNIST' in str(factory.cls): - if base_eps is None: - base_eps = .3 - if base_eps_iter is None: - base_eps_iter = .1 - base_eps_iter_small = None - else: - # Note that it is not required to specify base_eps_iter_small - if base_eps is None or base_eps_iter is None: - raise NotImplementedError("Not able to infer threat model from " + str(factory.cls)) - - eps = base_eps * value_range - eps_iter = base_eps_iter * value_range - if base_eps_iter_small is None: - eps_iter_small = None - else: - eps_iter_small = base_eps_iter_small * value_range - clip_min = min_value - clip_max = max_value - - x_data, y_data = dataset.get_set(which_set) - assert x_data.max() <= max_value - assert x_data.min() >= min_value - - assert eps_iter <= eps - assert eps_iter_small is None or eps_iter_small <= eps - - # Different recipes take different arguments. - # For now I don't have an idea for a beautiful unifying framework, so - # we get an if statement. - if recipe == 'random_search_max_confidence_recipe': - # pylint always checks against the default recipe here - # pylint: disable=no-value-for-parameter - run_recipe(sess=sess, model=model, x=x_data, y=y_data, eps=eps, - clip_min=clip_min, clip_max=clip_max, report_path=report_path) - else: - run_recipe(sess=sess, model=model, x=x_data, y=y_data, - nb_classes=dataset.NB_CLASSES, eps=eps, clip_min=clip_min, - clip_max=clip_max, eps_iter=eps_iter, nb_iter=nb_iter, - report_path=report_path, eps_iter_small=eps_iter_small, batch_size=batch_size) - - -def print_stats(correctness, confidence, name): - """ - Prints out accuracy, coverage, etc. statistics - :param correctness: ndarray - One bool per example specifying whether it was correctly classified - :param confidence: ndarray - The probability associated with each prediction - :param name: str - The name of this type of data (e.g. "clean", "MaxConfidence") - """ - accuracy = correctness.mean() - wrongness = 1 - correctness - denom1 = np.maximum(1, wrongness.sum()) - ave_prob_on_mistake = (wrongness * confidence).sum() / denom1 - assert ave_prob_on_mistake <= 1., ave_prob_on_mistake - denom2 = np.maximum(1, correctness.sum()) - ave_prob_on_correct = (correctness * confidence).sum() / denom2 - covered = confidence > 0.5 - cov_half = covered.mean() - acc_half = (correctness * covered).sum() / np.maximum(1, covered.sum()) - print('Accuracy on %s examples: %0.4f' % (name, accuracy)) - print("Average prob on mistakes: %0.4f" % ave_prob_on_mistake) - print("Average prob on correct: %0.4f" % ave_prob_on_correct) - print("Accuracy when prob thresholded at .5: %0.4f" % acc_half) - print("Coverage when prob thresholded at .5: %0.4f" % cov_half) - - success_rate = acc_half * cov_half - # Success is correctly classifying a covered example - print("Success rate at .5: %0.4f" % success_rate) - # Failure is misclassifying a covered example - failure_rate = (1. - acc_half) * cov_half - print("Failure rate at .5: %0.4f" % failure_rate) - print() - - -def make_confidence_report(filepath, train_start=TRAIN_START, - train_end=TRAIN_END, - test_start=TEST_START, test_end=TEST_END, - batch_size=BATCH_SIZE, which_set=WHICH_SET, - mc_batch_size=MC_BATCH_SIZE, - report_path=REPORT_PATH, - base_eps_iter=BASE_EPS_ITER, - nb_iter=NB_ITER, save_advx=SAVE_ADVX): - """ - Load a saved model, gather its predictions, and save a confidence report. - - - This function works by running a single MaxConfidence attack on each example. - This provides a reasonable estimate of the true failure rate quickly, so - long as the model does not suffer from gradient masking. - However, this estimate is mostly intended for development work and not - for publication. A more accurate estimate may be obtained by running - make_confidence_report_bundled.py instead. - - :param filepath: path to model to evaluate - :param train_start: index of first training set example to use - :param train_end: index of last training set example to use - :param test_start: index of first test set example to use - :param test_end: index of last test set example to use - :param batch_size: size of evaluation batches - :param which_set: 'train' or 'test' - :param mc_batch_size: batch size for MaxConfidence attack - :param base_eps_iter: step size if the data were in [0,1] - (Step size will be rescaled proportional to the actual data range) - :param nb_iter: Number of iterations of PGD to run per class - :param save_advx: bool. If True, saves the adversarial examples to disk. - On by default, but can be turned off to save memory, etc. - """ - - # Set TF random seed to improve reproducibility - tf.set_random_seed(1234) - - # Set logging level to see debug information - set_log_level(logging.INFO) - - # Create TF session - sess = tf.Session() - - if report_path is None: - assert filepath.endswith('.joblib') - report_path = filepath[:-len('.joblib')] + "_report.joblib" - - with sess.as_default(): - model = load(filepath) - assert len(model.get_params()) > 0 - factory = model.dataset_factory - factory.kwargs['train_start'] = train_start - factory.kwargs['train_end'] = train_end - factory.kwargs['test_start'] = test_start - factory.kwargs['test_end'] = test_end - dataset = factory() - - center = dataset.kwargs['center'] - max_val = dataset.kwargs['max_val'] - value_range = max_val * (1. + center) - min_value = 0. - center * max_val - - if 'CIFAR' in str(factory.cls): - base_eps = 8. / 255. - if base_eps_iter is None: - base_eps_iter = 2. / 255. - elif 'MNIST' in str(factory.cls): - base_eps = .3 - if base_eps_iter is None: - base_eps_iter = .1 - else: - raise NotImplementedError(str(factory.cls)) - - mc_params = {'eps': base_eps * value_range, - 'eps_iter': base_eps_iter * value_range, - 'nb_iter': nb_iter, - 'clip_min': min_value, - 'clip_max': max_val} - - x_data, y_data = dataset.get_set(which_set) - - report = ConfidenceReport() - - semantic = Semantic(model, center, max_val, sess) - mc = MaxConfidence(model, sess=sess) - - jobs = [('clean', None, None, None, False), - ('Semantic', semantic, None, None, False), - ('mc', mc, mc_params, mc_batch_size, True)] - - for job in jobs: - name, attack, attack_params, job_batch_size, save_this_job = job - if job_batch_size is None: - job_batch_size = batch_size - t1 = time.time() - if save_advx and save_this_job: - # If we want to save the adversarial examples to the filesystem, we need - # to fetch all of them. Otherwise they're just computed one batch at a - # time and discarded - - # The path to save to - assert report_path.endswith('.joblib') - advx_path = report_path[:-len('.joblib')] + '_advx_' + name + '.npy' - - # Fetch the adversarial examples - x_data = run_attack(sess, model, x_data, y_data, attack, attack_params, - batch_size=job_batch_size, devices=devices) - - # Turn off the attack so `correctness_and_confidence` won't run it a - # second time. - attack = None - attack_params = None - - # Save the adversarial examples - np.save(advx_path, x_data) - - # Run correctness and confidence evaluation on adversarial examples - packed = correctness_and_confidence(sess, model, x_data, y_data, - batch_size=job_batch_size, - devices=devices, - attack=attack, - attack_params=attack_params) - t2 = time.time() - print("Evaluation took", t2 - t1, "seconds") - correctness, confidence = packed - - report[name] = ConfidenceReportEntry(correctness=correctness, - confidence=confidence) - - print_stats(correctness, confidence, name) - - save(report_path, report) diff --git a/cleverhans/dataset.py b/cleverhans/dataset.py deleted file mode 100644 index 75e19cd2a..000000000 --- a/cleverhans/dataset.py +++ /dev/null @@ -1,307 +0,0 @@ -"""Dataset class for CleverHans - -""" -# pylint: disable=missing-docstring - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - -import array -import functools -import gzip -import operator -import os -import struct -import tempfile -import sys -import warnings - -import numpy as np -import tensorflow as tf -try: - from tensorflow.python.keras.utils import np_utils - from tensorflow.keras.datasets import cifar10 -except ImportError: - # In tf 1.8, np_utils doesn't seem to be publicly exposed. - # In later tf versions, it is, and in pre-tf keras it was too. - from tensorflow.python.keras import _impl - np_utils = _impl.keras.utils.np_utils - # In tf 1.8, "from tensorflow.keras.datasets import cifar10" doesn't work even though the module exists - cifar10 = tf.keras.datasets.cifar10 - warnings.warn("Support for TensorFlow versions prior to 1.12 is deprecated." - " CleverHans using earlier versions may quit working on or after 2019-07-07.") -from cleverhans import utils - - - -class Dataset(object): - """Abstract base class representing a dataset. - """ - - # The number of classes in the dataset. Should be specified by subclasses. - NB_CLASSES = None - - def __init__(self, kwargs=None): - if kwargs is None: - kwargs = {} - if "self" in kwargs: - del kwargs["self"] - self.kwargs = kwargs - - def get_factory(self): - """Returns a picklable callable that recreates the dataset. - """ - - return Factory(type(self), self.kwargs) - - def get_set(self, which_set): - """Returns the training set or test set as an (x_data, y_data) tuple. - :param which_set: 'train' or 'test' - """ - return (getattr(self, 'x_' + which_set), - getattr(self, 'y_' + which_set)) - - def to_tensorflow(self): - raise NotImplementedError() - - @classmethod - def in_memory_dataset(cls, x, y, shuffle=None, repeat=True): - assert x.shape[0] == y.shape[0] - d = tf.data.Dataset.range(x.shape[0]) - if repeat: - d = d.repeat() - if shuffle: - d = d.shuffle(shuffle) - - def lookup(p): - return x[p], y[p] - d = d.map(lambda i: tf.py_func(lookup, [i], [tf.float32] * 2)) - return d - - -class MNIST(Dataset): - """The MNIST dataset""" - - NB_CLASSES = 10 - - def __init__(self, train_start=0, train_end=60000, test_start=0, - test_end=10000, center=False, max_val=1.): - kwargs = locals() - if '__class__' in kwargs: - del kwargs['__class__'] - super(MNIST, self).__init__(kwargs) - x_train, y_train, x_test, y_test = data_mnist(train_start=train_start, - train_end=train_end, - test_start=test_start, - test_end=test_end) - - if center: - x_train = x_train * 2. - 1. - x_test = x_test * 2. - 1. - x_train *= max_val - x_test *= max_val - - self.x_train = x_train.astype('float32') - self.y_train = y_train.astype('float32') - self.x_test = x_test.astype('float32') - self.y_test = y_test.astype('float32') - - def to_tensorflow(self, shuffle=4096): - return (self.in_memory_dataset(self.x_train, self.y_train, shuffle), - self.in_memory_dataset(self.x_test, self.y_test, repeat=False)) - - -class CIFAR10(Dataset): - """The CIFAR-10 dataset""" - - NB_CLASSES = 10 - - LABEL_NAMES = ["airplane", "automobile", "bird", "cat", "deer", "dog", - "frog", "horse", "ship", "truck"] - - def __init__(self, train_start=0, train_end=60000, test_start=0, - test_end=10000, center=False, max_val=1.): - kwargs = locals() - if '__class__' in kwargs: - del kwargs['__class__'] - super(CIFAR10, self).__init__(kwargs) - packed = data_cifar10(train_start=train_start, - train_end=train_end, - test_start=test_start, - test_end=test_end) - x_train, y_train, x_test, y_test = packed - - if center: - x_train = x_train * 2. - 1. - x_test = x_test * 2. - 1. - x_train *= max_val - x_test *= max_val - - self.x_train = x_train - self.y_train = y_train - self.x_test = x_test - self.y_test = y_test - self.max_val = max_val - - def to_tensorflow(self, shuffle=4096): - # This is much more efficient with data augmentation, see tutorials. - return (self.in_memory_dataset(self.x_train, self.y_train, shuffle), - self.in_memory_dataset(self.x_test, self.y_test, repeat=False)) - - -class Factory(object): - """ - A callable that creates an object of the specified type and configuration. - """ - - def __init__(self, cls, kwargs): - self.cls = cls - self.kwargs = kwargs - - def __call__(self): - """Returns the created object. - """ - return self.cls(**self.kwargs) - - -def maybe_download_file(url, datadir=None, force=False): - try: - from urllib.request import urlretrieve - except ImportError: - from urllib import urlretrieve - - if not datadir: - datadir = tempfile.gettempdir() - file_name = url[url.rfind("/")+1:] - dest_file = os.path.join(datadir, file_name) - - isfile = os.path.isfile(dest_file) - - if force or not isfile: - urlretrieve(url, dest_file) - return dest_file - - -def download_and_parse_mnist_file(file_name, datadir=None, force=False): - url = os.path.join('http://yann.lecun.com/exdb/mnist/', file_name) - file_name = maybe_download_file(url, datadir=datadir, force=force) - - # Open the file and unzip it if necessary - if os.path.splitext(file_name)[1] == '.gz': - open_fn = gzip.open - else: - open_fn = open - - # Parse the file - with open_fn(file_name, 'rb') as file_descriptor: - header = file_descriptor.read(4) - assert len(header) == 4 - - zeros, data_type, n_dims = struct.unpack('>HBB', header) - assert zeros == 0 - - hex_to_data_type = { - 0x08: 'B', - 0x09: 'b', - 0x0b: 'h', - 0x0c: 'i', - 0x0d: 'f', - 0x0e: 'd'} - data_type = hex_to_data_type[data_type] - - # data_type unicode to ascii conversion (Python2 fix) - if sys.version_info[0] < 3: - data_type = data_type.encode('ascii', 'ignore') - - dim_sizes = struct.unpack( - '>' + 'I' * n_dims, - file_descriptor.read(4 * n_dims)) - - data = array.array(data_type, file_descriptor.read()) - data.byteswap() - - desired_items = functools.reduce(operator.mul, dim_sizes) - assert len(data) == desired_items - return np.array(data).reshape(dim_sizes) - - -def data_mnist(datadir=tempfile.gettempdir(), train_start=0, - train_end=60000, test_start=0, test_end=10000): - """ - Load and preprocess MNIST dataset - :param datadir: path to folder where data should be stored - :param train_start: index of first training set example - :param train_end: index of last training set example - :param test_start: index of first test set example - :param test_end: index of last test set example - :return: tuple of four arrays containing training data, training labels, - testing data and testing labels. - """ - assert isinstance(train_start, int) - assert isinstance(train_end, int) - assert isinstance(test_start, int) - assert isinstance(test_end, int) - - X_train = download_and_parse_mnist_file( - 'train-images-idx3-ubyte.gz', datadir=datadir) / 255. - Y_train = download_and_parse_mnist_file( - 'train-labels-idx1-ubyte.gz', datadir=datadir) - X_test = download_and_parse_mnist_file( - 't10k-images-idx3-ubyte.gz', datadir=datadir) / 255. - Y_test = download_and_parse_mnist_file( - 't10k-labels-idx1-ubyte.gz', datadir=datadir) - - X_train = np.expand_dims(X_train, -1) - X_test = np.expand_dims(X_test, -1) - - X_train = X_train[train_start:train_end] - Y_train = Y_train[train_start:train_end] - X_test = X_test[test_start:test_end] - Y_test = Y_test[test_start:test_end] - - Y_train = utils.to_categorical(Y_train, nb_classes=10) - Y_test = utils.to_categorical(Y_test, nb_classes=10) - return X_train, Y_train, X_test, Y_test - - -def data_cifar10(train_start=0, train_end=50000, test_start=0, test_end=10000): - """ - Preprocess CIFAR10 dataset - :return: - """ - - - # These values are specific to CIFAR10 - img_rows = 32 - img_cols = 32 - nb_classes = 10 - - # the data, shuffled and split between train and test sets - (x_train, y_train), (x_test, y_test) = cifar10.load_data() - - if tf.keras.backend.image_data_format() == 'channels_first': - x_train = x_train.reshape(x_train.shape[0], 3, img_rows, img_cols) - x_test = x_test.reshape(x_test.shape[0], 3, img_rows, img_cols) - else: - x_train = x_train.reshape(x_train.shape[0], img_rows, img_cols, 3) - x_test = x_test.reshape(x_test.shape[0], img_rows, img_cols, 3) - x_train = x_train.astype('float32') - x_test = x_test.astype('float32') - x_train /= 255 - x_test /= 255 - print('x_train shape:', x_train.shape) - print(x_train.shape[0], 'train samples') - print(x_test.shape[0], 'test samples') - - # convert class vectors to binary class matrices - y_train = np_utils.to_categorical(y_train, nb_classes) - y_test = np_utils.to_categorical(y_test, nb_classes) - - x_train = x_train[train_start:train_end, :, :, :] - y_train = y_train[train_start:train_end, :] - x_test = x_test[test_start:test_end, :] - y_test = y_test[test_start:test_end, :] - - return x_train, y_train, x_test, y_test diff --git a/cleverhans/devtools/autopep8_all.py b/cleverhans/devtools/autopep8_all.py index 555f42e12..74f54e39c 100644 --- a/cleverhans/devtools/autopep8_all.py +++ b/cleverhans/devtools/autopep8_all.py @@ -12,5 +12,5 @@ for f in list_files(".py"): - command = ["autopep8", "-i", "--indent-size", "2", f] - shell_call(command) + command = ["autopep8", "-i", "--indent-size", "2", f] + shell_call(command) diff --git a/cleverhans/devtools/checks.py b/cleverhans/devtools/checks.py index c6887e108..14c189f1d 100644 --- a/cleverhans/devtools/checks.py +++ b/cleverhans/devtools/checks.py @@ -13,19 +13,20 @@ import numpy as np + class CleverHansTest(unittest.TestCase): - """TestCase with some extra features""" + """TestCase with some extra features""" - def setUp(self): - self.test_start = time.time() - # seed the randomness - np.random.seed(1234) + def setUp(self): + self.test_start = time.time() + # seed the randomness + np.random.seed(1234) - def tearDown(self): - print(self.id(), "took", time.time() - self.test_start, "seconds") + def tearDown(self): + print(self.id(), "took", time.time() - self.test_start, "seconds") - def assertClose(self, x, y, *args, **kwargs): - """Assert that `x` and `y` have close to the same value""" - # self.assertTrue(np.allclose(x, y)) doesn't give a useful message - # on failure - assert np.allclose(x, y, *args, **kwargs), (x, y) + def assertClose(self, x, y, *args, **kwargs): + """Assert that `x` and `y` have close to the same value""" + # self.assertTrue(np.allclose(x, y)) doesn't give a useful message + # on failure + assert np.allclose(x, y, *args, **kwargs), (x, y) diff --git a/cleverhans/devtools/list_files.py b/cleverhans/devtools/list_files.py index aac84e3d5..121352bd5 100644 --- a/cleverhans/devtools/list_files.py +++ b/cleverhans/devtools/list_files.py @@ -4,68 +4,74 @@ def list_files(suffix=""): - """ - Returns a list of all files in CleverHans with the given suffix. + """ + Returns a list of all files in CleverHans with the given suffix. - Parameters - ---------- - suffix : str + Parameters + ---------- + suffix : str - Returns - ------- + Returns + ------- - file_list : list - A list of all files in CleverHans whose filepath ends with `suffix`. - """ + file_list : list + A list of all files in CleverHans whose filepath ends with `suffix`. + """ - cleverhans_path = os.path.abspath(cleverhans.__path__[0]) - # In some environments cleverhans_path does not point to a real directory. - # In such case return empty list. - if not os.path.isdir(cleverhans_path): - return [] - repo_path = os.path.abspath(os.path.join(cleverhans_path, os.pardir)) - file_list = _list_files(cleverhans_path, suffix) + cleverhans_path = os.path.abspath(cleverhans.__path__[0]) + # In some environments cleverhans_path does not point to a real directory. + # In such case return empty list. + if not os.path.isdir(cleverhans_path): + return [] + repo_path = os.path.abspath(os.path.join(cleverhans_path, os.pardir)) + file_list = _list_files(cleverhans_path, suffix) - extra_dirs = ['cleverhans_tutorials', 'examples', 'scripts', 'tests_tf', 'tests_pytorch'] + extra_dirs = [ + "cleverhans_tutorials", + "examples", + "scripts", + "tests_tf", + "tests_pytorch", + ] - for extra_dir in extra_dirs: - extra_path = os.path.join(repo_path, extra_dir) - if os.path.isdir(extra_path): - extra_files = _list_files(extra_path, suffix) - extra_files = [os.path.join(os.pardir, path) for path in extra_files] - file_list = file_list + extra_files + for extra_dir in extra_dirs: + extra_path = os.path.join(repo_path, extra_dir) + if os.path.isdir(extra_path): + extra_files = _list_files(extra_path, suffix) + extra_files = [os.path.join(os.pardir, path) for path in extra_files] + file_list = file_list + extra_files - return file_list + return file_list def _list_files(path, suffix=""): - """ - Returns a list of all files ending in `suffix` contained within `path`. + """ + Returns a list of all files ending in `suffix` contained within `path`. - Parameters - ---------- - path : str - a filepath - suffix : str + Parameters + ---------- + path : str + a filepath + suffix : str - Returns - ------- - l : list - A list of all files ending in `suffix` contained within `path`. - (If `path` is a file rather than a directory, it is considered - to "contain" itself) - """ - if os.path.isdir(path): - incomplete = os.listdir(path) - complete = [os.path.join(path, entry) for entry in incomplete] - lists = [_list_files(subpath, suffix) for subpath in complete] - flattened = [] - for one_list in lists: - for elem in one_list: - flattened.append(elem) - return flattened - else: - assert os.path.exists(path), "couldn't find file '%s'" % path - if path.endswith(suffix): - return [path] - return [] + Returns + ------- + l : list + A list of all files ending in `suffix` contained within `path`. + (If `path` is a file rather than a directory, it is considered + to "contain" itself) + """ + if os.path.isdir(path): + incomplete = os.listdir(path) + complete = [os.path.join(path, entry) for entry in incomplete] + lists = [_list_files(subpath, suffix) for subpath in complete] + flattened = [] + for one_list in lists: + for elem in one_list: + flattened.append(elem) + return flattened + else: + assert os.path.exists(path), "couldn't find file '%s'" % path + if path.endswith(suffix): + return [path] + return [] diff --git a/cleverhans/devtools/mocks.py b/cleverhans/devtools/mocks.py index 8e906fbf4..0556a70b3 100644 --- a/cleverhans/devtools/mocks.py +++ b/cleverhans/devtools/mocks.py @@ -14,52 +14,70 @@ def random_feed_dict(rng, placeholders): - """ - Returns random data to be used with `feed_dict`. - :param rng: A numpy.random.RandomState instance - :param placeholders: List of tensorflow placeholders - :return: A dict mapping placeholders to random numpy values - """ + """ + Returns random data to be used with `feed_dict`. + :param rng: A numpy.random.RandomState instance + :param placeholders: List of tensorflow placeholders + :return: A dict mapping placeholders to random numpy values + """ - output = {} + output = {} - for placeholder in placeholders: - if placeholder.dtype != 'float32': - raise NotImplementedError() - value = rng.randn(*placeholder.shape).astype('float32') - output[placeholder] = value + for placeholder in placeholders: + if placeholder.dtype != "float32": + raise NotImplementedError() + value = rng.randn(*placeholder.shape).astype("float32") + output[placeholder] = value + + return output - return output class SimpleDataset(Dataset): - """ - A dataset containing random values. - Values are uniformly distributed, either in [0, max_val] or [-1, max_val]. - """ + """ + A dataset containing random values. + Values are uniformly distributed, either in [0, max_val] or [-1, max_val]. + """ - def __init__(self, dim=2, train_start=0, train_end=3, test_start=0, test_end=5, center=False, max_val=1., - nb_classes=5): - kwargs = copy.copy(locals()) - del kwargs['self'] - if "__class__" in kwargs: - del kwargs["__class__"] - super(SimpleDataset, self).__init__(kwargs) - self.__dict__.update(kwargs) - train_x_rng = np.random.RandomState([2018, 11, 9, 1]) - # Even if train_start is not 0, we should still generate the first training examples from the rng. - # This way the dataset looks like it is an array of deterministic data that we index using train_start. - self.x_train = train_x_rng.uniform(- center * max_val, max_val, (train_end, dim))[train_start:] - # Use a second rng for the test set so that it also looks like an array of deterministic data that we - # index into, unaffected by the number of training examples. - test_x_rng = np.random.RandomState([2018, 11, 9, 2]) - self.x_test = test_x_rng.uniform(- center * max_val, max_val, (test_end, dim))[test_start:] - # Likewise, to keep the number of examples read from the rng affecting the values of the labels, we - # must generate the labels from a different rng - train_y_rng = np.random.RandomState([2018, 11, 9, 3]) - self.y_train = train_y_rng.randint(low=0, high=nb_classes, size=(train_end, 1))[train_start:] - test_y_rng = np.random.RandomState([2018, 11, 9, 4]) - self.y_test = test_y_rng.randint(low=0, high=nb_classes, size=(test_end, 1))[test_start:] - assert self.x_train.shape[0] == self.y_train.shape[0] - assert self.x_test.shape[0] == self.y_test.shape[0] - self.y_train = np_utils.to_categorical(self.y_train, nb_classes) - self.y_test = np_utils.to_categorical(self.y_test, nb_classes) + def __init__( + self, + dim=2, + train_start=0, + train_end=3, + test_start=0, + test_end=5, + center=False, + max_val=1.0, + nb_classes=5, + ): + kwargs = copy.copy(locals()) + del kwargs["self"] + if "__class__" in kwargs: + del kwargs["__class__"] + super(SimpleDataset, self).__init__(kwargs) + self.__dict__.update(kwargs) + train_x_rng = np.random.RandomState([2018, 11, 9, 1]) + # Even if train_start is not 0, we should still generate the first training examples from the rng. + # This way the dataset looks like it is an array of deterministic data that we index using train_start. + self.x_train = train_x_rng.uniform( + -center * max_val, max_val, (train_end, dim) + )[train_start:] + # Use a second rng for the test set so that it also looks like an array of deterministic data that we + # index into, unaffected by the number of training examples. + test_x_rng = np.random.RandomState([2018, 11, 9, 2]) + self.x_test = test_x_rng.uniform(-center * max_val, max_val, (test_end, dim))[ + test_start: + ] + # Likewise, to keep the number of examples read from the rng affecting the values of the labels, we + # must generate the labels from a different rng + train_y_rng = np.random.RandomState([2018, 11, 9, 3]) + self.y_train = train_y_rng.randint(low=0, high=nb_classes, size=(train_end, 1))[ + train_start: + ] + test_y_rng = np.random.RandomState([2018, 11, 9, 4]) + self.y_test = test_y_rng.randint(low=0, high=nb_classes, size=(test_end, 1))[ + test_start: + ] + assert self.x_train.shape[0] == self.y_train.shape[0] + assert self.x_test.shape[0] == self.y_test.shape[0] + self.y_train = np_utils.to_categorical(self.y_train, nb_classes) + self.y_test = np_utils.to_categorical(self.y_test, nb_classes) diff --git a/cleverhans/devtools/tests/test_format.py b/cleverhans/devtools/tests/test_format.py index 7d3abd51f..07a2d69e9 100644 --- a/cleverhans/devtools/tests/test_format.py +++ b/cleverhans/devtools/tests/test_format.py @@ -18,98 +18,123 @@ "../examples/multigpu_advtrain/test_attack_multigpu.py" ] -all_py_files = list_files('.py') +all_py_files = list_files(".py") + def update_whitelist(): - """Add files to the whitelist""" - global whitelist_pep8 - # We don't want to test RL-attack because it has so many dependencies - # not used elsewhere, and pylint wants to import them all - whitelist_pep8.extend([os.path.relpath(path, cleverhans.__path__[0]) - for path in all_py_files if "RL-attack" in path]) - # Similarly, we don't want to require robust_vision_benchmark installed - whitelist_pep8.extend([os.path.relpath(path, cleverhans.__path__[0]) - for path in all_py_files - if "robust_vision_benchmark" in path]) - # Similarly, we don't want to require that cloud be installed - whitelist_pep8.extend([os.path.relpath(path, cleverhans.__path__[0]) - for path in all_py_files - if "cloud_client" in path]) - # This example has more dependencies too - whitelist_pep8.extend([os.path.relpath(path, cleverhans.__path__[0]) - for path in all_py_files - if "facenet_adversarial_faces" in path]) - # This too - whitelist_pep8.extend([os.path.relpath(path, cleverhans.__path__[0]) - for path in all_py_files - if "madry_lab_challenges" in path]) - # This code is no longer compatible with new versions of cleverhans / scipy and will be removed soon - whitelist_pep8.extend([os.path.relpath(path, cleverhans.__path__[0]) - for path in all_py_files - if "nips17_adversarial_competition" in path]) + """Add files to the whitelist""" + global whitelist_pep8 + # We don't want to test RL-attack because it has so many dependencies + # not used elsewhere, and pylint wants to import them all + whitelist_pep8.extend( + [ + os.path.relpath(path, cleverhans.__path__[0]) + for path in all_py_files + if "RL-attack" in path + ] + ) + # Similarly, we don't want to require robust_vision_benchmark installed + whitelist_pep8.extend( + [ + os.path.relpath(path, cleverhans.__path__[0]) + for path in all_py_files + if "robust_vision_benchmark" in path + ] + ) + # Similarly, we don't want to require that cloud be installed + whitelist_pep8.extend( + [ + os.path.relpath(path, cleverhans.__path__[0]) + for path in all_py_files + if "cloud_client" in path + ] + ) + # This example has more dependencies too + whitelist_pep8.extend( + [ + os.path.relpath(path, cleverhans.__path__[0]) + for path in all_py_files + if "facenet_adversarial_faces" in path + ] + ) + # This too + whitelist_pep8.extend( + [ + os.path.relpath(path, cleverhans.__path__[0]) + for path in all_py_files + if "madry_lab_challenges" in path + ] + ) + # This code is no longer compatible with new versions of cleverhans / scipy and will be removed soon + whitelist_pep8.extend( + [ + os.path.relpath(path, cleverhans.__path__[0]) + for path in all_py_files + if "nips17_adversarial_competition" in path + ] + ) update_whitelist() -whitelist_docstrings = [ -] +whitelist_docstrings = [] def test_format_pep8(): - """ - Test if pep8 is respected. - """ - files_to_check = [] - module_dir = cleverhans.__path__[0] - for path in all_py_files: - rel_path = os.path.relpath(path, module_dir) - if rel_path in whitelist_pep8: - continue - else: - files_to_check.append(path) - repo_dir = os.path.join(module_dir, os.pardir) - rcpath = os.path.join(repo_dir, '.pylintrc') - assert os.path.exists(rcpath) - - # We must run pylint via the command line and subprocess because of - # problems with the pylint module. - # The documentation claims you can run it as a python module, but - # the documentation is wrong: https://github.com/PyCQA/pylint/issues/1870 - # If you run the version described in the linked issue, pylint - # calls sys.exit once it is done, so it kills the test. - - # Running all files in one pylint command is important for 2 reasons: - # 1) Correctness: pylint can detect issues that require access to multiple - # files, such as cyclic imports - # 2) Speed: pylint imports modules for deep analysis, so if you run - # multiple subprocesses each needs to re-import tensorflow. - # On Ian's laptop, pylint takes about 10s per file to run on the repo, - # and there are about 90 files as of the writing of this comment. - # Running pylint on all files simultaneously takes about 70s, so it - # is a little better than a 10X speedup. - - # Running multiple jobs in parallel helps but far less than linearly. - # On Ian's 4-core laptop, running 4 jobs drops the runtime from 70s - # to 45s. - # Some of the work is I/O, so it actually makes some sense to run - # more jobs than cores. On Ian's 4-core laptop, running 8 jobs drops - # the runtime to 40s. - # There's a further complication though: I think each job needs to - # redo imports, so the total amount of work to do increases with - # the number of jobs. On Ian's laptop, using 64 jobs causes the - # runtime to increase to 220s. There is not an obvious simple - # formula like "use one job per CPU core" or "use way more jobs - # than cores to saturate I/O". For now I'm hoping that 8 will be - # a reasonable default: it gets good performance on my laptop, - # and on machines with fewer than 4 cores there should still be - # a benefit to not being blocked on I/O. - - try: - shell_call(['pylint', '--rcfile', rcpath, '--jobs', '8'] + files_to_check) - except subprocess.CalledProcessError as e: - raise ValueError(e.output.decode("utf-8")) + """ + Test if pep8 is respected. + """ + files_to_check = [] + module_dir = cleverhans.__path__[0] + for path in all_py_files: + rel_path = os.path.relpath(path, module_dir) + if rel_path in whitelist_pep8: + continue + else: + files_to_check.append(path) + repo_dir = os.path.join(module_dir, os.pardir) + rcpath = os.path.join(repo_dir, ".pylintrc") + assert os.path.exists(rcpath) + + # We must run pylint via the command line and subprocess because of + # problems with the pylint module. + # The documentation claims you can run it as a python module, but + # the documentation is wrong: https://github.com/PyCQA/pylint/issues/1870 + # If you run the version described in the linked issue, pylint + # calls sys.exit once it is done, so it kills the test. + + # Running all files in one pylint command is important for 2 reasons: + # 1) Correctness: pylint can detect issues that require access to multiple + # files, such as cyclic imports + # 2) Speed: pylint imports modules for deep analysis, so if you run + # multiple subprocesses each needs to re-import tensorflow. + # On Ian's laptop, pylint takes about 10s per file to run on the repo, + # and there are about 90 files as of the writing of this comment. + # Running pylint on all files simultaneously takes about 70s, so it + # is a little better than a 10X speedup. + + # Running multiple jobs in parallel helps but far less than linearly. + # On Ian's 4-core laptop, running 4 jobs drops the runtime from 70s + # to 45s. + # Some of the work is I/O, so it actually makes some sense to run + # more jobs than cores. On Ian's 4-core laptop, running 8 jobs drops + # the runtime to 40s. + # There's a further complication though: I think each job needs to + # redo imports, so the total amount of work to do increases with + # the number of jobs. On Ian's laptop, using 64 jobs causes the + # runtime to increase to 220s. There is not an obvious simple + # formula like "use one job per CPU core" or "use way more jobs + # than cores to saturate I/O". For now I'm hoping that 8 will be + # a reasonable default: it gets good performance on my laptop, + # and on machines with fewer than 4 cores there should still be + # a benefit to not being blocked on I/O. + + try: + shell_call(["pylint", "--rcfile", rcpath, "--jobs", "8"] + files_to_check) + except subprocess.CalledProcessError as e: + raise ValueError(e.output.decode("utf-8")) if __name__ == "__main__": - test_format_pep8() + test_format_pep8() diff --git a/cleverhans/devtools/version.py b/cleverhans/devtools/version.py index bda88943e..22b5e8e89 100644 --- a/cleverhans/devtools/version.py +++ b/cleverhans/devtools/version.py @@ -9,28 +9,28 @@ def dev_version(): - """ - Returns a hexdigest of all the python files in the module. - """ + """ + Returns a hexdigest of all the python files in the module. + """ - md5_hash = hashlib.md5() - py_files = sorted(list_files(suffix=".py")) - if not py_files: - return '' - for filename in py_files: - with open(filename, 'rb') as fobj: - content = fobj.read() - md5_hash.update(content) - return md5_hash.hexdigest() + md5_hash = hashlib.md5() + py_files = sorted(list_files(suffix=".py")) + if not py_files: + return "" + for filename in py_files: + with open(filename, "rb") as fobj: + content = fobj.read() + md5_hash.update(content) + return md5_hash.hexdigest() def append_dev_version(release_version): - """ - If dev version is not empty appends it to release_version. - """ + """ + If dev version is not empty appends it to release_version. + """ - dev_version_value = dev_version() - if dev_version_value: - return release_version + '-' + dev_version_value - else: - return release_version + dev_version_value = dev_version() + if dev_version_value: + return release_version + "-" + dev_version_value + else: + return release_version diff --git a/cleverhans/evaluation.py b/cleverhans/evaluation.py deleted file mode 100644 index b065643d5..000000000 --- a/cleverhans/evaluation.py +++ /dev/null @@ -1,732 +0,0 @@ -""" -Functionality for evaluating expressions across entire datasets. -Includes multi-GPU support for fast evaluation. -""" - -from distutils.version import LooseVersion -import warnings -import numpy as np -from six.moves import range -import tensorflow as tf - -import cleverhans -from cleverhans import canary -from cleverhans.utils import create_logger -from cleverhans.utils_tf import infer_devices - - -def accuracy(sess, model, x, y, batch_size=None, devices=None, feed=None, - attack=None, attack_params=None): - """ - Compute the accuracy of a TF model on some data - :param sess: TF session to use when training the graph - :param model: cleverhans.model.Model instance - :param x: numpy array containing input examples (e.g. MNIST().x_test ) - :param y: numpy array containing example labels (e.g. MNIST().y_test ) - :param batch_size: Number of examples to use in a single evaluation batch. - If not specified, this function will use a reasonable guess and - may run out of memory. - When choosing the batch size, keep in mind that the batch will - be divided up evenly among available devices. If you can fit 128 - examples in memory on one GPU and you have 8 GPUs, you probably - want to use a batch size of 1024 (unless a different batch size - runs faster with the ops you are using, etc.) - :param devices: An optional list of string device names to use. - If not specified, this function will use all visible GPUs. - :param feed: An optional dictionary that is appended to the feeding - dictionary before the session runs. Can be used to feed - the learning phase of a Keras model for instance. - :param attack: cleverhans.attack.Attack - Optional. If no attack specified, evaluates the model on clean data. - If attack is specified, evaluates the model on adversarial examples - created by the attack. - :param attack_params: dictionary - If attack is specified, this dictionary is passed to attack.generate - as keyword arguments. - :return: a float with the accuracy value - """ - - _check_x(x) - _check_y(y) - if x.shape[0] != y.shape[0]: - raise ValueError("Number of input examples and labels do not match.") - - factory = _CorrectFactory(model, attack, attack_params) - - correct, = batch_eval_multi_worker(sess, factory, [x, y], - batch_size=batch_size, devices=devices, - feed=feed) - - return correct.mean() - - -def class_and_confidence(sess, model, x, y=None, batch_size=None, - devices=None, feed=None, attack=None, - attack_params=None): - """ - Return the model's classification of the input data, and the confidence - (probability) assigned to each example. - :param sess: tf.Session - :param model: cleverhans.model.Model - :param x: numpy array containing input examples (e.g. MNIST().x_test ) - :param y: numpy array containing true labels - (Needed only if using an attack that avoids these labels) - :param batch_size: Number of examples to use in a single evaluation batch. - If not specified, this function will use a reasonable guess and - may run out of memory. - When choosing the batch size, keep in mind that the batch will - be divided up evenly among available devices. If you can fit 128 - examples in memory on one GPU and you have 8 GPUs, you probably - want to use a batch size of 1024 (unless a different batch size - runs faster with the ops you are using, etc.) - :param devices: An optional list of string device names to use. - If not specified, this function will use all visible GPUs. - :param feed: An optional dictionary that is appended to the feeding - dictionary before the session runs. Can be used to feed - the learning phase of a Keras model for instance. - :param attack: cleverhans.attack.Attack - Optional. If no attack specified, evaluates the model on clean data. - If attack is specified, evaluates the model on adversarial examples - created by the attack. - :param attack_params: dictionary - If attack is specified, this dictionary is passed to attack.generate - as keyword arguments. - :return: - an ndarray of ints indicating the class assigned to each example - an ndarray of probabilities assigned to the prediction for each example - """ - - _check_x(x) - inputs = [x] - if attack is not None: - inputs.append(y) - _check_y(y) - if x.shape[0] != y.shape[0]: - raise ValueError("Number of input examples and labels do not match.") - - factory = _ClassAndProbFactory(model, attack, attack_params) - - out = batch_eval_multi_worker(sess, factory, inputs, batch_size=batch_size, - devices=devices, feed=feed) - - classes, confidence = out - - assert classes.shape == (x.shape[0],) - assert confidence.shape == (x.shape[0],) - min_confidence = confidence.min() - if min_confidence < 0.: - raise ValueError("Model does not return valid probabilities: " + - str(min_confidence)) - max_confidence = confidence.max() - if max_confidence > 1.: - raise ValueError("Model does not return valid probablities: " + - str(max_confidence)) - assert confidence.min() >= 0., confidence.min() - - return out - - -def correctness_and_confidence(sess, model, x, y, batch_size=None, - devices=None, feed=None, attack=None, - attack_params=None): - """ - Report whether the model is correct and its confidence on each example in - a dataset. - :param sess: tf.Session - :param model: cleverhans.model.Model - :param x: numpy array containing input examples (e.g. MNIST().x_test ) - :param y: numpy array containing example labels (e.g. MNIST().y_test ) - :param batch_size: Number of examples to use in a single evaluation batch. - If not specified, this function will use a reasonable guess and - may run out of memory. - When choosing the batch size, keep in mind that the batch will - be divided up evenly among available devices. If you can fit 128 - examples in memory on one GPU and you have 8 GPUs, you probably - want to use a batch size of 1024 (unless a different batch size - runs faster with the ops you are using, etc.) - :param devices: An optional list of string device names to use. - If not specified, this function will use all visible GPUs. - :param feed: An optional dictionary that is appended to the feeding - dictionary before the session runs. Can be used to feed - the learning phase of a Keras model for instance. - :param attack: cleverhans.attack.Attack - Optional. If no attack specified, evaluates the model on clean data. - If attack is specified, evaluates the model on adversarial examples - created by the attack. - :param attack_params: dictionary - If attack is specified, this dictionary is passed to attack.generate - as keyword arguments. - :return: - an ndarray of bools indicating whether each example is correct - an ndarray of probabilities assigned to the prediction for each example - """ - - _check_x(x) - _check_y(y) - if x.shape[0] != y.shape[0]: - raise ValueError("Number of input examples and labels do not match.") - - factory = _CorrectAndProbFactory(model, attack, attack_params) - - out = batch_eval_multi_worker(sess, factory, [x, y], batch_size=batch_size, - devices=devices, feed=feed) - - correctness, confidence = out - - assert correctness.shape == (x.shape[0],) - assert confidence.shape == (x.shape[0],) - min_confidence = confidence.min() - if min_confidence < 0.: - raise ValueError("Model does not return valid probabilities: " + - str(min_confidence)) - max_confidence = confidence.max() - if max_confidence > 1.: - raise ValueError("Model does not return valid probablities: " + - str(max_confidence)) - assert confidence.min() >= 0., confidence.min() - - return out - - -def run_attack(sess, model, x, y, attack, attack_params, batch_size=None, - devices=None, feed=None, pass_y=False): - """ - Run attack on every example in a dataset. - :param sess: tf.Session - :param model: cleverhans.model.Model - :param x: numpy array containing input examples (e.g. MNIST().x_test ) - :param y: numpy array containing example labels (e.g. MNIST().y_test ) - :param attack: cleverhans.attack.Attack - :param attack_params: dictionary - passed to attack.generate as keyword arguments. - :param batch_size: Number of examples to use in a single evaluation batch. - If not specified, this function will use a reasonable guess and - may run out of memory. - When choosing the batch size, keep in mind that the batch will - be divided up evenly among available devices. If you can fit 128 - examples in memory on one GPU and you have 8 GPUs, you probably - want to use a batch size of 1024 (unless a different batch size - runs faster with the ops you are using, etc.) - :param devices: An optional list of string device names to use. - If not specified, this function will use all visible GPUs. - :param feed: An optional dictionary that is appended to the feeding - dictionary before the session runs. Can be used to feed - the learning phase of a Keras model for instance. - :param pass_y: bool. If true pass 'y' to `attack.generate` - :return: - an ndarray of bools indicating whether each example is correct - an ndarray of probabilities assigned to the prediction for each example - """ - - _check_x(x) - _check_y(y) - - factory = _AttackFactory(model, attack, attack_params, pass_y) - - out, = batch_eval_multi_worker(sess, factory, [x, y], batch_size=batch_size, - devices=devices, feed=feed) - return out - - -def batch_eval_multi_worker(sess, graph_factory, numpy_inputs, batch_size=None, - devices=None, feed=None): - """ - Generic computation engine for evaluating an expression across a whole - dataset, divided into batches. - - This function assumes that the work can be parallelized with one worker - device handling one batch of data. If you need multiple devices per - batch, use `batch_eval`. - - The tensorflow graph for multiple workers is large, so the first few - runs of the graph will be very slow. If you expect to run the graph - few times (few calls to `batch_eval_multi_worker` that each run few - batches) the startup cost might dominate the runtime, and it might be - preferable to use the single worker `batch_eval` just because its - startup cost will be lower. - - :param sess: tensorflow Session - :param graph_factory: callable - When called, returns (tf_inputs, tf_outputs) where: - tf_inputs is a list of placeholders to feed from the dataset - tf_outputs is a list of tf tensors to calculate - Example: tf_inputs is [x, y] placeholders, tf_outputs is [accuracy]. - This factory must make new tensors when called, rather than, e.g. - handing out a reference to existing tensors. - This factory must make exactly equivalent expressions every time - it is called, otherwise the results of `batch_eval` will vary - depending on how work is distributed to devices. - This factory must respect "with tf.device()" context managers - that are active when it is called, otherwise work will not be - distributed to devices correctly. - :param numpy_inputs: - A list of numpy arrays defining the dataset to be evaluated. - The list should have the same length as tf_inputs. - Each array should have the same number of examples (shape[0]). - Example: numpy_inputs is [MNIST().x_test, MNIST().y_test] - :param batch_size: Number of examples to use in a single evaluation batch. - If not specified, this function will use a reasonable guess and - may run out of memory. - When choosing the batch size, keep in mind that the batch will - be divided up evenly among available devices. If you can fit 128 - examples in memory on one GPU and you have 8 GPUs, you probably - want to use a batch size of 1024 (unless a different batch size - runs faster with the ops you are using, etc.) - :param devices: List of devices to run on. If unspecified, uses all - available GPUs if any GPUS are available, otherwise uses CPUs. - :param feed: An optional dictionary that is appended to the feeding - dictionary before the session runs. Can be used to feed - the learning phase of a Keras model for instance. - :returns: List of numpy arrays corresponding to the outputs produced by - the graph_factory - """ - canary.run_canary() - global _batch_eval_multi_worker_cache - - devices = infer_devices(devices) - - if batch_size is None: - # For big models this might result in OOM and then the user - # should just specify batch_size - batch_size = len(devices) * DEFAULT_EXAMPLES_PER_DEVICE - - n = len(numpy_inputs) - assert n > 0 - m = numpy_inputs[0].shape[0] - for i in range(1, n): - m_i = numpy_inputs[i].shape[0] - if m != m_i: - raise ValueError("All of numpy_inputs must have the same number of examples, but the first one has " + str(m) - + " examples and input " + str(i) + " has " + str(m_i) + "examples.") - out = [] - - replicated_tf_inputs = [] - replicated_tf_outputs = [] - p = None - - num_devices = len(devices) - assert batch_size % num_devices == 0 - device_batch_size = batch_size // num_devices - - cache_key = (graph_factory, tuple(devices)) - if cache_key in _batch_eval_multi_worker_cache: - # Retrieve graph for multi-GPU inference from cache. - # This avoids adding tf ops to the graph - packed = _batch_eval_multi_worker_cache[cache_key] - replicated_tf_inputs, replicated_tf_outputs = packed - p = len(replicated_tf_outputs[0]) - assert p > 0 - else: - # This graph has not been built before. - # Build it now. - - for device in devices: - with tf.device(device): - tf_inputs, tf_outputs = graph_factory() - assert len(tf_inputs) == n - if p is None: - p = len(tf_outputs) - assert p > 0 - else: - assert len(tf_outputs) == p - replicated_tf_inputs.append(tf_inputs) - replicated_tf_outputs.append(tf_outputs) - del tf_inputs - del tf_outputs - # Store the result in the cache - packed = replicated_tf_inputs, replicated_tf_outputs - _batch_eval_multi_worker_cache[cache_key] = packed - for _ in range(p): - out.append([]) - flat_tf_outputs = [] - for output in range(p): - for dev_idx in range(num_devices): - flat_tf_outputs.append(replicated_tf_outputs[dev_idx][output]) - - # pad data to have # examples be multiple of batch size - # we discard the excess later - num_batches = int(np.ceil(float(m) / batch_size)) - needed_m = num_batches * batch_size - excess = needed_m - m - if excess > m: - raise NotImplementedError(("Your batch size (%(batch_size)d) is bigger" - " than the dataset (%(m)d), this function is " - "probably overkill.") % locals()) - - def pad(array): - """Pads an array with replicated examples to have `excess` more entries""" - if excess > 0: - array = np.concatenate((array, array[:excess]), axis=0) - return array - numpy_inputs = [pad(numpy_input) for numpy_input in numpy_inputs] - orig_m = m - m = needed_m - - for start in range(0, m, batch_size): - batch = start // batch_size - if batch % 100 == 0 and batch > 0: - _logger.debug("Batch " + str(batch)) - - # Compute batch start and end indices - end = start + batch_size - numpy_input_batches = [numpy_input[start:end] - for numpy_input in numpy_inputs] - feed_dict = {} - for dev_idx, tf_inputs in enumerate(replicated_tf_inputs): - for tf_input, numpy_input in zip(tf_inputs, numpy_input_batches): - dev_start = dev_idx * device_batch_size - dev_end = (dev_idx + 1) * device_batch_size - value = numpy_input[dev_start:dev_end] - assert value.shape[0] == device_batch_size - feed_dict[tf_input] = value - if feed is not None: - feed_dict.update(feed) - flat_output_batches = sess.run(flat_tf_outputs, feed_dict=feed_dict) - for e in flat_output_batches: - assert e.shape[0] == device_batch_size, e.shape - - output_batches = [] - for output in range(p): - o_start = output * num_devices - o_end = (output + 1) * num_devices - device_values = flat_output_batches[o_start:o_end] - assert len(device_values) == num_devices - output_batches.append(device_values) - - for out_elem, device_values in zip(out, output_batches): - assert len(device_values) == num_devices, (len(device_values), - num_devices) - for device_value in device_values: - assert device_value.shape[0] == device_batch_size - out_elem.extend(device_values) - - out = [np.concatenate(x, axis=0) for x in out] - for e in out: - assert e.shape[0] == m, e.shape - - # Trim off the examples we used to pad up to batch size - out = [e[:orig_m] for e in out] - assert len(out) == p, (len(out), p) - - return out - - -def batch_eval(sess, tf_inputs, tf_outputs, numpy_inputs, batch_size=None, - feed=None, - args=None): - """ - A helper function that computes a tensor on numpy inputs by batches. - This version uses exactly the tensorflow graph constructed by the - caller, so the caller can place specific ops on specific devices - to implement model parallelism. - Most users probably prefer `batch_eval_multi_worker` which maps - a single-device expression to multiple devices in order to evaluate - faster by parallelizing across data. - - :param sess: tf Session to use - :param tf_inputs: list of tf Placeholders to feed from the dataset - :param tf_outputs: list of tf tensors to calculate - :param numpy_inputs: list of numpy arrays defining the dataset - :param batch_size: int, batch size to use for evaluation - If not specified, this function will try to guess the batch size, - but might get an out of memory error or run the model with an - unsupported batch size, etc. - :param feed: An optional dictionary that is appended to the feeding - dictionary before the session runs. Can be used to feed - the learning phase of a Keras model for instance. - :param args: dict or argparse `Namespace` object. - Deprecated and included only for backwards compatibility. - Should contain `batch_size` - """ - - if args is not None: - warnings.warn("`args` is deprecated and will be removed on or " - "after 2019-03-09. Pass `batch_size` directly.") - if "batch_size" in args: - assert batch_size is None - batch_size = args["batch_size"] - - if batch_size is None: - batch_size = DEFAULT_EXAMPLES_PER_DEVICE - - n = len(numpy_inputs) - assert n > 0 - assert n == len(tf_inputs) - m = numpy_inputs[0].shape[0] - for i in range(1, n): - assert numpy_inputs[i].shape[0] == m - out = [] - for _ in tf_outputs: - out.append([]) - for start in range(0, m, batch_size): - batch = start // batch_size - if batch % 100 == 0 and batch > 0: - _logger.debug("Batch " + str(batch)) - - # Compute batch start and end indices - start = batch * batch_size - end = start + batch_size - numpy_input_batches = [numpy_input[start:end] - for numpy_input in numpy_inputs] - cur_batch_size = numpy_input_batches[0].shape[0] - assert cur_batch_size <= batch_size - for e in numpy_input_batches: - assert e.shape[0] == cur_batch_size - - feed_dict = dict(zip(tf_inputs, numpy_input_batches)) - if feed is not None: - feed_dict.update(feed) - numpy_output_batches = sess.run(tf_outputs, feed_dict=feed_dict) - for e in numpy_output_batches: - assert e.shape[0] == cur_batch_size, e.shape - for out_elem, numpy_output_batch in zip(out, numpy_output_batches): - out_elem.append(numpy_output_batch) - - out = [np.concatenate(x, axis=0) for x in out] - for e in out: - assert e.shape[0] == m, e.shape - return out - - -DEFAULT_EXAMPLES_PER_DEVICE = 128 - - -class _CorrectFactory(object): - """ - A factory for an expression for one bool per example indicating - whether each example is correct. - """ - - def __init__(self, model, attack=None, attack_params=None): - if attack_params is None: - attack_params = {} - self.model = model - self.attack = attack - self.attack_params = attack_params - hashable_attack_params = tuple((key, attack_params[key]) for key - in sorted(attack_params.keys())) - self.properties_to_hash = (model, attack, hashable_attack_params) - - def __hash__(self): - # Make factory hashable so that no two factories for the - # same model will be used to build redundant tf graphs - return self.properties_to_hash.__hash__() - - def __eq__(self, other): - # Make factory hashable so that no two factories for the - # same model will be used to build redundant tf graphs - if not isinstance(other, _CorrectFactory): - return False - return self.properties_to_hash == other.properties_to_hash - - def __call__(self): - x_batch = self.model.make_input_placeholder() - y_batch = self.model.make_label_placeholder() - - if LooseVersion(tf.__version__) < LooseVersion('1.0.0'): - raise NotImplementedError() - - if self.attack is None: - x_input = x_batch - else: - attack_params = self.attack_params - if attack_params is None: - attack_params = {} - x_input = self.attack.generate(x_batch, y=y_batch, **attack_params) - - predictions = self.model.get_probs(x_input) - correct = tf.equal(tf.argmax(y_batch, axis=-1), - tf.argmax(predictions, axis=-1)) - - return (x_batch, y_batch), (correct,) - - -class _ClassAndProbFactory(object): - """ - A factory for an expression for the following tuple per (optionally - adversarial) example: - - integer class assigned to the example by the model - - probability assigned to that prediction - """ - - def __init__(self, model, attack=None, attack_params=None): - if attack_params is None: - attack_params = {} - self.model = model - self.attack = attack - self.attack_params = attack_params - hashable_attack_params = tuple((key, attack_params[key]) for key - in sorted(attack_params.keys())) - self.properties_to_hash = (model, attack, hashable_attack_params) - - def __hash__(self): - # Make factory hashable so that no two factories for the - # same model will be used to build redundant tf graphs - return self.properties_to_hash.__hash__() - - def __eq__(self, other): - # Make factory hashable so that no two factories for the - # same model will be used to build redundant tf graphs - if not isinstance(other, _ClassAndProbFactory): - return False - return self.properties_to_hash == other.properties_to_hash - - def __call__(self): - x_batch = self.model.make_input_placeholder() - inputs = [x_batch] - - if LooseVersion(tf.__version__) < LooseVersion('1.0.0'): - raise NotImplementedError() - - if self.attack is None: - x_input = x_batch - else: - y_batch = self.model.make_label_placeholder() - inputs.append(y_batch) - attack_params = self.attack_params - if attack_params is None: - attack_params = {} - x_input = self.attack.generate(x_batch, y=y_batch, **attack_params) - - predictions = self.model.get_probs(x_input) - classes = tf.argmax(predictions, axis=-1) - max_probs = tf.reduce_max(predictions, axis=1) - - return tuple(inputs), (classes, max_probs) - - -class _CorrectAndProbFactory(object): - """ - A factory for an expression for the following tuple per (optionally - adversarial) example: - - bool per indicating whether each the example was classified correctly - - probability assigned to that prediction - """ - - def __init__(self, model, attack=None, attack_params=None): - if attack_params is None: - attack_params = {} - self.model = model - self.attack = attack - self.attack_params = attack_params - hashable_attack_params = tuple((key, attack_params[key]) for key - in sorted(attack_params.keys())) - self.properties_to_hash = (model, attack, hashable_attack_params) - - def __hash__(self): - # Make factory hashable so that no two factories for the - # same model will be used to build redundant tf graphs - return self.properties_to_hash.__hash__() - - def __eq__(self, other): - # Make factory hashable so that no two factories for the - # same model will be used to build redundant tf graphs - if not isinstance(other, _CorrectAndProbFactory): - return False - return self.properties_to_hash == other.properties_to_hash - - def __call__(self): - x_batch = self.model.make_input_placeholder() - y_batch = self.model.make_label_placeholder() - - if LooseVersion(tf.__version__) < LooseVersion('1.0.0'): - raise NotImplementedError() - - if self.attack is None: - x_input = x_batch - else: - attack_params = self.attack_params - if attack_params is None: - attack_params = {} - x_input = self.attack.generate(x_batch, y=y_batch, **attack_params) - - predictions = self.model.get_probs(x_input) - correct = tf.equal(tf.argmax(y_batch, axis=-1), - tf.argmax(predictions, axis=-1)) - max_probs = tf.reduce_max(predictions, axis=1) - - return (x_batch, y_batch), (correct, max_probs) - - -class _AttackFactory(object): - """ - A factory for an expression that runs an adversarial attack - - :param model: cleverhans.model.Model - :param attack: cleverhans.attack.Attack - :param attack_params: dict of arguments to pass to attack.generate - :param pass_y: bool. If True, pass y to the attack. - (Some untargeted attacks prefer to infer y to avoid label leaking. - Targeted attacks require that y not be passed) - """ - - def __init__(self, model, attack, attack_params=None, pass_y=False): - assert isinstance(model, cleverhans.model.Model) - if not isinstance(attack, cleverhans.attacks.Attack): - raise TypeError("`attack` must be an instance of cleverhans.attacks." - "attack. Got %s with type %s " % (str(attack), - str(type(attack)))) - - if attack_params is None: - attack_params = {} - self.model = model - self.attack = attack - self.attack_params = attack_params - self.pass_y = pass_y - hashable_attack_params = tuple((key, attack_params[key]) for key - in sorted(attack_params.keys())) - self.properties_to_hash = (model, attack, hashable_attack_params) - - def __hash__(self): - # Make factory hashable so that no two factories for the - # same model will be used to build redundant tf graphs - return self.properties_to_hash.__hash__() - - def __eq__(self, other): - # Make factory hashable so that no two factories for the - # same model will be used to build redundant tf graphs - if not isinstance(other, _AttackFactory): - return False - return self.properties_to_hash == other.properties_to_hash - - def __call__(self): - x_batch = self.model.make_input_placeholder() - y_batch = self.model.make_label_placeholder() - - attack_params = self.attack_params - if attack_params is None: - attack_params = {} - if self.pass_y: - x_adv = self.attack.generate(x_batch, y=y_batch, **attack_params) - else: - # Some code checks the keys of kwargs, rather than checking if - # y is None, so we need to truly not pass y at all, rather than - # just passing a None value for it. - x_adv = self.attack.generate(x_batch, **attack_params) - - return (x_batch, y_batch), tuple([x_adv]) - - -_logger = create_logger("cleverhans.evaluation") - -# Cache for storing output of `batch_eval_multi_worker`'s calls to -# `graph_factory`, to avoid making the tf graph too big -_batch_eval_multi_worker_cache = {} - - -def _check_x(x): - """ - Makes sure an `x` argument is a valid numpy dataset. - """ - if not isinstance(x, np.ndarray): - raise TypeError("x must be a numpy array. Typically x contains " - "the entire test set inputs.") - - -def _check_y(y): - """ - Makes sure a `y` argument is a vliad numpy dataset. - """ - if not isinstance(y, np.ndarray): - raise TypeError("y must be numpy array. Typically y contains " - "the entire test set labels. Got " + str(y) + " of type " + str(type(y))) diff --git a/cleverhans/experimental/README.md b/cleverhans/experimental/README.md index f4499ecd3..35e490b2b 100644 --- a/cleverhans/experimental/README.md +++ b/cleverhans/experimental/README.md @@ -1,2 +1,2 @@ -This directory contains experimental features of cleverhans, which are not +This directory contains experimental features of CleverHans, which are not integrated into the main API yet. diff --git a/cleverhans/experimental/certification/certify.py b/cleverhans/experimental/certification/certify.py index f33522299..08c873c16 100644 --- a/cleverhans/experimental/certification/certify.py +++ b/cleverhans/experimental/certification/certify.py @@ -15,143 +15,152 @@ flags = tf.app.flags FLAGS = flags.FLAGS -flags.DEFINE_string('checkpoint', None, - 'Path of checkpoint with trained model to verify') -flags.DEFINE_string('model_json', None, - 'Path of json file with model description') -flags.DEFINE_string('init_dual_file', None, - 'Path of numpy file with dual variables to initialize') -flags.DEFINE_string('test_input', None, - 'Path of numpy file with test input to certify') -flags.DEFINE_integer('true_class', 0, 'True class of the test input') -flags.DEFINE_integer('adv_class', -1, - 'target class of adversarial example; all classes if -1') -flags.DEFINE_float('input_minval', -1, 'Minimum value of valid input') -flags.DEFINE_float('input_maxval', 1, 'Maximum value of valid input') -flags.DEFINE_float('epsilon', 0.2, 'Size of perturbation') +flags.DEFINE_string( + "checkpoint", None, "Path of checkpoint with trained model to verify" +) +flags.DEFINE_string("model_json", None, "Path of json file with model description") +flags.DEFINE_string( + "init_dual_file", None, "Path of numpy file with dual variables to initialize" +) +flags.DEFINE_string("test_input", None, "Path of numpy file with test input to certify") +flags.DEFINE_integer("true_class", 0, "True class of the test input") +flags.DEFINE_integer( + "adv_class", -1, "target class of adversarial example; all classes if -1" +) +flags.DEFINE_float("input_minval", -1, "Minimum value of valid input") +flags.DEFINE_float("input_maxval", 1, "Maximum value of valid input") +flags.DEFINE_float("epsilon", 0.2, "Size of perturbation") # Nu might need tuning based on the network -flags.DEFINE_float('init_nu', 300.0, 'Initialization of nu variable.') -flags.DEFINE_float('init_penalty', 100.0, 'Initial penalty') -flags.DEFINE_integer('small_eig_num_steps', 500, - 'Number of eigen value steps in intermediate iterations') -flags.DEFINE_integer('large_eig_num_steps', 5000, - 'Number of eigen value steps in each outer iteration') -flags.DEFINE_integer('inner_num_steps', 600, - 'Number of steps to run in inner loop') -flags.DEFINE_float('outer_num_steps', 10, - 'Number of steps to run in outer loop') -flags.DEFINE_float('beta', 2, 'Multiplicative factor to increase penalty by') -flags.DEFINE_float('smoothness_parameter', 0.001, - 'Smoothness parameter if using eigen decomposition') -flags.DEFINE_float('eig_learning_rate', 0.001, - 'Learning rate for computing min eigen value') -flags.DEFINE_string('optimizer', 'adam', - 'Optimizer to use for entire optimization') -flags.DEFINE_float('init_learning_rate', 0.1, 'Initial learning rate') -flags.DEFINE_float('learning_rate_decay', 0.1, 'Decay of learning rate') -flags.DEFINE_float('momentum_parameter', 0.9, - 'Momentum parameter if using momentum optimizer') -flags.DEFINE_integer('print_stats_steps', 50, - 'Number of steps to print stats after') -flags.DEFINE_string('stats_folder', None, - 'Folder to save stats of the iterations') -flags.DEFINE_integer('projection_steps', 200, - 'Number of steps to compute projection after') -flags.DEFINE_integer('num_classes', 10, 'Total number of classes') -flags.DEFINE_enum('verbosity', 'INFO', - ['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'], - 'Logging verbosity level.') -flags.DEFINE_string('eig_type', 'LZS', - 'Method to compute eigenvalues (TF, SCIPY, or LZS), LZS') -flags.DEFINE_integer('lanczos_steps', 20, - 'Number of steps to perform in Lanczos method.') -flags.DEFINE_integer('num_rows', 28, - 'Number of rows in image') -flags.DEFINE_integer('num_columns', 28, - 'Number of columns in image') -flags.DEFINE_integer('num_channels', 1, - 'Number of channels in image') +flags.DEFINE_float("init_nu", 300.0, "Initialization of nu variable.") +flags.DEFINE_float("init_penalty", 100.0, "Initial penalty") +flags.DEFINE_integer( + "small_eig_num_steps", 500, "Number of eigen value steps in intermediate iterations" +) +flags.DEFINE_integer( + "large_eig_num_steps", 5000, "Number of eigen value steps in each outer iteration" +) +flags.DEFINE_integer("inner_num_steps", 600, "Number of steps to run in inner loop") +flags.DEFINE_float("outer_num_steps", 10, "Number of steps to run in outer loop") +flags.DEFINE_float("beta", 2, "Multiplicative factor to increase penalty by") +flags.DEFINE_float( + "smoothness_parameter", 0.001, "Smoothness parameter if using eigen decomposition" +) +flags.DEFINE_float( + "eig_learning_rate", 0.001, "Learning rate for computing min eigen value" +) +flags.DEFINE_string("optimizer", "adam", "Optimizer to use for entire optimization") +flags.DEFINE_float("init_learning_rate", 0.1, "Initial learning rate") +flags.DEFINE_float("learning_rate_decay", 0.1, "Decay of learning rate") +flags.DEFINE_float( + "momentum_parameter", 0.9, "Momentum parameter if using momentum optimizer" +) +flags.DEFINE_integer("print_stats_steps", 50, "Number of steps to print stats after") +flags.DEFINE_string("stats_folder", None, "Folder to save stats of the iterations") +flags.DEFINE_integer( + "projection_steps", 200, "Number of steps to compute projection after" +) +flags.DEFINE_integer("num_classes", 10, "Total number of classes") +flags.DEFINE_enum( + "verbosity", + "INFO", + ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"], + "Logging verbosity level.", +) +flags.DEFINE_string( + "eig_type", "LZS", "Method to compute eigenvalues (TF, SCIPY, or LZS), LZS" +) +flags.DEFINE_integer( + "lanczos_steps", 20, "Number of steps to perform in Lanczos method." +) +flags.DEFINE_integer("num_rows", 28, "Number of rows in image") +flags.DEFINE_integer("num_columns", 28, "Number of columns in image") +flags.DEFINE_integer("num_channels", 1, "Number of channels in image") MIN_LANCZOS_ITER = 5 + def main(_): - # pylint: disable=missing-docstring - tf.logging.set_verbosity(FLAGS.verbosity) + # pylint: disable=missing-docstring + tf.logging.set_verbosity(FLAGS.verbosity) - start_time = time.time() + start_time = time.time() - # Initialize neural network based on config files - input_shape = [FLAGS.num_rows, FLAGS.num_columns, FLAGS.num_channels] - nn_params = nn.load_network_from_checkpoint(FLAGS.checkpoint, FLAGS.model_json, input_shape) - tf.logging.info('Loaded neural network with size of layers: %s', - nn_params.sizes) - tf.logging.info('Loaded neural network with input shapes: %s', - nn_params.input_shapes) - tf.logging.info('Loaded neural network with output shapes: %s', - nn_params.output_shapes) - dual_var = utils.initialize_dual( - nn_params, FLAGS.init_dual_file, init_nu=FLAGS.init_nu) + # Initialize neural network based on config files + input_shape = [FLAGS.num_rows, FLAGS.num_columns, FLAGS.num_channels] + nn_params = nn.load_network_from_checkpoint( + FLAGS.checkpoint, FLAGS.model_json, input_shape + ) + tf.logging.info("Loaded neural network with size of layers: %s", nn_params.sizes) + tf.logging.info( + "Loaded neural network with input shapes: %s", nn_params.input_shapes + ) + tf.logging.info( + "Loaded neural network with output shapes: %s", nn_params.output_shapes + ) + dual_var = utils.initialize_dual( + nn_params, FLAGS.init_dual_file, init_nu=FLAGS.init_nu + ) - # Reading test input and reshaping - with tf.gfile.Open(FLAGS.test_input) as f: - test_input = np.load(f) - test_input = np.reshape(test_input, [np.size(test_input), 1]) + # Reading test input and reshaping + with tf.gfile.Open(FLAGS.test_input) as f: + test_input = np.load(f) + test_input = np.reshape(test_input, [np.size(test_input), 1]) - if FLAGS.adv_class == -1: - start_class = 0 - end_class = FLAGS.num_classes - else: - start_class = FLAGS.adv_class - end_class = FLAGS.adv_class + 1 - for adv_class in range(start_class, end_class): - tf.logging.info('Running certification for adversarial class %d', adv_class) - if adv_class == FLAGS.true_class: - continue + if FLAGS.adv_class == -1: + start_class = 0 + end_class = FLAGS.num_classes + else: + start_class = FLAGS.adv_class + end_class = FLAGS.adv_class + 1 + for adv_class in range(start_class, end_class): + tf.logging.info("Running certification for adversarial class %d", adv_class) + if adv_class == FLAGS.true_class: + continue - optimization_params = { - 'init_penalty': FLAGS.init_penalty, - 'large_eig_num_steps': FLAGS.large_eig_num_steps, - 'small_eig_num_steps': FLAGS.small_eig_num_steps, - 'inner_num_steps': FLAGS.inner_num_steps, - 'outer_num_steps': FLAGS.outer_num_steps, - 'beta': FLAGS.beta, - 'smoothness_parameter': FLAGS.smoothness_parameter, - 'eig_learning_rate': FLAGS.eig_learning_rate, - 'optimizer': FLAGS.optimizer, - 'init_learning_rate': FLAGS.init_learning_rate, - 'learning_rate_decay': FLAGS.learning_rate_decay, - 'momentum_parameter': FLAGS.momentum_parameter, - 'print_stats_steps': FLAGS.print_stats_steps, - 'stats_folder': FLAGS.stats_folder, - 'projection_steps': FLAGS.projection_steps, - 'eig_type': FLAGS.eig_type, - 'has_conv': nn_params.has_conv, - 'lanczos_steps': FLAGS.lanczos_steps - } - lzs_params = { - 'min_iter': MIN_LANCZOS_ITER, - 'max_iter': FLAGS.lanczos_steps - } - with tf.Session() as sess: - dual = dual_formulation.DualFormulation(sess, - dual_var, - nn_params, - test_input, - FLAGS.true_class, - adv_class, - FLAGS.input_minval, - FLAGS.input_maxval, - FLAGS.epsilon, - lzs_params) - optimization_object = optimization.Optimization(dual, sess, - optimization_params) - is_cert_found = optimization_object.run_optimization() - if not is_cert_found: - print('Example could not be verified') - exit() - print('Example successfully verified') - print('Elapsed time: ' + str(time.time() - start_time)) + optimization_params = { + "init_penalty": FLAGS.init_penalty, + "large_eig_num_steps": FLAGS.large_eig_num_steps, + "small_eig_num_steps": FLAGS.small_eig_num_steps, + "inner_num_steps": FLAGS.inner_num_steps, + "outer_num_steps": FLAGS.outer_num_steps, + "beta": FLAGS.beta, + "smoothness_parameter": FLAGS.smoothness_parameter, + "eig_learning_rate": FLAGS.eig_learning_rate, + "optimizer": FLAGS.optimizer, + "init_learning_rate": FLAGS.init_learning_rate, + "learning_rate_decay": FLAGS.learning_rate_decay, + "momentum_parameter": FLAGS.momentum_parameter, + "print_stats_steps": FLAGS.print_stats_steps, + "stats_folder": FLAGS.stats_folder, + "projection_steps": FLAGS.projection_steps, + "eig_type": FLAGS.eig_type, + "has_conv": nn_params.has_conv, + "lanczos_steps": FLAGS.lanczos_steps, + } + lzs_params = {"min_iter": MIN_LANCZOS_ITER, "max_iter": FLAGS.lanczos_steps} + with tf.Session() as sess: + dual = dual_formulation.DualFormulation( + sess, + dual_var, + nn_params, + test_input, + FLAGS.true_class, + adv_class, + FLAGS.input_minval, + FLAGS.input_maxval, + FLAGS.epsilon, + lzs_params, + ) + optimization_object = optimization.Optimization( + dual, sess, optimization_params + ) + is_cert_found = optimization_object.run_optimization() + if not is_cert_found: + print("Example could not be verified") + exit() + print("Example successfully verified") + print("Elapsed time: " + str(time.time() - start_time)) -if __name__ == '__main__': - tf.app.run(main) +if __name__ == "__main__": + tf.app.run(main) diff --git a/cleverhans/experimental/certification/dual_formulation.py b/cleverhans/experimental/certification/dual_formulation.py index cfd05c794..71ac6236c 100644 --- a/cleverhans/experimental/certification/dual_formulation.py +++ b/cleverhans/experimental/certification/dual_formulation.py @@ -15,7 +15,7 @@ FLAGS = flags.FLAGS # Tolerance value for eigenvalue computation -TOL = 1E-5 +TOL = 1e-5 # Binary search constants MAX_BINARY_SEARCH_ITER = 10 @@ -23,499 +23,586 @@ # Bound on lowest value of certificate to check for numerical errors LOWER_CERT_BOUND = -5.0 -DEFAULT_LZS_PARAMS = {'min_iter': 5, 'max_iter': 50} +DEFAULT_LZS_PARAMS = {"min_iter": 5, "max_iter": 50} class DualFormulation(object): - """DualFormulation is a class that creates the dual objective function - and access to matrix vector products for the matrix that is constrained - to be Positive semidefinite - """ - - def __init__(self, sess, dual_var, neural_net_param_object, test_input, true_class, - adv_class, input_minval, input_maxval, epsilon, - lzs_params=None, project_dual=True): - """Initializes dual formulation class. - - Args: - sess: Tensorflow session - dual_var: dictionary of dual variables containing a) lambda_pos - b) lambda_neg, c) lambda_quad, d) lambda_lu - neural_net_param_object: NeuralNetParam object created for the network - under consideration - test_input: clean example to certify around - true_class: the class label of the test input - adv_class: the label that the adversary tried to perturb input to - input_minval: minimum value of valid input range - input_maxval: maximum value of valid input range - epsilon: Size of the perturbation (scaled for [0, 1] input) - lzs_params: Parameters for Lanczos algorithm (dictionary) in the form: - { - 'min_iter': 5 - 'max_iter': 50 - } - project_dual: Whether we should create a projected dual object - """ - self.sess = sess - self.nn_params = neural_net_param_object - self.test_input = tf.convert_to_tensor(test_input, dtype=tf.float32) - self.true_class = true_class - self.adv_class = adv_class - self.input_minval = tf.convert_to_tensor(input_minval, dtype=tf.float32) - self.input_maxval = tf.convert_to_tensor(input_maxval, dtype=tf.float32) - self.epsilon = tf.convert_to_tensor(epsilon, dtype=tf.float32) - self.lzs_params = lzs_params or DEFAULT_LZS_PARAMS.copy() - self.final_linear = (self.nn_params.final_weights[adv_class, :] - - self.nn_params.final_weights[true_class, :]) - self.final_linear = tf.reshape( - self.final_linear, shape=[tf.size(self.final_linear), 1]) - self.final_constant = (self.nn_params.final_bias[adv_class] - - self.nn_params.final_bias[true_class]) - self.lanczos_dtype = tf.float64 - self.nn_dtype = tf.float32 - - # Computing lower and upper bounds - # Note that lower and upper are of size nn_params.num_hidden_layers + 1 - self.lower = [] - self.upper = [] - - # Also computing pre activation lower and upper bounds - # to compute always-off and always-on units - self.pre_lower = [] - self.pre_upper = [] - - # Initializing at the input layer with \ell_\infty constraints - self.lower.append( - tf.maximum(self.test_input - self.epsilon, self.input_minval)) - self.upper.append( - tf.minimum(self.test_input + self.epsilon, self.input_maxval)) - self.pre_lower.append(self.lower[0]) - self.pre_upper.append(self.upper[0]) - - for i in range(0, self.nn_params.num_hidden_layers): - lo_plus_up = self.nn_params.forward_pass(self.lower[i] + self.upper[i], i) - lo_minus_up = self.nn_params.forward_pass(self.lower[i] - self.upper[i], i, is_abs=True) - up_minus_lo = self.nn_params.forward_pass(self.upper[i] - self.lower[i], i, is_abs=True) - current_lower = 0.5 * (lo_plus_up + lo_minus_up) + self.nn_params.biases[i] - current_upper = 0.5 * (lo_plus_up + up_minus_lo) + self.nn_params.biases[i] - self.pre_lower.append(current_lower) - self.pre_upper.append(current_upper) - self.lower.append(tf.nn.relu(current_lower)) - self.upper.append(tf.nn.relu(current_upper)) - - # Run lower and upper because they don't change - self.pre_lower = self.sess.run(self.pre_lower) - self.pre_upper = self.sess.run(self.pre_upper) - self.lower = self.sess.run(self.lower) - self.upper = self.sess.run(self.upper) - - # Using the preactivation lower and upper bounds - # to compute the linear regions - self.positive_indices = [] - self.negative_indices = [] - self.switch_indices = [] - - for i in range(0, self.nn_params.num_hidden_layers + 1): - # Positive index = 1 if the ReLU is always "on" - self.positive_indices.append(np.asarray(self.pre_lower[i] >= 0, dtype=np.float32)) - # Negative index = 1 if the ReLU is always off - self.negative_indices.append(np.asarray(self.pre_upper[i] <= 0, dtype=np.float32)) - # Switch index = 1 if the ReLU could be either on or off - self.switch_indices.append(np.asarray( - np.multiply(self.pre_lower[i], self.pre_upper[i]) < 0, dtype=np.float32)) - - # Computing the optimization terms - self.lambda_pos = [x for x in dual_var['lambda_pos']] - self.lambda_neg = [x for x in dual_var['lambda_neg']] - self.lambda_quad = [x for x in dual_var['lambda_quad']] - self.lambda_lu = [x for x in dual_var['lambda_lu']] - self.nu = dual_var['nu'] - self.vector_g = None - self.scalar_f = None - self.matrix_h = None - self.matrix_m = None - self.matrix_m_dimension = 1 + np.sum(self.nn_params.sizes) - - # The primal vector in the SDP can be thought of as [layer_1, layer_2..] - # In this concatenated version, dual_index[i] that marks the start - # of layer_i - # This is useful while computing implicit products with matrix H - self.dual_index = [0] - for i in range(self.nn_params.num_hidden_layers + 1): - self.dual_index.append(self.dual_index[-1] + self.nn_params.sizes[i]) - - # Construct objectives, matrices, and certificate - self.set_differentiable_objective() - if not self.nn_params.has_conv: - self.get_full_psd_matrix() - - # Setup Lanczos functionality for compute certificate - self.construct_lanczos_params() - - # Create projected dual object - if project_dual: - self.projected_dual = self.create_projected_dual() - - def create_projected_dual(self): - """Function to create variables for the projected dual object. - Function that projects the input dual variables onto the feasible set. - Returns: - projected_dual: Feasible dual solution corresponding to current dual - """ - # TODO: consider whether we can use shallow copy of the lists without - # using tf.identity - projected_nu = tf.placeholder(tf.float32, shape=[]) - min_eig_h = tf.placeholder(tf.float32, shape=[]) - projected_lambda_pos = [tf.identity(x) for x in self.lambda_pos] - projected_lambda_neg = [tf.identity(x) for x in self.lambda_neg] - projected_lambda_quad = [ - tf.identity(x) for x in self.lambda_quad - ] - projected_lambda_lu = [tf.identity(x) for x in self.lambda_lu] - - for i in range(self.nn_params.num_hidden_layers + 1): - # Making H PSD - projected_lambda_lu[i] = self.lambda_lu[i] + 0.5*tf.maximum(-min_eig_h, 0) + TOL - # Adjusting the value of \lambda_neg to make change in g small - projected_lambda_neg[i] = self.lambda_neg[i] + tf.multiply( - (self.lower[i] + self.upper[i]), - (self.lambda_lu[i] - projected_lambda_lu[i])) - projected_lambda_neg[i] = (tf.multiply(self.negative_indices[i], - projected_lambda_neg[i]) + - tf.multiply(self.switch_indices[i], - tf.maximum(projected_lambda_neg[i], 0))) - - projected_dual_var = { - 'lambda_pos': projected_lambda_pos, - 'lambda_neg': projected_lambda_neg, - 'lambda_lu': projected_lambda_lu, - 'lambda_quad': projected_lambda_quad, - 'nu': projected_nu, - } - projected_dual_object = DualFormulation( - self.sess, projected_dual_var, self.nn_params, - self.test_input, self.true_class, - self.adv_class, self.input_minval, - self.input_maxval, self.epsilon, - self.lzs_params, - project_dual=False) - projected_dual_object.min_eig_val_h = min_eig_h - return projected_dual_object - - def construct_lanczos_params(self): - """Computes matrices T and V using the Lanczos algorithm. - - Args: - k: number of iterations and dimensionality of the tridiagonal matrix - Returns: - eig_vec: eigen vector corresponding to min eigenvalue - """ - # Using autograph to automatically handle - # the control flow of minimum_eigen_vector - self.min_eigen_vec = autograph.to_graph(utils.tf_lanczos_smallest_eigval) - - def _m_vector_prod_fn(x): - return self.get_psd_product(x, dtype=self.lanczos_dtype) - def _h_vector_prod_fn(x): - return self.get_h_product(x, dtype=self.lanczos_dtype) - - # Construct nodes for computing eigenvalue of M - self.m_min_vec_estimate = np.zeros(shape=(self.matrix_m_dimension, 1), dtype=np.float64) - zeros_m = tf.zeros(shape=(self.matrix_m_dimension, 1), dtype=tf.float64) - self.m_min_vec_ph = tf.placeholder_with_default(input=zeros_m, - shape=(self.matrix_m_dimension, 1), - name='m_min_vec_ph') - self.m_min_eig, self.m_min_vec = self.min_eigen_vec(_m_vector_prod_fn, - self.matrix_m_dimension, - self.m_min_vec_ph, - self.lzs_params['max_iter'], - dtype=self.lanczos_dtype) - self.m_min_eig = tf.cast(self.m_min_eig, self.nn_dtype) - self.m_min_vec = tf.cast(self.m_min_vec, self.nn_dtype) - - self.h_min_vec_estimate = np.zeros(shape=(self.matrix_m_dimension - 1, 1), dtype=np.float64) - zeros_h = tf.zeros(shape=(self.matrix_m_dimension - 1, 1), dtype=tf.float64) - self.h_min_vec_ph = tf.placeholder_with_default(input=zeros_h, - shape=(self.matrix_m_dimension - 1, 1), - name='h_min_vec_ph') - self.h_min_eig, self.h_min_vec = self.min_eigen_vec(_h_vector_prod_fn, - self.matrix_m_dimension-1, - self.h_min_vec_ph, - self.lzs_params['max_iter'], - dtype=self.lanczos_dtype) - self.h_min_eig = tf.cast(self.h_min_eig, self.nn_dtype) - self.h_min_vec = tf.cast(self.h_min_vec, self.nn_dtype) - - def set_differentiable_objective(self): - """Function that constructs minimization objective from dual variables.""" - # Checking if graphs are already created - if self.vector_g is not None: - return - - # Computing the scalar term - bias_sum = 0 - for i in range(0, self.nn_params.num_hidden_layers): - bias_sum = bias_sum + tf.reduce_sum( - tf.multiply(self.nn_params.biases[i], self.lambda_pos[i + 1])) - lu_sum = 0 - for i in range(0, self.nn_params.num_hidden_layers + 1): - lu_sum = lu_sum + tf.reduce_sum( - tf.multiply(tf.multiply(self.lower[i], self.upper[i]), - self.lambda_lu[i])) - - self.scalar_f = -bias_sum - lu_sum + self.final_constant - - # Computing the vector term - g_rows = [] - for i in range(0, self.nn_params.num_hidden_layers): - if i > 0: - current_row = (self.lambda_neg[i] + self.lambda_pos[i] - - self.nn_params.forward_pass(self.lambda_pos[i+1], - i, is_transpose=True) + - tf.multiply(self.lower[i]+self.upper[i], - self.lambda_lu[i]) + - tf.multiply(self.lambda_quad[i], - self.nn_params.biases[i-1])) - else: - current_row = (-self.nn_params.forward_pass(self.lambda_pos[i+1], - i, is_transpose=True) - + tf.multiply(self.lower[i]+self.upper[i], - self.lambda_lu[i])) - g_rows.append(current_row) - - # Term for final linear term - g_rows.append((self.lambda_pos[self.nn_params.num_hidden_layers] + - self.lambda_neg[self.nn_params.num_hidden_layers] + - self.final_linear + - tf.multiply((self.lower[self.nn_params.num_hidden_layers]+ - self.upper[self.nn_params.num_hidden_layers]), - self.lambda_lu[self.nn_params.num_hidden_layers]) - + tf.multiply( - self.lambda_quad[self.nn_params.num_hidden_layers], - self.nn_params.biases[ - self.nn_params.num_hidden_layers-1]))) - self.vector_g = tf.concat(g_rows, axis=0) - self.unconstrained_objective = self.scalar_f + 0.5 * self.nu - - def get_h_product(self, vector, dtype=None): - """Function that provides matrix product interface with PSD matrix. - - Args: - vector: the vector to be multiplied with matrix H - - Returns: - result_product: Matrix product of H and vector - """ - # Computing the product of matrix_h with beta (input vector) - # At first layer, h is simply diagonal - if dtype is None: - dtype = self.nn_dtype - beta = tf.cast(vector, self.nn_dtype) - h_beta_rows = [] - for i in range(self.nn_params.num_hidden_layers): - # Split beta of this block into [gamma, delta] - gamma = beta[self.dual_index[i]:self.dual_index[i + 1]] - delta = beta[self.dual_index[i + 1]:self.dual_index[i + 2]] - - # Expanding the product with diagonal matrices - if i == 0: - h_beta_rows.append( - tf.multiply(2 * self.lambda_lu[i], gamma) - - self.nn_params.forward_pass( - tf.multiply(self.lambda_quad[i + 1], delta), - i, - is_transpose=True)) - else: - h_beta_rows[i] = (h_beta_rows[i] + - tf.multiply(self.lambda_quad[i] + - self.lambda_lu[i], gamma) - - self.nn_params.forward_pass( - tf.multiply(self.lambda_quad[i+1], delta), - i, is_transpose=True)) - - new_row = ( - tf.multiply(self.lambda_quad[i + 1] + self.lambda_lu[i + 1], delta) - - tf.multiply(self.lambda_quad[i + 1], - self.nn_params.forward_pass(gamma, i))) - h_beta_rows.append(new_row) - - # Last boundary case - h_beta_rows[self.nn_params.num_hidden_layers] = ( - h_beta_rows[self.nn_params.num_hidden_layers] + - tf.multiply((self.lambda_quad[self.nn_params.num_hidden_layers] + - self.lambda_lu[self.nn_params.num_hidden_layers]), - delta)) - - h_beta = tf.concat(h_beta_rows, axis=0) - return tf.cast(h_beta, dtype) - - def get_psd_product(self, vector, dtype=None): - """Function that provides matrix product interface with PSD matrix. - - Args: - vector: the vector to be multiplied with matrix M - - Returns: - result_product: Matrix product of M and vector + """DualFormulation is a class that creates the dual objective function + and access to matrix vector products for the matrix that is constrained + to be Positive semidefinite """ - # For convenience, think of x as [\alpha, \beta] - if dtype is None: - dtype = self.nn_dtype - vector = tf.cast(vector, self.nn_dtype) - alpha = tf.reshape(vector[0], shape=[1, 1]) - beta = vector[1:] - # Computing the product of matrix_h with beta part of vector - # At first layer, h is simply diagonal - h_beta = self.get_h_product(beta) - - # Constructing final result using vector_g - result = tf.concat( - [ - alpha * self.nu + tf.reduce_sum(tf.multiply(beta, self.vector_g)), - tf.multiply(alpha, self.vector_g) + h_beta - ], - axis=0) - return tf.cast(result, dtype) - - def get_full_psd_matrix(self): - """Function that returns the tf graph corresponding to the entire matrix M. - - Returns: - matrix_h: unrolled version of tf matrix corresponding to H - matrix_m: unrolled tf matrix corresponding to M - """ - if self.matrix_m is not None: - return self.matrix_h, self.matrix_m - - # Computing the matrix term - h_columns = [] - for i in range(self.nn_params.num_hidden_layers + 1): - current_col_elems = [] - for j in range(i): - current_col_elems.append( - tf.zeros([self.nn_params.sizes[j], self.nn_params.sizes[i]])) - - # For the first layer, there is no relu constraint - if i == 0: - current_col_elems.append(utils.diag(self.lambda_lu[i])) - else: - current_col_elems.append( - utils.diag(self.lambda_lu[i] + self.lambda_quad[i])) - if i < self.nn_params.num_hidden_layers: - current_col_elems.append(tf.matmul( - utils.diag(-1 * self.lambda_quad[i + 1]), - self.nn_params.weights[i])) - for j in range(i + 2, self.nn_params.num_hidden_layers + 1): - current_col_elems.append( - tf.zeros([self.nn_params.sizes[j], self.nn_params.sizes[i]])) - current_column = tf.concat(current_col_elems, 0) - h_columns.append(current_column) - - self.matrix_h = tf.concat(h_columns, 1) - self.matrix_h = (self.matrix_h + tf.transpose(self.matrix_h)) - - self.matrix_m = tf.concat( - [ - tf.concat([tf.reshape(self.nu, (1, 1)), tf.transpose(self.vector_g)], axis=1), - tf.concat([self.vector_g, self.matrix_h], axis=1) - ], - axis=0) - return self.matrix_h, self.matrix_m - - def make_m_psd(self, original_nu, feed_dictionary): - """Run binary search to find a value for nu that makes M PSD - Args: - original_nu: starting value of nu to do binary search on - feed_dictionary: dictionary of updated lambda variables to feed into M - Returns: - new_nu: new value of nu - """ - feed_dict = feed_dictionary.copy() - _, min_eig_val_m = self.get_lanczos_eig(compute_m=True, feed_dict=feed_dict) - - lower_nu = original_nu - upper_nu = original_nu - num_iter = 0 - - # Find an upper bound on nu - while min_eig_val_m - TOL < 0 and num_iter < (MAX_BINARY_SEARCH_ITER / 2): - num_iter += 1 - upper_nu *= NU_UPDATE_CONSTANT - feed_dict.update({self.nu: upper_nu}) - _, min_eig_val_m = self.get_lanczos_eig(compute_m=True, feed_dict=feed_dict) - - final_nu = upper_nu - - # Perform binary search to find best value of nu - while lower_nu <= upper_nu and num_iter < MAX_BINARY_SEARCH_ITER: - num_iter += 1 - mid_nu = (lower_nu + upper_nu) / 2 - feed_dict.update({self.nu: mid_nu}) - _, min_eig_val_m = self.get_lanczos_eig(compute_m=True, feed_dict=feed_dict) - if min_eig_val_m - TOL < 0: - lower_nu = mid_nu - else: - upper_nu = mid_nu - - final_nu = upper_nu - - return final_nu - - def get_lanczos_eig(self, compute_m=True, feed_dict=None): - """Computes the min eigen value and corresponding vector of matrix M or H - using the Lanczos algorithm. - Args: - compute_m: boolean to determine whether we should compute eig val/vec - for M or for H. True for M; False for H. - feed_dict: dictionary mapping from TF placeholders to values (optional) - Returns: - min_eig_vec: Corresponding eigen vector to min eig val - eig_val: Minimum eigen value - """ - if compute_m: - min_eig, min_vec = self.sess.run([self.m_min_eig, self.m_min_vec], feed_dict=feed_dict) - - else: - min_eig, min_vec = self.sess.run([self.h_min_eig, self.h_min_vec], feed_dict=feed_dict) - - return min_vec, min_eig - - def compute_certificate(self, current_step, feed_dictionary): - """ Function to compute the certificate based either current value - or dual variables loaded from dual folder """ - feed_dict = feed_dictionary.copy() - nu = feed_dict[self.nu] - second_term = self.make_m_psd(nu, feed_dict) - tf.logging.info('Nu after modifying: ' + str(second_term)) - feed_dict.update({self.nu: second_term}) - computed_certificate = self.sess.run(self.unconstrained_objective, feed_dict=feed_dict) - - tf.logging.info('Inner step: %d, current value of certificate: %f', - current_step, computed_certificate) - - # Sometimes due to either overflow or instability in inverses, - # the returned certificate is large and negative -- keeping a check - if LOWER_CERT_BOUND < computed_certificate < 0: - _, min_eig_val_m = self.get_lanczos_eig(feed_dict=feed_dict) - tf.logging.info('min eig val from lanczos: ' + str(min_eig_val_m)) - input_vector_m = tf.placeholder(tf.float32, shape=(self.matrix_m_dimension, 1)) - output_vector_m = self.get_psd_product(input_vector_m) - - def np_vector_prod_fn_m(np_vector): - np_vector = np.reshape(np_vector, [-1, 1]) - feed_dict.update({input_vector_m:np_vector}) - output_np_vector = self.sess.run(output_vector_m, feed_dict=feed_dict) - return output_np_vector - linear_operator_m = LinearOperator((self.matrix_m_dimension, - self.matrix_m_dimension), - matvec=np_vector_prod_fn_m) - # Performing shift invert scipy operation when eig val estimate is available - min_eig_val_m_scipy, _ = eigs(linear_operator_m, k=1, which='SR', tol=TOL) - - tf.logging.info('min eig val m from scipy: ' + str(min_eig_val_m_scipy)) - - if min_eig_val_m - TOL > 0: - tf.logging.info('Found certificate of robustness!') - return True - - return False + + def __init__( + self, + sess, + dual_var, + neural_net_param_object, + test_input, + true_class, + adv_class, + input_minval, + input_maxval, + epsilon, + lzs_params=None, + project_dual=True, + ): + """Initializes dual formulation class. + + Args: + sess: Tensorflow session + dual_var: dictionary of dual variables containing a) lambda_pos + b) lambda_neg, c) lambda_quad, d) lambda_lu + neural_net_param_object: NeuralNetParam object created for the network + under consideration + test_input: clean example to certify around + true_class: the class label of the test input + adv_class: the label that the adversary tried to perturb input to + input_minval: minimum value of valid input range + input_maxval: maximum value of valid input range + epsilon: Size of the perturbation (scaled for [0, 1] input) + lzs_params: Parameters for Lanczos algorithm (dictionary) in the form: + { + 'min_iter': 5 + 'max_iter': 50 + } + project_dual: Whether we should create a projected dual object + """ + self.sess = sess + self.nn_params = neural_net_param_object + self.test_input = tf.convert_to_tensor(test_input, dtype=tf.float32) + self.true_class = true_class + self.adv_class = adv_class + self.input_minval = tf.convert_to_tensor(input_minval, dtype=tf.float32) + self.input_maxval = tf.convert_to_tensor(input_maxval, dtype=tf.float32) + self.epsilon = tf.convert_to_tensor(epsilon, dtype=tf.float32) + self.lzs_params = lzs_params or DEFAULT_LZS_PARAMS.copy() + self.final_linear = ( + self.nn_params.final_weights[adv_class, :] + - self.nn_params.final_weights[true_class, :] + ) + self.final_linear = tf.reshape( + self.final_linear, shape=[tf.size(self.final_linear), 1] + ) + self.final_constant = ( + self.nn_params.final_bias[adv_class] - self.nn_params.final_bias[true_class] + ) + self.lanczos_dtype = tf.float64 + self.nn_dtype = tf.float32 + + # Computing lower and upper bounds + # Note that lower and upper are of size nn_params.num_hidden_layers + 1 + self.lower = [] + self.upper = [] + + # Also computing pre activation lower and upper bounds + # to compute always-off and always-on units + self.pre_lower = [] + self.pre_upper = [] + + # Initializing at the input layer with \ell_\infty constraints + self.lower.append(tf.maximum(self.test_input - self.epsilon, self.input_minval)) + self.upper.append(tf.minimum(self.test_input + self.epsilon, self.input_maxval)) + self.pre_lower.append(self.lower[0]) + self.pre_upper.append(self.upper[0]) + + for i in range(0, self.nn_params.num_hidden_layers): + lo_plus_up = self.nn_params.forward_pass(self.lower[i] + self.upper[i], i) + lo_minus_up = self.nn_params.forward_pass( + self.lower[i] - self.upper[i], i, is_abs=True + ) + up_minus_lo = self.nn_params.forward_pass( + self.upper[i] - self.lower[i], i, is_abs=True + ) + current_lower = 0.5 * (lo_plus_up + lo_minus_up) + self.nn_params.biases[i] + current_upper = 0.5 * (lo_plus_up + up_minus_lo) + self.nn_params.biases[i] + self.pre_lower.append(current_lower) + self.pre_upper.append(current_upper) + self.lower.append(tf.nn.relu(current_lower)) + self.upper.append(tf.nn.relu(current_upper)) + + # Run lower and upper because they don't change + self.pre_lower = self.sess.run(self.pre_lower) + self.pre_upper = self.sess.run(self.pre_upper) + self.lower = self.sess.run(self.lower) + self.upper = self.sess.run(self.upper) + + # Using the preactivation lower and upper bounds + # to compute the linear regions + self.positive_indices = [] + self.negative_indices = [] + self.switch_indices = [] + + for i in range(0, self.nn_params.num_hidden_layers + 1): + # Positive index = 1 if the ReLU is always "on" + self.positive_indices.append( + np.asarray(self.pre_lower[i] >= 0, dtype=np.float32) + ) + # Negative index = 1 if the ReLU is always off + self.negative_indices.append( + np.asarray(self.pre_upper[i] <= 0, dtype=np.float32) + ) + # Switch index = 1 if the ReLU could be either on or off + self.switch_indices.append( + np.asarray( + np.multiply(self.pre_lower[i], self.pre_upper[i]) < 0, + dtype=np.float32, + ) + ) + + # Computing the optimization terms + self.lambda_pos = [x for x in dual_var["lambda_pos"]] + self.lambda_neg = [x for x in dual_var["lambda_neg"]] + self.lambda_quad = [x for x in dual_var["lambda_quad"]] + self.lambda_lu = [x for x in dual_var["lambda_lu"]] + self.nu = dual_var["nu"] + self.vector_g = None + self.scalar_f = None + self.matrix_h = None + self.matrix_m = None + self.matrix_m_dimension = 1 + np.sum(self.nn_params.sizes) + + # The primal vector in the SDP can be thought of as [layer_1, layer_2..] + # In this concatenated version, dual_index[i] that marks the start + # of layer_i + # This is useful while computing implicit products with matrix H + self.dual_index = [0] + for i in range(self.nn_params.num_hidden_layers + 1): + self.dual_index.append(self.dual_index[-1] + self.nn_params.sizes[i]) + + # Construct objectives, matrices, and certificate + self.set_differentiable_objective() + if not self.nn_params.has_conv: + self.get_full_psd_matrix() + + # Setup Lanczos functionality for compute certificate + self.construct_lanczos_params() + + # Create projected dual object + if project_dual: + self.projected_dual = self.create_projected_dual() + + def create_projected_dual(self): + """Function to create variables for the projected dual object. + Function that projects the input dual variables onto the feasible set. + Returns: + projected_dual: Feasible dual solution corresponding to current dual + """ + # TODO: consider whether we can use shallow copy of the lists without + # using tf.identity + projected_nu = tf.placeholder(tf.float32, shape=[]) + min_eig_h = tf.placeholder(tf.float32, shape=[]) + projected_lambda_pos = [tf.identity(x) for x in self.lambda_pos] + projected_lambda_neg = [tf.identity(x) for x in self.lambda_neg] + projected_lambda_quad = [tf.identity(x) for x in self.lambda_quad] + projected_lambda_lu = [tf.identity(x) for x in self.lambda_lu] + + for i in range(self.nn_params.num_hidden_layers + 1): + # Making H PSD + projected_lambda_lu[i] = ( + self.lambda_lu[i] + 0.5 * tf.maximum(-min_eig_h, 0) + TOL + ) + # Adjusting the value of \lambda_neg to make change in g small + projected_lambda_neg[i] = self.lambda_neg[i] + tf.multiply( + (self.lower[i] + self.upper[i]), + (self.lambda_lu[i] - projected_lambda_lu[i]), + ) + projected_lambda_neg[i] = tf.multiply( + self.negative_indices[i], projected_lambda_neg[i] + ) + tf.multiply( + self.switch_indices[i], tf.maximum(projected_lambda_neg[i], 0) + ) + + projected_dual_var = { + "lambda_pos": projected_lambda_pos, + "lambda_neg": projected_lambda_neg, + "lambda_lu": projected_lambda_lu, + "lambda_quad": projected_lambda_quad, + "nu": projected_nu, + } + projected_dual_object = DualFormulation( + self.sess, + projected_dual_var, + self.nn_params, + self.test_input, + self.true_class, + self.adv_class, + self.input_minval, + self.input_maxval, + self.epsilon, + self.lzs_params, + project_dual=False, + ) + projected_dual_object.min_eig_val_h = min_eig_h + return projected_dual_object + + def construct_lanczos_params(self): + """Computes matrices T and V using the Lanczos algorithm. + + Args: + k: number of iterations and dimensionality of the tridiagonal matrix + Returns: + eig_vec: eigen vector corresponding to min eigenvalue + """ + # Using autograph to automatically handle + # the control flow of minimum_eigen_vector + self.min_eigen_vec = autograph.to_graph(utils.tf_lanczos_smallest_eigval) + + def _m_vector_prod_fn(x): + return self.get_psd_product(x, dtype=self.lanczos_dtype) + + def _h_vector_prod_fn(x): + return self.get_h_product(x, dtype=self.lanczos_dtype) + + # Construct nodes for computing eigenvalue of M + self.m_min_vec_estimate = np.zeros( + shape=(self.matrix_m_dimension, 1), dtype=np.float64 + ) + zeros_m = tf.zeros(shape=(self.matrix_m_dimension, 1), dtype=tf.float64) + self.m_min_vec_ph = tf.placeholder_with_default( + input=zeros_m, shape=(self.matrix_m_dimension, 1), name="m_min_vec_ph" + ) + self.m_min_eig, self.m_min_vec = self.min_eigen_vec( + _m_vector_prod_fn, + self.matrix_m_dimension, + self.m_min_vec_ph, + self.lzs_params["max_iter"], + dtype=self.lanczos_dtype, + ) + self.m_min_eig = tf.cast(self.m_min_eig, self.nn_dtype) + self.m_min_vec = tf.cast(self.m_min_vec, self.nn_dtype) + + self.h_min_vec_estimate = np.zeros( + shape=(self.matrix_m_dimension - 1, 1), dtype=np.float64 + ) + zeros_h = tf.zeros(shape=(self.matrix_m_dimension - 1, 1), dtype=tf.float64) + self.h_min_vec_ph = tf.placeholder_with_default( + input=zeros_h, shape=(self.matrix_m_dimension - 1, 1), name="h_min_vec_ph" + ) + self.h_min_eig, self.h_min_vec = self.min_eigen_vec( + _h_vector_prod_fn, + self.matrix_m_dimension - 1, + self.h_min_vec_ph, + self.lzs_params["max_iter"], + dtype=self.lanczos_dtype, + ) + self.h_min_eig = tf.cast(self.h_min_eig, self.nn_dtype) + self.h_min_vec = tf.cast(self.h_min_vec, self.nn_dtype) + + def set_differentiable_objective(self): + """Function that constructs minimization objective from dual variables.""" + # Checking if graphs are already created + if self.vector_g is not None: + return + + # Computing the scalar term + bias_sum = 0 + for i in range(0, self.nn_params.num_hidden_layers): + bias_sum = bias_sum + tf.reduce_sum( + tf.multiply(self.nn_params.biases[i], self.lambda_pos[i + 1]) + ) + lu_sum = 0 + for i in range(0, self.nn_params.num_hidden_layers + 1): + lu_sum = lu_sum + tf.reduce_sum( + tf.multiply( + tf.multiply(self.lower[i], self.upper[i]), self.lambda_lu[i] + ) + ) + + self.scalar_f = -bias_sum - lu_sum + self.final_constant + + # Computing the vector term + g_rows = [] + for i in range(0, self.nn_params.num_hidden_layers): + if i > 0: + current_row = ( + self.lambda_neg[i] + + self.lambda_pos[i] + - self.nn_params.forward_pass( + self.lambda_pos[i + 1], i, is_transpose=True + ) + + tf.multiply(self.lower[i] + self.upper[i], self.lambda_lu[i]) + + tf.multiply(self.lambda_quad[i], self.nn_params.biases[i - 1]) + ) + else: + current_row = -self.nn_params.forward_pass( + self.lambda_pos[i + 1], i, is_transpose=True + ) + tf.multiply(self.lower[i] + self.upper[i], self.lambda_lu[i]) + g_rows.append(current_row) + + # Term for final linear term + g_rows.append( + ( + self.lambda_pos[self.nn_params.num_hidden_layers] + + self.lambda_neg[self.nn_params.num_hidden_layers] + + self.final_linear + + tf.multiply( + ( + self.lower[self.nn_params.num_hidden_layers] + + self.upper[self.nn_params.num_hidden_layers] + ), + self.lambda_lu[self.nn_params.num_hidden_layers], + ) + + tf.multiply( + self.lambda_quad[self.nn_params.num_hidden_layers], + self.nn_params.biases[self.nn_params.num_hidden_layers - 1], + ) + ) + ) + self.vector_g = tf.concat(g_rows, axis=0) + self.unconstrained_objective = self.scalar_f + 0.5 * self.nu + + def get_h_product(self, vector, dtype=None): + """Function that provides matrix product interface with PSD matrix. + + Args: + vector: the vector to be multiplied with matrix H + + Returns: + result_product: Matrix product of H and vector + """ + # Computing the product of matrix_h with beta (input vector) + # At first layer, h is simply diagonal + if dtype is None: + dtype = self.nn_dtype + beta = tf.cast(vector, self.nn_dtype) + h_beta_rows = [] + for i in range(self.nn_params.num_hidden_layers): + # Split beta of this block into [gamma, delta] + gamma = beta[self.dual_index[i] : self.dual_index[i + 1]] + delta = beta[self.dual_index[i + 1] : self.dual_index[i + 2]] + + # Expanding the product with diagonal matrices + if i == 0: + h_beta_rows.append( + tf.multiply(2 * self.lambda_lu[i], gamma) + - self.nn_params.forward_pass( + tf.multiply(self.lambda_quad[i + 1], delta), + i, + is_transpose=True, + ) + ) + else: + h_beta_rows[i] = ( + h_beta_rows[i] + + tf.multiply(self.lambda_quad[i] + self.lambda_lu[i], gamma) + - self.nn_params.forward_pass( + tf.multiply(self.lambda_quad[i + 1], delta), + i, + is_transpose=True, + ) + ) + + new_row = tf.multiply( + self.lambda_quad[i + 1] + self.lambda_lu[i + 1], delta + ) - tf.multiply( + self.lambda_quad[i + 1], self.nn_params.forward_pass(gamma, i) + ) + h_beta_rows.append(new_row) + + # Last boundary case + h_beta_rows[self.nn_params.num_hidden_layers] = h_beta_rows[ + self.nn_params.num_hidden_layers + ] + tf.multiply( + ( + self.lambda_quad[self.nn_params.num_hidden_layers] + + self.lambda_lu[self.nn_params.num_hidden_layers] + ), + delta, + ) + + h_beta = tf.concat(h_beta_rows, axis=0) + return tf.cast(h_beta, dtype) + + def get_psd_product(self, vector, dtype=None): + """Function that provides matrix product interface with PSD matrix. + + Args: + vector: the vector to be multiplied with matrix M + + Returns: + result_product: Matrix product of M and vector + """ + # For convenience, think of x as [\alpha, \beta] + if dtype is None: + dtype = self.nn_dtype + vector = tf.cast(vector, self.nn_dtype) + alpha = tf.reshape(vector[0], shape=[1, 1]) + beta = vector[1:] + # Computing the product of matrix_h with beta part of vector + # At first layer, h is simply diagonal + h_beta = self.get_h_product(beta) + + # Constructing final result using vector_g + result = tf.concat( + [ + alpha * self.nu + tf.reduce_sum(tf.multiply(beta, self.vector_g)), + tf.multiply(alpha, self.vector_g) + h_beta, + ], + axis=0, + ) + return tf.cast(result, dtype) + + def get_full_psd_matrix(self): + """Function that returns the tf graph corresponding to the entire matrix M. + + Returns: + matrix_h: unrolled version of tf matrix corresponding to H + matrix_m: unrolled tf matrix corresponding to M + """ + if self.matrix_m is not None: + return self.matrix_h, self.matrix_m + + # Computing the matrix term + h_columns = [] + for i in range(self.nn_params.num_hidden_layers + 1): + current_col_elems = [] + for j in range(i): + current_col_elems.append( + tf.zeros([self.nn_params.sizes[j], self.nn_params.sizes[i]]) + ) + + # For the first layer, there is no relu constraint + if i == 0: + current_col_elems.append(utils.diag(self.lambda_lu[i])) + else: + current_col_elems.append( + utils.diag(self.lambda_lu[i] + self.lambda_quad[i]) + ) + if i < self.nn_params.num_hidden_layers: + current_col_elems.append( + tf.matmul( + utils.diag(-1 * self.lambda_quad[i + 1]), + self.nn_params.weights[i], + ) + ) + for j in range(i + 2, self.nn_params.num_hidden_layers + 1): + current_col_elems.append( + tf.zeros([self.nn_params.sizes[j], self.nn_params.sizes[i]]) + ) + current_column = tf.concat(current_col_elems, 0) + h_columns.append(current_column) + + self.matrix_h = tf.concat(h_columns, 1) + self.matrix_h = self.matrix_h + tf.transpose(self.matrix_h) + + self.matrix_m = tf.concat( + [ + tf.concat( + [tf.reshape(self.nu, (1, 1)), tf.transpose(self.vector_g)], axis=1 + ), + tf.concat([self.vector_g, self.matrix_h], axis=1), + ], + axis=0, + ) + return self.matrix_h, self.matrix_m + + def make_m_psd(self, original_nu, feed_dictionary): + """Run binary search to find a value for nu that makes M PSD + Args: + original_nu: starting value of nu to do binary search on + feed_dictionary: dictionary of updated lambda variables to feed into M + Returns: + new_nu: new value of nu + """ + feed_dict = feed_dictionary.copy() + _, min_eig_val_m = self.get_lanczos_eig(compute_m=True, feed_dict=feed_dict) + + lower_nu = original_nu + upper_nu = original_nu + num_iter = 0 + + # Find an upper bound on nu + while min_eig_val_m - TOL < 0 and num_iter < (MAX_BINARY_SEARCH_ITER / 2): + num_iter += 1 + upper_nu *= NU_UPDATE_CONSTANT + feed_dict.update({self.nu: upper_nu}) + _, min_eig_val_m = self.get_lanczos_eig(compute_m=True, feed_dict=feed_dict) + + final_nu = upper_nu + + # Perform binary search to find best value of nu + while lower_nu <= upper_nu and num_iter < MAX_BINARY_SEARCH_ITER: + num_iter += 1 + mid_nu = (lower_nu + upper_nu) / 2 + feed_dict.update({self.nu: mid_nu}) + _, min_eig_val_m = self.get_lanczos_eig(compute_m=True, feed_dict=feed_dict) + if min_eig_val_m - TOL < 0: + lower_nu = mid_nu + else: + upper_nu = mid_nu + + final_nu = upper_nu + + return final_nu + + def get_lanczos_eig(self, compute_m=True, feed_dict=None): + """Computes the min eigen value and corresponding vector of matrix M or H + using the Lanczos algorithm. + Args: + compute_m: boolean to determine whether we should compute eig val/vec + for M or for H. True for M; False for H. + feed_dict: dictionary mapping from TF placeholders to values (optional) + Returns: + min_eig_vec: Corresponding eigen vector to min eig val + eig_val: Minimum eigen value + """ + if compute_m: + min_eig, min_vec = self.sess.run( + [self.m_min_eig, self.m_min_vec], feed_dict=feed_dict + ) + + else: + min_eig, min_vec = self.sess.run( + [self.h_min_eig, self.h_min_vec], feed_dict=feed_dict + ) + + return min_vec, min_eig + + def compute_certificate(self, current_step, feed_dictionary): + """Function to compute the certificate based either current value + or dual variables loaded from dual folder""" + feed_dict = feed_dictionary.copy() + nu = feed_dict[self.nu] + second_term = self.make_m_psd(nu, feed_dict) + tf.logging.info("Nu after modifying: " + str(second_term)) + feed_dict.update({self.nu: second_term}) + computed_certificate = self.sess.run( + self.unconstrained_objective, feed_dict=feed_dict + ) + + tf.logging.info( + "Inner step: %d, current value of certificate: %f", + current_step, + computed_certificate, + ) + + # Sometimes due to either overflow or instability in inverses, + # the returned certificate is large and negative -- keeping a check + if LOWER_CERT_BOUND < computed_certificate < 0: + _, min_eig_val_m = self.get_lanczos_eig(feed_dict=feed_dict) + tf.logging.info("min eig val from lanczos: " + str(min_eig_val_m)) + input_vector_m = tf.placeholder( + tf.float32, shape=(self.matrix_m_dimension, 1) + ) + output_vector_m = self.get_psd_product(input_vector_m) + + def np_vector_prod_fn_m(np_vector): + np_vector = np.reshape(np_vector, [-1, 1]) + feed_dict.update({input_vector_m: np_vector}) + output_np_vector = self.sess.run(output_vector_m, feed_dict=feed_dict) + return output_np_vector + + linear_operator_m = LinearOperator( + (self.matrix_m_dimension, self.matrix_m_dimension), + matvec=np_vector_prod_fn_m, + ) + # Performing shift invert scipy operation when eig val estimate is available + min_eig_val_m_scipy, _ = eigs(linear_operator_m, k=1, which="SR", tol=TOL) + + tf.logging.info("min eig val m from scipy: " + str(min_eig_val_m_scipy)) + + if min_eig_val_m - TOL > 0: + tf.logging.info("Found certificate of robustness!") + return True + + return False diff --git a/cleverhans/experimental/certification/nn.py b/cleverhans/experimental/certification/nn.py index cd3911876..585a15773 100644 --- a/cleverhans/experimental/certification/nn.py +++ b/cleverhans/experimental/certification/nn.py @@ -11,155 +11,189 @@ import numpy as np import tensorflow as tf + class NeuralNetwork(object): - """NeuralNetwork is a class that interfaces the verification code with - the neural net parameters (weights). - """ + """NeuralNetwork is a class that interfaces the verification code with + the neural net parameters (weights). + """ - def __init__(self, net_weights, net_biases, net_layer_types, input_shape=None, cnn_params=None): - """Function to initialize NeuralNetParams class. + def __init__( + self, + net_weights, + net_biases, + net_layer_types, + input_shape=None, + cnn_params=None, + ): + """Function to initialize NeuralNetParams class. - Args: - net_weights: list of numpy matrices of weights of each layer - [convention: x[i+1] = W[i] x[i] - net_biases: list of numpy arrays of biases of each layer - net_layer_types: type of each layer ['ff' or 'ff_relu' or 'ff_conv' or - 'ff_conv_relu'] + Args: + net_weights: list of numpy matrices of weights of each layer + [convention: x[i+1] = W[i] x[i] + net_biases: list of numpy arrays of biases of each layer + net_layer_types: type of each layer ['ff' or 'ff_relu' or 'ff_conv' or + 'ff_conv_relu'] - 'ff': Simple feedforward layer with no activations - 'ff_relu': Simple feedforward layer with ReLU activations - 'ff_conv': Convolution layer with no activation - 'ff_conv_relu': Convolution layer with ReLU activation - input_shape: [num_rows, num_columns, num_channels] at the input layer - cnn_params: list of dictionaries containing stride and padding for - each layer + 'ff': Simple feedforward layer with no activations + 'ff_relu': Simple feedforward layer with ReLU activations + 'ff_conv': Convolution layer with no activation + 'ff_conv_relu': Convolution layer with ReLU activation + input_shape: [num_rows, num_columns, num_channels] at the input layer + cnn_params: list of dictionaries containing stride and padding for + each layer - Raises: - ValueError: the input lists of net params are not of the same length - """ - if ((len(net_weights) != len(net_biases)) or - len(net_biases) != len(net_layer_types)): - raise ValueError('Inputs of net params are not of same length ....') - if net_layer_types[len(net_layer_types) - 1] != 'ff': - raise ValueError('Final layer is not linear') - self.num_hidden_layers = len(net_weights) - 1 - self.weights = [] - self.biases = [] - self.layer_types = [] - self.sizes = [] - self.input_shapes = [] - self.output_shapes = [] - self.has_conv = False - if(input_shape is not None): - current_num_rows = input_shape[0] - current_num_columns = input_shape[1] - current_num_channels = input_shape[2] - self.cnn_params = cnn_params - - # Setting the sizes of the layers of the network - # sizes[i] contains the size of x_i - for i in range(self.num_hidden_layers): - shape = np.shape(net_weights[i]) - self.weights.append( - tf.convert_to_tensor(net_weights[i], dtype=tf.float32)) - self.layer_types.append(net_layer_types[i]) - - if(self.layer_types[i] in {'ff', 'ff_relu'}): - self.sizes.append(int(shape[1])) - # For feedforward networks, no unraveling the bias terms - - small_bias = tf.convert_to_tensor(net_biases[i], dtype=tf.float32) - self.biases.append(tf.reshape(small_bias, [-1, 1])) - # Assumes that x^{i+1} = W_i x^i - self.input_shapes.append([int(shape[1]), 1]) - self.output_shapes.append([int(shape[0]), 1]) - - # Convolution type - else: - self.has_conv = True - num_filters = shape[3] - self.input_shapes.append([1, current_num_rows, current_num_columns, current_num_channels]) - self.sizes.append(current_num_rows*current_num_columns*current_num_channels) - current_num_channels = num_filters - # For propagating across multiple conv layers - if(self.cnn_params[i]['padding'] == 'SAME'): - current_num_rows = int(current_num_rows/self.cnn_params[i]['stride']) - current_num_columns = int(current_num_columns/self.cnn_params[i]['stride']) - self.output_shapes.append( - [1, current_num_rows, current_num_columns, current_num_channels]) - - # For conv networks, unraveling the bias terms - small_bias = tf.convert_to_tensor(net_biases[i], dtype=tf.float32) - large_bias = tf.tile(tf.reshape(small_bias, [-1, 1]), - [current_num_rows*current_num_columns, 1]) - self.biases.append(large_bias) - - # Last layer shape: always ff - if self.has_conv: - final_dim = int(np.shape(net_weights[self.num_hidden_layers])[1]) - self.input_shapes.append([final_dim, 1]) - - else: - final_dim = int(np.shape(net_weights[self.num_hidden_layers - 1])[0]) - - self.sizes.append(final_dim) - self.final_weights = tf.convert_to_tensor( - net_weights[self.num_hidden_layers], dtype=tf.float32) - self.final_bias = tf.convert_to_tensor( - net_biases[self.num_hidden_layers], dtype=tf.float32) - - def forward_pass(self, vector, layer_index, is_transpose=False, is_abs=False): - """Performs forward pass through the layer weights at layer_index. + Raises: + ValueError: the input lists of net params are not of the same length + """ + if (len(net_weights) != len(net_biases)) or len(net_biases) != len( + net_layer_types + ): + raise ValueError("Inputs of net params are not of same length ....") + if net_layer_types[len(net_layer_types) - 1] != "ff": + raise ValueError("Final layer is not linear") + self.num_hidden_layers = len(net_weights) - 1 + self.weights = [] + self.biases = [] + self.layer_types = [] + self.sizes = [] + self.input_shapes = [] + self.output_shapes = [] + self.has_conv = False + if input_shape is not None: + current_num_rows = input_shape[0] + current_num_columns = input_shape[1] + current_num_channels = input_shape[2] + self.cnn_params = cnn_params - Args: - vector: vector that has to be passed through in forward pass - layer_index: index of the layer - is_transpose: whether the weights of the layer have to be transposed - is_abs: whether to take the absolute value of the weights + # Setting the sizes of the layers of the network + # sizes[i] contains the size of x_i + for i in range(self.num_hidden_layers): + shape = np.shape(net_weights[i]) + self.weights.append(tf.convert_to_tensor(net_weights[i], dtype=tf.float32)) + self.layer_types.append(net_layer_types[i]) + + if self.layer_types[i] in {"ff", "ff_relu"}: + self.sizes.append(int(shape[1])) + # For feedforward networks, no unraveling the bias terms + + small_bias = tf.convert_to_tensor(net_biases[i], dtype=tf.float32) + self.biases.append(tf.reshape(small_bias, [-1, 1])) + # Assumes that x^{i+1} = W_i x^i + self.input_shapes.append([int(shape[1]), 1]) + self.output_shapes.append([int(shape[0]), 1]) + + # Convolution type + else: + self.has_conv = True + num_filters = shape[3] + self.input_shapes.append( + [1, current_num_rows, current_num_columns, current_num_channels] + ) + self.sizes.append( + current_num_rows * current_num_columns * current_num_channels + ) + current_num_channels = num_filters + # For propagating across multiple conv layers + if self.cnn_params[i]["padding"] == "SAME": + current_num_rows = int( + current_num_rows / self.cnn_params[i]["stride"] + ) + current_num_columns = int( + current_num_columns / self.cnn_params[i]["stride"] + ) + self.output_shapes.append( + [1, current_num_rows, current_num_columns, current_num_channels] + ) + + # For conv networks, unraveling the bias terms + small_bias = tf.convert_to_tensor(net_biases[i], dtype=tf.float32) + large_bias = tf.tile( + tf.reshape(small_bias, [-1, 1]), + [current_num_rows * current_num_columns, 1], + ) + self.biases.append(large_bias) + + # Last layer shape: always ff + if self.has_conv: + final_dim = int(np.shape(net_weights[self.num_hidden_layers])[1]) + self.input_shapes.append([final_dim, 1]) + + else: + final_dim = int(np.shape(net_weights[self.num_hidden_layers - 1])[0]) + + self.sizes.append(final_dim) + self.final_weights = tf.convert_to_tensor( + net_weights[self.num_hidden_layers], dtype=tf.float32 + ) + self.final_bias = tf.convert_to_tensor( + net_biases[self.num_hidden_layers], dtype=tf.float32 + ) + + def forward_pass(self, vector, layer_index, is_transpose=False, is_abs=False): + """Performs forward pass through the layer weights at layer_index. + + Args: + vector: vector that has to be passed through in forward pass + layer_index: index of the layer + is_transpose: whether the weights of the layer have to be transposed + is_abs: whether to take the absolute value of the weights + + Returns: + tensor that corresponds to the forward pass through the layer + Raises: + ValueError: if the layer_index is negative or more than num hidden layers + """ + if layer_index < 0 or layer_index > self.num_hidden_layers: + raise ValueError("Invalid layer index") + + layer_type = self.layer_types[layer_index] + weight = self.weights[layer_index] + if is_abs: + weight = tf.abs(weight) + if is_transpose: + vector = tf.reshape(vector, self.output_shapes[layer_index]) + else: + vector = tf.reshape(vector, self.input_shapes[layer_index]) + + if layer_type in {"ff", "ff_relu"}: + if is_transpose: + weight = tf.transpose(weight) + return_vector = tf.matmul(weight, vector) + elif layer_type in {"conv", "conv_relu"}: + if is_transpose: + return_vector = tf.nn.conv2d_transpose( + vector, + weight, + output_shape=self.input_shapes[layer_index], + strides=[ + 1, + self.cnn_params[layer_index]["stride"], + self.cnn_params[layer_index]["stride"], + 1, + ], + padding=self.cnn_params[layer_index]["padding"], + ) + else: + return_vector = tf.nn.conv2d( + vector, + weight, + strides=[ + 1, + self.cnn_params[layer_index]["stride"], + self.cnn_params[layer_index]["stride"], + 1, + ], + padding=self.cnn_params[layer_index]["padding"], + ) + else: + raise NotImplementedError("Unsupported layer type: {0}".format(layer_type)) + if is_transpose: + return tf.reshape(return_vector, (self.sizes[layer_index], 1)) + return tf.reshape(return_vector, (self.sizes[layer_index + 1], 1)) - Returns: - tensor that corresponds to the forward pass through the layer - Raises: - ValueError: if the layer_index is negative or more than num hidden layers - """ - if(layer_index < 0 or layer_index > self.num_hidden_layers): - raise ValueError('Invalid layer index') - - layer_type = self.layer_types[layer_index] - weight = self.weights[layer_index] - if is_abs: - weight = tf.abs(weight) - if is_transpose: - vector = tf.reshape(vector, self.output_shapes[layer_index]) - else: - vector = tf.reshape(vector, self.input_shapes[layer_index]) - - if layer_type in {'ff', 'ff_relu'}: - if is_transpose: - weight = tf.transpose(weight) - return_vector = tf.matmul(weight, vector) - elif layer_type in {'conv', 'conv_relu'}: - if is_transpose: - return_vector = tf.nn.conv2d_transpose(vector, - weight, - output_shape=self.input_shapes[layer_index], - strides=[1, self.cnn_params[layer_index]['stride'], - self.cnn_params[layer_index]['stride'], 1], - padding=self.cnn_params[layer_index]['padding']) - else: - return_vector = tf.nn.conv2d(vector, - weight, - strides=[1, self.cnn_params[layer_index]['stride'], - self.cnn_params[layer_index]['stride'], 1], - padding=self.cnn_params[layer_index]['padding']) - else: - raise NotImplementedError('Unsupported layer type: {0}'.format(layer_type)) - if is_transpose: - return tf.reshape(return_vector, (self.sizes[layer_index], 1)) - return tf.reshape(return_vector, (self.sizes[layer_index + 1], 1)) def load_network_from_checkpoint(checkpoint, model_json, input_shape=None): - """Function to read the weights from checkpoint based on json description. + """Function to read the weights from checkpoint based on json description. Args: checkpoint: tensorflow checkpoint with trained model to @@ -186,41 +220,50 @@ def load_network_from_checkpoint(checkpoint, model_json, input_shape=None): Raises: ValueError: If layer_types are invalid or variable names not found in checkpoint - """ - # Load checkpoint - reader = tf.train.load_checkpoint(checkpoint) - variable_map = reader.get_variable_to_shape_map() - checkpoint_variable_names = variable_map.keys() - # Parse JSON file for names - with tf.gfile.Open(model_json) as f: - list_model_var = json.load(f) - - net_layer_types = [] - net_weights = [] - net_biases = [] - cnn_params = [] - - # Checking validity of the input and adding to list - for layer_model_var in list_model_var: - if layer_model_var['type'] not in {'ff', 'ff_relu', 'conv'}: - raise ValueError('Invalid layer type in description') - if (layer_model_var['weight_var'] not in checkpoint_variable_names or - layer_model_var['bias_var'] not in checkpoint_variable_names): - raise ValueError('Variable names not found in checkpoint') - net_layer_types.append(layer_model_var['type']) - layer_weight = reader.get_tensor(layer_model_var['weight_var']) - layer_bias = reader.get_tensor(layer_model_var['bias_var']) - # TODO(aditirag): is there a way to automatically check when to transpose - # We want weights W such that x^{i+1} = W^i x^i + b^i - # Can think of a hack involving matching shapes but if shapes are equal - # it can be ambiguous - if layer_model_var['type'] in {'ff', 'ff_relu'}: - layer_weight = np.transpose(layer_weight) - cnn_params.append(None) - if layer_model_var['type'] in {'conv'}: - if 'stride' not in layer_model_var or 'padding' not in layer_model_var: - raise ValueError('Please define stride and padding for conv layers.') - cnn_params.append({'stride': layer_model_var['stride'], 'padding': layer_model_var['padding']}) - net_weights.append(layer_weight) - net_biases.append(np.reshape(layer_bias, (np.size(layer_bias), 1))) - return NeuralNetwork(net_weights, net_biases, net_layer_types, input_shape, cnn_params) + """ + # Load checkpoint + reader = tf.train.load_checkpoint(checkpoint) + variable_map = reader.get_variable_to_shape_map() + checkpoint_variable_names = variable_map.keys() + # Parse JSON file for names + with tf.gfile.Open(model_json) as f: + list_model_var = json.load(f) + + net_layer_types = [] + net_weights = [] + net_biases = [] + cnn_params = [] + + # Checking validity of the input and adding to list + for layer_model_var in list_model_var: + if layer_model_var["type"] not in {"ff", "ff_relu", "conv"}: + raise ValueError("Invalid layer type in description") + if ( + layer_model_var["weight_var"] not in checkpoint_variable_names + or layer_model_var["bias_var"] not in checkpoint_variable_names + ): + raise ValueError("Variable names not found in checkpoint") + net_layer_types.append(layer_model_var["type"]) + layer_weight = reader.get_tensor(layer_model_var["weight_var"]) + layer_bias = reader.get_tensor(layer_model_var["bias_var"]) + # TODO(aditirag): is there a way to automatically check when to transpose + # We want weights W such that x^{i+1} = W^i x^i + b^i + # Can think of a hack involving matching shapes but if shapes are equal + # it can be ambiguous + if layer_model_var["type"] in {"ff", "ff_relu"}: + layer_weight = np.transpose(layer_weight) + cnn_params.append(None) + if layer_model_var["type"] in {"conv"}: + if "stride" not in layer_model_var or "padding" not in layer_model_var: + raise ValueError("Please define stride and padding for conv layers.") + cnn_params.append( + { + "stride": layer_model_var["stride"], + "padding": layer_model_var["padding"], + } + ) + net_weights.append(layer_weight) + net_biases.append(np.reshape(layer_bias, (np.size(layer_bias), 1))) + return NeuralNetwork( + net_weights, net_biases, net_layer_types, input_shape, cnn_params + ) diff --git a/cleverhans/experimental/certification/optimization.py b/cleverhans/experimental/certification/optimization.py index 6da552630..edc9b87f7 100644 --- a/cleverhans/experimental/certification/optimization.py +++ b/cleverhans/experimental/certification/optimization.py @@ -15,333 +15,410 @@ UPDATE_PARAM_CONSTANT = -0.1 # Tolerance value for eigenvalue computation -TOL = 1E-5 +TOL = 1e-5 + class Optimization(object): - """Class that sets up and runs the optimization of dual_formulation""" - - def __init__(self, dual_formulation_object, sess, optimization_params): - """Initialize the class variables. - - Args: - dual_formulation_object: Instance of DualFormulation that contains the - dual variables and objective - sess: tf session to be used to run - optimization_params: Dictionary with the following - eig_num_iter - Number of iterations to run for computing minimum eigen - value - eig_learning_rate - Learning rate for minimum eigen value iterations - init_smooth - Starting value of the smoothness parameter (typically - around 0.001) - smooth_decay - The factor by which to decay after every outer loop epoch - optimizer - one of gd, adam, momentum or adagrad - eig_type - The method to compute eigenvalues (TF or SCIPY) - """ - self.sess = sess - self.dual_object = dual_formulation_object - self.params = optimization_params - self.penalty_placeholder = tf.placeholder(tf.float32, shape=[]) - - # The dimensionality of matrix M is the sum of sizes of all layers + 1 - # The + 1 comes due to a row and column of M representing the linear terms - self.eig_init_vec_placeholder = tf.placeholder( - tf.float32, shape=[1 + self.dual_object.dual_index[-1], 1]) - self.smooth_placeholder = tf.placeholder(tf.float32, shape=[]) - self.eig_num_iter_placeholder = tf.placeholder(tf.int32, shape=[]) - self.current_eig_val_estimate = None - - # Create graph for optimization - self.prepare_for_optimization() - - def tf_min_eig_vec(self): - """Function for min eigen vector using tf's full eigen decomposition.""" - # Full eigen decomposition requires the explicit psd matrix M - _, matrix_m = self.dual_object.get_full_psd_matrix() - [eig_vals, eig_vectors] = tf.self_adjoint_eig(matrix_m) - index = tf.argmin(eig_vals) - return tf.reshape( - eig_vectors[:, index], shape=[eig_vectors.shape[0].value, 1]) - - def tf_smooth_eig_vec(self): - """Function that returns smoothed version of min eigen vector.""" - _, matrix_m = self.dual_object.get_full_psd_matrix() - # Easier to think in terms of max so negating the matrix - [eig_vals, eig_vectors] = tf.self_adjoint_eig(-matrix_m) - exp_eig_vals = tf.exp(tf.divide(eig_vals, self.smooth_placeholder)) - scaling_factor = tf.reduce_sum(exp_eig_vals) - # Multiplying each eig vector by exponential of corresponding eig value - # Scaling factor normalizes the vector to be unit norm - eig_vec_smooth = tf.divide( - tf.matmul(eig_vectors, tf.diag(tf.sqrt(exp_eig_vals))), - tf.sqrt(scaling_factor)) - return tf.reshape( - tf.reduce_sum(eig_vec_smooth, axis=1), - shape=[eig_vec_smooth.shape[0].value, 1]) - - def get_min_eig_vec_proxy(self, use_tf_eig=False): - """Computes the min eigen value and corresponding vector of matrix M. - - Args: - use_tf_eig: Whether to use tf's default full eigen decomposition - Returns: - eig_vec: Minimum absolute eigen value - eig_val: Corresponding eigen vector - """ - if use_tf_eig: - # If smoothness parameter is too small, essentially no smoothing - # Just output the eigen vector corresponding to min - return tf.cond(self.smooth_placeholder < 1E-8, - self.tf_min_eig_vec, - self.tf_smooth_eig_vec) - - # Using autograph to automatically handle - # the control flow of minimum_eigen_vector - min_eigen_tf = autograph.to_graph(utils.minimum_eigen_vector) - - def _vector_prod_fn(x): - return self.dual_object.get_psd_product(x) - - estimated_eigen_vector = min_eigen_tf( - x=self.eig_init_vec_placeholder, - num_steps=self.eig_num_iter_placeholder, - learning_rate=self.params['eig_learning_rate'], - vector_prod_fn=_vector_prod_fn) - return estimated_eigen_vector - - def get_scipy_eig_vec(self): - """Computes scipy estimate of min eigenvalue for matrix M. - - Returns: - eig_vec: Minimum absolute eigen value - eig_val: Corresponding eigen vector - """ - if not self.params['has_conv']: - matrix_m = self.sess.run(self.dual_object.matrix_m) - min_eig_vec_val, estimated_eigen_vector = eigs(matrix_m, k=1, which='SR', - tol=1E-4) - min_eig_vec_val = np.reshape(np.real(min_eig_vec_val), [1, 1]) - return np.reshape(estimated_eigen_vector, [-1, 1]), min_eig_vec_val - else: - dim = self.dual_object.matrix_m_dimension - input_vector = tf.placeholder(tf.float32, shape=(dim, 1)) - output_vector = self.dual_object.get_psd_product(input_vector) - - def np_vector_prod_fn(np_vector): - np_vector = np.reshape(np_vector, [-1, 1]) - output_np_vector = self.sess.run(output_vector, feed_dict={input_vector:np_vector}) - return output_np_vector - linear_operator = LinearOperator((dim, dim), matvec=np_vector_prod_fn) - # Performing shift invert scipy operation when eig val estimate is available - min_eig_vec_val, estimated_eigen_vector = eigs(linear_operator, - k=1, which='SR', tol=1E-4) - min_eig_vec_val = np.reshape(np.real(min_eig_vec_val), [1, 1]) - return np.reshape(estimated_eigen_vector, [-1, 1]), min_eig_vec_val - - def prepare_for_optimization(self): - """Create tensorflow op for running one step of descent.""" - if self.params['eig_type'] == 'TF': - self.eig_vec_estimate = self.get_min_eig_vec_proxy() - elif self.params['eig_type'] == 'LZS': - self.eig_vec_estimate = self.dual_object.m_min_vec - else: - self.eig_vec_estimate = tf.placeholder(tf.float32, shape=(self.dual_object.matrix_m_dimension, 1)) - self.stopped_eig_vec_estimate = tf.stop_gradient(self.eig_vec_estimate) - # Eig value is v^\top M v, where v is eigen vector - self.eig_val_estimate = tf.matmul( - tf.transpose(self.stopped_eig_vec_estimate), - self.dual_object.get_psd_product(self.stopped_eig_vec_estimate)) - # Penalizing negative of min eigen value because we want min eig value - # to be positive - self.total_objective = ( - self.dual_object.unconstrained_objective - + 0.5 * tf.square( - tf.maximum(-self.penalty_placeholder * self.eig_val_estimate, 0))) - global_step = tf.Variable(0, trainable=False) - # Set up learning rate as a placeholder - self.learning_rate = tf.placeholder(tf.float32, shape=[]) - - # Set up the optimizer - if self.params['optimizer'] == 'adam': - self.optimizer = tf.train.AdamOptimizer(learning_rate=self.learning_rate) - elif self.params['optimizer'] == 'adagrad': - self.optimizer = tf.train.AdagradOptimizer(learning_rate=self.learning_rate) - elif self.params['optimizer'] == 'momentum': - self.optimizer = tf.train.MomentumOptimizer( - learning_rate=self.learning_rate, - momentum=self.params['momentum_parameter'], - use_nesterov=True) - else: - self.optimizer = tf.train.GradientDescentOptimizer( - learning_rate=self.learning_rate) - - # Write out the projection step - self.train_step = self.optimizer.minimize( - self.total_objective, global_step=global_step) - - self.sess.run(tf.global_variables_initializer()) - - # Projecting the dual variables - proj_ops = [] - for i in range(self.dual_object.nn_params.num_hidden_layers + 1): - # Lambda_pos is non negative for switch indices, - # Unconstrained for positive indices - # Zero for negative indices - proj_ops.append(self.dual_object.lambda_pos[i].assign( - tf.multiply(self.dual_object.positive_indices[i], - self.dual_object.lambda_pos[i])+ - tf.multiply(self.dual_object.switch_indices[i], - tf.nn.relu(self.dual_object.lambda_pos[i])))) - proj_ops.append(self.dual_object.lambda_neg[i].assign( - tf.multiply(self.dual_object.negative_indices[i], - self.dual_object.lambda_neg[i])+ - tf.multiply(self.dual_object.switch_indices[i], - tf.nn.relu(self.dual_object.lambda_neg[i])))) - # Lambda_quad is only non zero and positive for switch - proj_ops.append(self.dual_object.lambda_quad[i].assign( - tf.multiply(self.dual_object.switch_indices[i], - tf.nn.relu(self.dual_object.lambda_quad[i])))) - # Lambda_lu is always non negative - proj_ops.append(self.dual_object.lambda_lu[i].assign( - tf.nn.relu(self.dual_object.lambda_lu[i]))) - - self.proj_step = tf.group(proj_ops) - - # Create folder for saving stats if the folder is not None - if (self.params.get('stats_folder') and - not tf.gfile.IsDirectory(self.params['stats_folder'])): - tf.gfile.MkDir(self.params['stats_folder']) - - def run_one_step(self, eig_init_vec_val, eig_num_iter_val, smooth_val, - penalty_val, learning_rate_val): - """Run one step of gradient descent for optimization. - - Args: - eig_init_vec_val: Start value for eigen value computations - eig_num_iter_val: Number of iterations to run for eigen computations - smooth_val: Value of smoothness parameter - penalty_val: Value of penalty for the current step - learning_rate_val: Value of learning rate - Returns: - found_cert: True is negative certificate is found, False otherwise - """ - # Running step - step_feed_dict = {self.eig_init_vec_placeholder: eig_init_vec_val, - self.eig_num_iter_placeholder: eig_num_iter_val, - self.smooth_placeholder: smooth_val, - self.penalty_placeholder: penalty_val, - self.learning_rate: learning_rate_val} - - if self.params['eig_type'] == 'SCIPY': - current_eig_vector, self.current_eig_val_estimate = self.get_scipy_eig_vec() - step_feed_dict.update({ - self.eig_vec_estimate: current_eig_vector - }) - elif self.params['eig_type'] == 'LZS': - step_feed_dict.update({ - self.dual_object.m_min_vec_ph: self.dual_object.m_min_vec_estimate - }) - - self.sess.run(self.train_step, feed_dict=step_feed_dict) - - [ - _, self.dual_object.m_min_vec_estimate, self.current_eig_val_estimate - ] = self.sess.run([ - self.proj_step, - self.eig_vec_estimate, - self.eig_val_estimate - ], feed_dict=step_feed_dict) - - if self.current_step % self.params['print_stats_steps'] == 0: - [self.current_total_objective, self.current_unconstrained_objective, - self.dual_object.m_min_vec_estimate, - self.current_eig_val_estimate, - self.current_nu] = self.sess.run( - [self.total_objective, - self.dual_object.unconstrained_objective, - self.eig_vec_estimate, - self.eig_val_estimate, - self.dual_object.nu], feed_dict=step_feed_dict) - - stats = { - 'total_objective': - float(self.current_total_objective), - 'unconstrained_objective': - float(self.current_unconstrained_objective), - 'min_eig_val_estimate': - float(self.current_eig_val_estimate) - } - tf.logging.info('Current inner step: %d, optimization stats: %s', - self.current_step, stats) - if self.params['stats_folder'] is not None: - stats = json.dumps(stats) - filename = os.path.join(self.params['stats_folder'], - str(self.current_step) + '.json') - with tf.gfile.Open(filename) as file_f: - file_f.write(stats) - - # Project onto feasible set of dual variables - if self.current_step % self.params['projection_steps'] == 0 and self.current_unconstrained_objective < 0: - nu = self.sess.run(self.dual_object.nu) - dual_feed_dict = { - self.dual_object.h_min_vec_ph: self.dual_object.h_min_vec_estimate - } - _, min_eig_val_h_lz = self.dual_object.get_lanczos_eig(compute_m=False, feed_dict=dual_feed_dict) - projected_dual_feed_dict = { - self.dual_object.projected_dual.nu: nu, - self.dual_object.projected_dual.min_eig_val_h: min_eig_val_h_lz - } - if self.dual_object.projected_dual.compute_certificate(self.current_step, projected_dual_feed_dict): - return True - - return False - - def run_optimization(self): - """Run the optimization, call run_one_step with suitable placeholders. - - Returns: - True if certificate is found - False otherwise - """ - penalty_val = self.params['init_penalty'] - # Don't use smoothing initially - very inaccurate for large dimension - self.smooth_on = False - smooth_val = 0 - learning_rate_val = self.params['init_learning_rate'] - self.current_outer_step = 1 - - - while self.current_outer_step <= self.params['outer_num_steps']: - tf.logging.info('Running outer step %d with penalty %f', - self.current_outer_step, penalty_val) - # Running inner loop of optimization with current_smooth_val, - # current_penalty as smoothness parameters and penalty respectively - self.current_step = 0 - # Run first step with random eig initialization and large number of steps - found_cert = self.run_one_step( - self.dual_object.m_min_vec_estimate, - self.params['large_eig_num_steps'], smooth_val, penalty_val, learning_rate_val) - if found_cert: - return True - while self.current_step < self.params['inner_num_steps']: - self.current_step = self.current_step + 1 - found_cert = self.run_one_step(self.dual_object.m_min_vec_estimate, - self.params['small_eig_num_steps'], - smooth_val, penalty_val, - learning_rate_val) - if found_cert: - return True - # Update penalty only if it looks like current objective is optimizes - if self.current_total_objective < UPDATE_PARAM_CONSTANT: - penalty_val = penalty_val * self.params['beta'] - learning_rate_val = learning_rate_val*self.params['learning_rate_decay'] - else: - # To get more accurate gradient estimate - self.params['small_eig_num_steps'] = ( - 1.5 * self.params['small_eig_num_steps']) - - # If eigen values seem small enough, turn on smoothing - # useful only when performing full eigen decomposition - if np.abs(self.current_eig_val_estimate) < 0.01: - smooth_val = self.params['smoothness_parameter'] - self.current_outer_step = self.current_outer_step + 1 - return False + """Class that sets up and runs the optimization of dual_formulation""" + + def __init__(self, dual_formulation_object, sess, optimization_params): + """Initialize the class variables. + + Args: + dual_formulation_object: Instance of DualFormulation that contains the + dual variables and objective + sess: tf session to be used to run + optimization_params: Dictionary with the following + eig_num_iter - Number of iterations to run for computing minimum eigen + value + eig_learning_rate - Learning rate for minimum eigen value iterations + init_smooth - Starting value of the smoothness parameter (typically + around 0.001) + smooth_decay - The factor by which to decay after every outer loop epoch + optimizer - one of gd, adam, momentum or adagrad + eig_type - The method to compute eigenvalues (TF or SCIPY) + """ + self.sess = sess + self.dual_object = dual_formulation_object + self.params = optimization_params + self.penalty_placeholder = tf.placeholder(tf.float32, shape=[]) + + # The dimensionality of matrix M is the sum of sizes of all layers + 1 + # The + 1 comes due to a row and column of M representing the linear terms + self.eig_init_vec_placeholder = tf.placeholder( + tf.float32, shape=[1 + self.dual_object.dual_index[-1], 1] + ) + self.smooth_placeholder = tf.placeholder(tf.float32, shape=[]) + self.eig_num_iter_placeholder = tf.placeholder(tf.int32, shape=[]) + self.current_eig_val_estimate = None + + # Create graph for optimization + self.prepare_for_optimization() + + def tf_min_eig_vec(self): + """Function for min eigen vector using tf's full eigen decomposition.""" + # Full eigen decomposition requires the explicit psd matrix M + _, matrix_m = self.dual_object.get_full_psd_matrix() + [eig_vals, eig_vectors] = tf.self_adjoint_eig(matrix_m) + index = tf.argmin(eig_vals) + return tf.reshape(eig_vectors[:, index], shape=[eig_vectors.shape[0].value, 1]) + + def tf_smooth_eig_vec(self): + """Function that returns smoothed version of min eigen vector.""" + _, matrix_m = self.dual_object.get_full_psd_matrix() + # Easier to think in terms of max so negating the matrix + [eig_vals, eig_vectors] = tf.self_adjoint_eig(-matrix_m) + exp_eig_vals = tf.exp(tf.divide(eig_vals, self.smooth_placeholder)) + scaling_factor = tf.reduce_sum(exp_eig_vals) + # Multiplying each eig vector by exponential of corresponding eig value + # Scaling factor normalizes the vector to be unit norm + eig_vec_smooth = tf.divide( + tf.matmul(eig_vectors, tf.diag(tf.sqrt(exp_eig_vals))), + tf.sqrt(scaling_factor), + ) + return tf.reshape( + tf.reduce_sum(eig_vec_smooth, axis=1), + shape=[eig_vec_smooth.shape[0].value, 1], + ) + + def get_min_eig_vec_proxy(self, use_tf_eig=False): + """Computes the min eigen value and corresponding vector of matrix M. + + Args: + use_tf_eig: Whether to use tf's default full eigen decomposition + Returns: + eig_vec: Minimum absolute eigen value + eig_val: Corresponding eigen vector + """ + if use_tf_eig: + # If smoothness parameter is too small, essentially no smoothing + # Just output the eigen vector corresponding to min + return tf.cond( + self.smooth_placeholder < 1e-8, + self.tf_min_eig_vec, + self.tf_smooth_eig_vec, + ) + + # Using autograph to automatically handle + # the control flow of minimum_eigen_vector + min_eigen_tf = autograph.to_graph(utils.minimum_eigen_vector) + + def _vector_prod_fn(x): + return self.dual_object.get_psd_product(x) + + estimated_eigen_vector = min_eigen_tf( + x=self.eig_init_vec_placeholder, + num_steps=self.eig_num_iter_placeholder, + learning_rate=self.params["eig_learning_rate"], + vector_prod_fn=_vector_prod_fn, + ) + return estimated_eigen_vector + + def get_scipy_eig_vec(self): + """Computes scipy estimate of min eigenvalue for matrix M. + + Returns: + eig_vec: Minimum absolute eigen value + eig_val: Corresponding eigen vector + """ + if not self.params["has_conv"]: + matrix_m = self.sess.run(self.dual_object.matrix_m) + min_eig_vec_val, estimated_eigen_vector = eigs( + matrix_m, k=1, which="SR", tol=1e-4 + ) + min_eig_vec_val = np.reshape(np.real(min_eig_vec_val), [1, 1]) + return np.reshape(estimated_eigen_vector, [-1, 1]), min_eig_vec_val + else: + dim = self.dual_object.matrix_m_dimension + input_vector = tf.placeholder(tf.float32, shape=(dim, 1)) + output_vector = self.dual_object.get_psd_product(input_vector) + + def np_vector_prod_fn(np_vector): + np_vector = np.reshape(np_vector, [-1, 1]) + output_np_vector = self.sess.run( + output_vector, feed_dict={input_vector: np_vector} + ) + return output_np_vector + + linear_operator = LinearOperator((dim, dim), matvec=np_vector_prod_fn) + # Performing shift invert scipy operation when eig val estimate is available + min_eig_vec_val, estimated_eigen_vector = eigs( + linear_operator, k=1, which="SR", tol=1e-4 + ) + min_eig_vec_val = np.reshape(np.real(min_eig_vec_val), [1, 1]) + return np.reshape(estimated_eigen_vector, [-1, 1]), min_eig_vec_val + + def prepare_for_optimization(self): + """Create tensorflow op for running one step of descent.""" + if self.params["eig_type"] == "TF": + self.eig_vec_estimate = self.get_min_eig_vec_proxy() + elif self.params["eig_type"] == "LZS": + self.eig_vec_estimate = self.dual_object.m_min_vec + else: + self.eig_vec_estimate = tf.placeholder( + tf.float32, shape=(self.dual_object.matrix_m_dimension, 1) + ) + self.stopped_eig_vec_estimate = tf.stop_gradient(self.eig_vec_estimate) + # Eig value is v^\top M v, where v is eigen vector + self.eig_val_estimate = tf.matmul( + tf.transpose(self.stopped_eig_vec_estimate), + self.dual_object.get_psd_product(self.stopped_eig_vec_estimate), + ) + # Penalizing negative of min eigen value because we want min eig value + # to be positive + self.total_objective = ( + self.dual_object.unconstrained_objective + + 0.5 + * tf.square( + tf.maximum(-self.penalty_placeholder * self.eig_val_estimate, 0) + ) + ) + global_step = tf.Variable(0, trainable=False) + # Set up learning rate as a placeholder + self.learning_rate = tf.placeholder(tf.float32, shape=[]) + + # Set up the optimizer + if self.params["optimizer"] == "adam": + self.optimizer = tf.train.AdamOptimizer(learning_rate=self.learning_rate) + elif self.params["optimizer"] == "adagrad": + self.optimizer = tf.train.AdagradOptimizer(learning_rate=self.learning_rate) + elif self.params["optimizer"] == "momentum": + self.optimizer = tf.train.MomentumOptimizer( + learning_rate=self.learning_rate, + momentum=self.params["momentum_parameter"], + use_nesterov=True, + ) + else: + self.optimizer = tf.train.GradientDescentOptimizer( + learning_rate=self.learning_rate + ) + + # Write out the projection step + self.train_step = self.optimizer.minimize( + self.total_objective, global_step=global_step + ) + + self.sess.run(tf.global_variables_initializer()) + + # Projecting the dual variables + proj_ops = [] + for i in range(self.dual_object.nn_params.num_hidden_layers + 1): + # Lambda_pos is non negative for switch indices, + # Unconstrained for positive indices + # Zero for negative indices + proj_ops.append( + self.dual_object.lambda_pos[i].assign( + tf.multiply( + self.dual_object.positive_indices[i], + self.dual_object.lambda_pos[i], + ) + + tf.multiply( + self.dual_object.switch_indices[i], + tf.nn.relu(self.dual_object.lambda_pos[i]), + ) + ) + ) + proj_ops.append( + self.dual_object.lambda_neg[i].assign( + tf.multiply( + self.dual_object.negative_indices[i], + self.dual_object.lambda_neg[i], + ) + + tf.multiply( + self.dual_object.switch_indices[i], + tf.nn.relu(self.dual_object.lambda_neg[i]), + ) + ) + ) + # Lambda_quad is only non zero and positive for switch + proj_ops.append( + self.dual_object.lambda_quad[i].assign( + tf.multiply( + self.dual_object.switch_indices[i], + tf.nn.relu(self.dual_object.lambda_quad[i]), + ) + ) + ) + # Lambda_lu is always non negative + proj_ops.append( + self.dual_object.lambda_lu[i].assign( + tf.nn.relu(self.dual_object.lambda_lu[i]) + ) + ) + + self.proj_step = tf.group(proj_ops) + + # Create folder for saving stats if the folder is not None + if self.params.get("stats_folder") and not tf.gfile.IsDirectory( + self.params["stats_folder"] + ): + tf.gfile.MkDir(self.params["stats_folder"]) + + def run_one_step( + self, + eig_init_vec_val, + eig_num_iter_val, + smooth_val, + penalty_val, + learning_rate_val, + ): + """Run one step of gradient descent for optimization. + + Args: + eig_init_vec_val: Start value for eigen value computations + eig_num_iter_val: Number of iterations to run for eigen computations + smooth_val: Value of smoothness parameter + penalty_val: Value of penalty for the current step + learning_rate_val: Value of learning rate + Returns: + found_cert: True is negative certificate is found, False otherwise + """ + # Running step + step_feed_dict = { + self.eig_init_vec_placeholder: eig_init_vec_val, + self.eig_num_iter_placeholder: eig_num_iter_val, + self.smooth_placeholder: smooth_val, + self.penalty_placeholder: penalty_val, + self.learning_rate: learning_rate_val, + } + + if self.params["eig_type"] == "SCIPY": + current_eig_vector, self.current_eig_val_estimate = self.get_scipy_eig_vec() + step_feed_dict.update({self.eig_vec_estimate: current_eig_vector}) + elif self.params["eig_type"] == "LZS": + step_feed_dict.update( + {self.dual_object.m_min_vec_ph: self.dual_object.m_min_vec_estimate} + ) + + self.sess.run(self.train_step, feed_dict=step_feed_dict) + + [ + _, + self.dual_object.m_min_vec_estimate, + self.current_eig_val_estimate, + ] = self.sess.run( + [self.proj_step, self.eig_vec_estimate, self.eig_val_estimate], + feed_dict=step_feed_dict, + ) + + if self.current_step % self.params["print_stats_steps"] == 0: + [ + self.current_total_objective, + self.current_unconstrained_objective, + self.dual_object.m_min_vec_estimate, + self.current_eig_val_estimate, + self.current_nu, + ] = self.sess.run( + [ + self.total_objective, + self.dual_object.unconstrained_objective, + self.eig_vec_estimate, + self.eig_val_estimate, + self.dual_object.nu, + ], + feed_dict=step_feed_dict, + ) + + stats = { + "total_objective": float(self.current_total_objective), + "unconstrained_objective": float(self.current_unconstrained_objective), + "min_eig_val_estimate": float(self.current_eig_val_estimate), + } + tf.logging.info( + "Current inner step: %d, optimization stats: %s", + self.current_step, + stats, + ) + if self.params["stats_folder"] is not None: + stats = json.dumps(stats) + filename = os.path.join( + self.params["stats_folder"], str(self.current_step) + ".json" + ) + with tf.gfile.Open(filename) as file_f: + file_f.write(stats) + + # Project onto feasible set of dual variables + if ( + self.current_step % self.params["projection_steps"] == 0 + and self.current_unconstrained_objective < 0 + ): + nu = self.sess.run(self.dual_object.nu) + dual_feed_dict = { + self.dual_object.h_min_vec_ph: self.dual_object.h_min_vec_estimate + } + _, min_eig_val_h_lz = self.dual_object.get_lanczos_eig( + compute_m=False, feed_dict=dual_feed_dict + ) + projected_dual_feed_dict = { + self.dual_object.projected_dual.nu: nu, + self.dual_object.projected_dual.min_eig_val_h: min_eig_val_h_lz, + } + if self.dual_object.projected_dual.compute_certificate( + self.current_step, projected_dual_feed_dict + ): + return True + + return False + + def run_optimization(self): + """Run the optimization, call run_one_step with suitable placeholders. + + Returns: + True if certificate is found + False otherwise + """ + penalty_val = self.params["init_penalty"] + # Don't use smoothing initially - very inaccurate for large dimension + self.smooth_on = False + smooth_val = 0 + learning_rate_val = self.params["init_learning_rate"] + self.current_outer_step = 1 + + while self.current_outer_step <= self.params["outer_num_steps"]: + tf.logging.info( + "Running outer step %d with penalty %f", + self.current_outer_step, + penalty_val, + ) + # Running inner loop of optimization with current_smooth_val, + # current_penalty as smoothness parameters and penalty respectively + self.current_step = 0 + # Run first step with random eig initialization and large number of steps + found_cert = self.run_one_step( + self.dual_object.m_min_vec_estimate, + self.params["large_eig_num_steps"], + smooth_val, + penalty_val, + learning_rate_val, + ) + if found_cert: + return True + while self.current_step < self.params["inner_num_steps"]: + self.current_step = self.current_step + 1 + found_cert = self.run_one_step( + self.dual_object.m_min_vec_estimate, + self.params["small_eig_num_steps"], + smooth_val, + penalty_val, + learning_rate_val, + ) + if found_cert: + return True + # Update penalty only if it looks like current objective is optimizes + if self.current_total_objective < UPDATE_PARAM_CONSTANT: + penalty_val = penalty_val * self.params["beta"] + learning_rate_val = ( + learning_rate_val * self.params["learning_rate_decay"] + ) + else: + # To get more accurate gradient estimate + self.params["small_eig_num_steps"] = ( + 1.5 * self.params["small_eig_num_steps"] + ) + + # If eigen values seem small enough, turn on smoothing + # useful only when performing full eigen decomposition + if np.abs(self.current_eig_val_estimate) < 0.01: + smooth_val = self.params["smoothness_parameter"] + self.current_outer_step = self.current_outer_step + 1 + return False diff --git a/cleverhans/experimental/certification/tests/dual_formulation_test.py b/cleverhans/experimental/certification/tests/dual_formulation_test.py index 768095ece..fcaed1a5b 100644 --- a/cleverhans/experimental/certification/tests/dual_formulation_test.py +++ b/cleverhans/experimental/certification/tests/dual_formulation_test.py @@ -14,194 +14,207 @@ class DualFormulationTest(unittest.TestCase): - - def test_init(self): - # Function to test initialization of dual formulation class. - net_weights = [[[2, 2], [3, 3], [4, 4]], [[1, 1, 1], [-1, -1, -1]]] - net_biases = [np.transpose(np.matrix([0, 0, 0])), - np.transpose(np.matrix([0, 0]))] - net_layer_types = ['ff_relu', 'ff'] - nn_params1 = nn.NeuralNetwork(net_weights, net_biases, net_layer_types) - - test_input = np.transpose(np.matrix([0, 0])) - true_class = 0 - adv_class = 1 - input_minval = 0 - input_maxval = 0 - epsilon = 0.1 - three_dim_tensor = tf.random_uniform(shape=(3, 1), dtype=tf.float32) - two_dim_tensor = tf.random_uniform(shape=(2, 1), dtype=tf.float32) - scalar = tf.random_uniform(shape=(1, 1), dtype=tf.float32) - lambda_pos = [two_dim_tensor, three_dim_tensor] - lambda_neg = lambda_pos - lambda_quad = lambda_pos - lambda_lu = lambda_pos - nu = scalar - dual_var = { - 'lambda_pos': lambda_pos, - 'lambda_neg': lambda_neg, - 'lambda_quad': lambda_quad, - 'lambda_lu': lambda_lu, - 'nu': nu - } - with tf.Session() as sess: - dual_formulation_object = dual_formulation.DualFormulation(sess, - dual_var, - nn_params1, - test_input, - true_class, - adv_class, - input_minval, - input_maxval, - epsilon) - self.assertIsNotNone(dual_formulation_object) - - def test_set_differentiable_objective(self): - # Function to test the function that sets the differentiable objective. - net_weights = [[[2, 2], [3, 3], [4, 4]], [[1, 1, 1], [-1, -1, -1]]] - net_biases = [ - np.transpose(np.matrix([0, 0, 0])), - np.transpose(np.matrix([0, 0])) - ] - net_layer_types = ['ff_relu', 'ff'] - nn_params1 = nn.NeuralNetwork(net_weights, net_biases, net_layer_types) - - test_input = np.transpose(np.matrix([0, 0])) - true_class = 0 - adv_class = 1 - input_minval = 0 - input_maxval = 0 - epsilon = 0.1 - three_dim_tensor = tf.random_uniform(shape=(3, 1), dtype=tf.float32) - two_dim_tensor = tf.random_uniform(shape=(2, 1), dtype=tf.float32) - scalar = tf.random_uniform(shape=(1, 1), dtype=tf.float32) - lambda_pos = [two_dim_tensor, three_dim_tensor] - lambda_neg = lambda_pos - lambda_quad = lambda_pos - lambda_lu = lambda_pos - nu = scalar - dual_var = { - 'lambda_pos': lambda_pos, - 'lambda_neg': lambda_neg, - 'lambda_quad': lambda_quad, - 'lambda_lu': lambda_lu, - 'nu': nu - } - with tf.Session() as sess: - dual_formulation_object = dual_formulation.DualFormulation(sess, - dual_var, - nn_params1, - test_input, - true_class, - adv_class, - input_minval, - input_maxval, - epsilon) - dual_formulation_object.set_differentiable_objective() - self.assertEqual(dual_formulation_object.scalar_f.shape.as_list(), [1]) - self.assertEqual( - dual_formulation_object.unconstrained_objective.shape.as_list(), [1, 1]) - self.assertEqual(dual_formulation_object.vector_g.shape.as_list(), [5, 1]) - - def test_get_full_psd_matrix(self): - # Function to test product with PSD matrix. - net_weights = [[[2, 2], [3, 3], [4, 4]], [[1, 1, 1], [-1, -1, -1]]] - net_biases = [ - np.transpose(np.matrix([0, 0, 0])), - np.transpose(np.matrix([0, 0])) - ] - net_layer_types = ['ff_relu', 'ff'] - nn_params1 = nn.NeuralNetwork(net_weights, net_biases, net_layer_types) - - test_input = np.transpose(np.matrix([0, 0])) - true_class = 0 - adv_class = 1 - input_minval = 0 - input_maxval = 0 - epsilon = 0.1 - three_dim_tensor = tf.random_uniform(shape=(3, 1), dtype=tf.float32) - two_dim_tensor = tf.random_uniform(shape=(2, 1), dtype=tf.float32) - scalar = tf.random_uniform(shape=(1, 1), dtype=tf.float32) - lambda_pos = [two_dim_tensor, three_dim_tensor] - lambda_neg = lambda_pos - lambda_quad = lambda_pos - lambda_lu = lambda_pos - nu = scalar - dual_var = { - 'lambda_pos': lambda_pos, - 'lambda_neg': lambda_neg, - 'lambda_quad': lambda_quad, - 'lambda_lu': lambda_lu, - 'nu': nu - } - with tf.Session() as sess: - dual_formulation_object = dual_formulation.DualFormulation(sess, - dual_var, - nn_params1, - test_input, - true_class, - adv_class, - input_minval, - input_maxval, - epsilon) - matrix_h, matrix_m = dual_formulation_object.get_full_psd_matrix() - self.assertEqual(matrix_h.shape.as_list(), [5, 5]) - self.assertEqual(matrix_m.shape.as_list(), [6, 6]) - - def test_get_psd_product(self): - # Function to test implicit product with PSD matrix. - net_weights = [[[2, 2], [3, 3], [4, 4]], [[1, 1, 1], [-1, -1, -1]]] - net_biases = [ - np.transpose(np.matrix([0, 0, 0])), - np.transpose(np.matrix([0, 0])) - ] - net_layer_types = ['ff_relu', 'ff'] - nn_params1 = nn.NeuralNetwork(net_weights, net_biases, net_layer_types) - - test_input = np.transpose(np.matrix([0, 0])) - true_class = 0 - adv_class = 1 - input_minval = 0 - input_maxval = 0 - epsilon = 0.1 - three_dim_tensor = tf.random_uniform(shape=(3, 1), dtype=tf.float32) - two_dim_tensor = tf.random_uniform(shape=(2, 1), dtype=tf.float32) - scalar = tf.random_uniform(shape=(1, 1), dtype=tf.float32) - lambda_pos = [two_dim_tensor, three_dim_tensor] - lambda_neg = lambda_pos - lambda_quad = lambda_pos - lambda_lu = lambda_pos - nu = scalar - dual_var = { - 'lambda_pos': lambda_pos, - 'lambda_neg': lambda_neg, - 'lambda_quad': lambda_quad, - 'lambda_lu': lambda_lu, - 'nu': nu - } - with tf.Session() as sess: - dual_formulation_object = dual_formulation.DualFormulation(sess, - dual_var, - nn_params1, - test_input, - true_class, - adv_class, - input_minval, - input_maxval, - epsilon) - _, matrix_m = dual_formulation_object.get_full_psd_matrix() - - # Testing if the values match - six_dim_tensor = tf.random_uniform(shape=(6, 1), dtype=tf.float32) - implicit_product = dual_formulation_object.get_psd_product(six_dim_tensor) - explicit_product = tf.matmul(matrix_m, six_dim_tensor) - [implicit_product_value, - explicit_product_value] = sess.run([implicit_product, explicit_product]) - self.assertEqual( - np.shape(implicit_product_value), np.shape(explicit_product_value)) - self.assertLess( - np.max(np.abs(implicit_product_value - explicit_product_value)), 1E-5) - - -if __name__ == '__main__': - unittest.main() + def test_init(self): + # Function to test initialization of dual formulation class. + net_weights = [[[2, 2], [3, 3], [4, 4]], [[1, 1, 1], [-1, -1, -1]]] + net_biases = [ + np.transpose(np.matrix([0, 0, 0])), + np.transpose(np.matrix([0, 0])), + ] + net_layer_types = ["ff_relu", "ff"] + nn_params1 = nn.NeuralNetwork(net_weights, net_biases, net_layer_types) + + test_input = np.transpose(np.matrix([0, 0])) + true_class = 0 + adv_class = 1 + input_minval = 0 + input_maxval = 0 + epsilon = 0.1 + three_dim_tensor = tf.random_uniform(shape=(3, 1), dtype=tf.float32) + two_dim_tensor = tf.random_uniform(shape=(2, 1), dtype=tf.float32) + scalar = tf.random_uniform(shape=(1, 1), dtype=tf.float32) + lambda_pos = [two_dim_tensor, three_dim_tensor] + lambda_neg = lambda_pos + lambda_quad = lambda_pos + lambda_lu = lambda_pos + nu = scalar + dual_var = { + "lambda_pos": lambda_pos, + "lambda_neg": lambda_neg, + "lambda_quad": lambda_quad, + "lambda_lu": lambda_lu, + "nu": nu, + } + with tf.Session() as sess: + dual_formulation_object = dual_formulation.DualFormulation( + sess, + dual_var, + nn_params1, + test_input, + true_class, + adv_class, + input_minval, + input_maxval, + epsilon, + ) + self.assertIsNotNone(dual_formulation_object) + + def test_set_differentiable_objective(self): + # Function to test the function that sets the differentiable objective. + net_weights = [[[2, 2], [3, 3], [4, 4]], [[1, 1, 1], [-1, -1, -1]]] + net_biases = [ + np.transpose(np.matrix([0, 0, 0])), + np.transpose(np.matrix([0, 0])), + ] + net_layer_types = ["ff_relu", "ff"] + nn_params1 = nn.NeuralNetwork(net_weights, net_biases, net_layer_types) + + test_input = np.transpose(np.matrix([0, 0])) + true_class = 0 + adv_class = 1 + input_minval = 0 + input_maxval = 0 + epsilon = 0.1 + three_dim_tensor = tf.random_uniform(shape=(3, 1), dtype=tf.float32) + two_dim_tensor = tf.random_uniform(shape=(2, 1), dtype=tf.float32) + scalar = tf.random_uniform(shape=(1, 1), dtype=tf.float32) + lambda_pos = [two_dim_tensor, three_dim_tensor] + lambda_neg = lambda_pos + lambda_quad = lambda_pos + lambda_lu = lambda_pos + nu = scalar + dual_var = { + "lambda_pos": lambda_pos, + "lambda_neg": lambda_neg, + "lambda_quad": lambda_quad, + "lambda_lu": lambda_lu, + "nu": nu, + } + with tf.Session() as sess: + dual_formulation_object = dual_formulation.DualFormulation( + sess, + dual_var, + nn_params1, + test_input, + true_class, + adv_class, + input_minval, + input_maxval, + epsilon, + ) + dual_formulation_object.set_differentiable_objective() + self.assertEqual(dual_formulation_object.scalar_f.shape.as_list(), [1]) + self.assertEqual( + dual_formulation_object.unconstrained_objective.shape.as_list(), [1, 1] + ) + self.assertEqual(dual_formulation_object.vector_g.shape.as_list(), [5, 1]) + + def test_get_full_psd_matrix(self): + # Function to test product with PSD matrix. + net_weights = [[[2, 2], [3, 3], [4, 4]], [[1, 1, 1], [-1, -1, -1]]] + net_biases = [ + np.transpose(np.matrix([0, 0, 0])), + np.transpose(np.matrix([0, 0])), + ] + net_layer_types = ["ff_relu", "ff"] + nn_params1 = nn.NeuralNetwork(net_weights, net_biases, net_layer_types) + + test_input = np.transpose(np.matrix([0, 0])) + true_class = 0 + adv_class = 1 + input_minval = 0 + input_maxval = 0 + epsilon = 0.1 + three_dim_tensor = tf.random_uniform(shape=(3, 1), dtype=tf.float32) + two_dim_tensor = tf.random_uniform(shape=(2, 1), dtype=tf.float32) + scalar = tf.random_uniform(shape=(1, 1), dtype=tf.float32) + lambda_pos = [two_dim_tensor, three_dim_tensor] + lambda_neg = lambda_pos + lambda_quad = lambda_pos + lambda_lu = lambda_pos + nu = scalar + dual_var = { + "lambda_pos": lambda_pos, + "lambda_neg": lambda_neg, + "lambda_quad": lambda_quad, + "lambda_lu": lambda_lu, + "nu": nu, + } + with tf.Session() as sess: + dual_formulation_object = dual_formulation.DualFormulation( + sess, + dual_var, + nn_params1, + test_input, + true_class, + adv_class, + input_minval, + input_maxval, + epsilon, + ) + matrix_h, matrix_m = dual_formulation_object.get_full_psd_matrix() + self.assertEqual(matrix_h.shape.as_list(), [5, 5]) + self.assertEqual(matrix_m.shape.as_list(), [6, 6]) + + def test_get_psd_product(self): + # Function to test implicit product with PSD matrix. + net_weights = [[[2, 2], [3, 3], [4, 4]], [[1, 1, 1], [-1, -1, -1]]] + net_biases = [ + np.transpose(np.matrix([0, 0, 0])), + np.transpose(np.matrix([0, 0])), + ] + net_layer_types = ["ff_relu", "ff"] + nn_params1 = nn.NeuralNetwork(net_weights, net_biases, net_layer_types) + + test_input = np.transpose(np.matrix([0, 0])) + true_class = 0 + adv_class = 1 + input_minval = 0 + input_maxval = 0 + epsilon = 0.1 + three_dim_tensor = tf.random_uniform(shape=(3, 1), dtype=tf.float32) + two_dim_tensor = tf.random_uniform(shape=(2, 1), dtype=tf.float32) + scalar = tf.random_uniform(shape=(1, 1), dtype=tf.float32) + lambda_pos = [two_dim_tensor, three_dim_tensor] + lambda_neg = lambda_pos + lambda_quad = lambda_pos + lambda_lu = lambda_pos + nu = scalar + dual_var = { + "lambda_pos": lambda_pos, + "lambda_neg": lambda_neg, + "lambda_quad": lambda_quad, + "lambda_lu": lambda_lu, + "nu": nu, + } + with tf.Session() as sess: + dual_formulation_object = dual_formulation.DualFormulation( + sess, + dual_var, + nn_params1, + test_input, + true_class, + adv_class, + input_minval, + input_maxval, + epsilon, + ) + _, matrix_m = dual_formulation_object.get_full_psd_matrix() + + # Testing if the values match + six_dim_tensor = tf.random_uniform(shape=(6, 1), dtype=tf.float32) + implicit_product = dual_formulation_object.get_psd_product(six_dim_tensor) + explicit_product = tf.matmul(matrix_m, six_dim_tensor) + [implicit_product_value, explicit_product_value] = sess.run( + [implicit_product, explicit_product] + ) + self.assertEqual( + np.shape(implicit_product_value), np.shape(explicit_product_value) + ) + self.assertLess( + np.max(np.abs(implicit_product_value - explicit_product_value)), 1e-5 + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/cleverhans/experimental/certification/tests/nn_test.py b/cleverhans/experimental/certification/tests/nn_test.py index 37a52c0ef..6d4f5c746 100644 --- a/cleverhans/experimental/certification/tests/nn_test.py +++ b/cleverhans/experimental/certification/tests/nn_test.py @@ -13,49 +13,49 @@ class NeuralNetworkTest(unittest.TestCase): - - def test_init(self): - # Function to test initialization of NeuralNetParams object. - # Valid params - net_weights = [[[2, 2], [3, 3], [4, 4]], [1, 1, 1]] - net_biases = [ - np.transpose(np.matrix([0, 0, 0])), - np.transpose(np.matrix([0, 0])) - ] - net_layer_types = ['ff_relu', 'ff'] - nn_params1 = nn.NeuralNetwork(net_weights, net_biases, net_layer_types) - - self.assertIsNotNone(nn_params1) - # Invalid params : list length - net_biases = [0] - with self.assertRaises(ValueError): - nn.NeuralNetwork(net_weights, net_biases, net_layer_types) - - # Invalid params: layer types - with self.assertRaises(ValueError): - net_layer_types = ['ff_relu', 'ff_relu'] - nn.NeuralNetwork(net_weights, net_biases, net_layer_types) - - def test_forward_pass(self): - # Function to test forward pass of nn_params. - net_weights = [[[2, 2], [3, 3], [4, 4]], [1, 1, 1]] - net_biases = [ - np.transpose(np.matrix([0, 0, 0])), - np.transpose(np.matrix([0, 0])) - ] - net_layer_types = ['ff_relu', 'ff'] - nn_params = nn.NeuralNetwork(net_weights, net_biases, net_layer_types) - - input_vector = tf.random_uniform(shape=(2, 1), dtype=tf.float32) - output_vector = nn_params.forward_pass(input_vector, 0) - self.assertEqual(output_vector.shape.as_list(), [3, 1]) - output_vector_2 = nn_params.forward_pass(input_vector, 0, is_abs=True) - self.assertEqual(output_vector_2.shape.as_list(), [3, 1]) - input_vector_trans = tf.random_uniform(shape=(3, 1), dtype=tf.float32) - output_vector_3 = nn_params.forward_pass( - input_vector_trans, 0, is_transpose=True) - self.assertEqual(output_vector_3.shape.as_list(), [2, 1]) - - -if __name__ == '__main__': - unittest.main() + def test_init(self): + # Function to test initialization of NeuralNetParams object. + # Valid params + net_weights = [[[2, 2], [3, 3], [4, 4]], [1, 1, 1]] + net_biases = [ + np.transpose(np.matrix([0, 0, 0])), + np.transpose(np.matrix([0, 0])), + ] + net_layer_types = ["ff_relu", "ff"] + nn_params1 = nn.NeuralNetwork(net_weights, net_biases, net_layer_types) + + self.assertIsNotNone(nn_params1) + # Invalid params : list length + net_biases = [0] + with self.assertRaises(ValueError): + nn.NeuralNetwork(net_weights, net_biases, net_layer_types) + + # Invalid params: layer types + with self.assertRaises(ValueError): + net_layer_types = ["ff_relu", "ff_relu"] + nn.NeuralNetwork(net_weights, net_biases, net_layer_types) + + def test_forward_pass(self): + # Function to test forward pass of nn_params. + net_weights = [[[2, 2], [3, 3], [4, 4]], [1, 1, 1]] + net_biases = [ + np.transpose(np.matrix([0, 0, 0])), + np.transpose(np.matrix([0, 0])), + ] + net_layer_types = ["ff_relu", "ff"] + nn_params = nn.NeuralNetwork(net_weights, net_biases, net_layer_types) + + input_vector = tf.random_uniform(shape=(2, 1), dtype=tf.float32) + output_vector = nn_params.forward_pass(input_vector, 0) + self.assertEqual(output_vector.shape.as_list(), [3, 1]) + output_vector_2 = nn_params.forward_pass(input_vector, 0, is_abs=True) + self.assertEqual(output_vector_2.shape.as_list(), [3, 1]) + input_vector_trans = tf.random_uniform(shape=(3, 1), dtype=tf.float32) + output_vector_3 = nn_params.forward_pass( + input_vector_trans, 0, is_transpose=True + ) + self.assertEqual(output_vector_3.shape.as_list(), [2, 1]) + + +if __name__ == "__main__": + unittest.main() diff --git a/cleverhans/experimental/certification/tests/optimization_test.py b/cleverhans/experimental/certification/tests/optimization_test.py index 0c0c4249f..f1193e318 100644 --- a/cleverhans/experimental/certification/tests/optimization_test.py +++ b/cleverhans/experimental/certification/tests/optimization_test.py @@ -13,188 +13,216 @@ class OptimizationTest(tf.test.TestCase): - # pylint: disable=missing-docstring - - def prepare_dual_object(self): - # Function to prepare dual object to be used for testing optimization. - net_weights = [[[2, 2], [3, 3], [4, 4]], [[1, 1, 1], [-1, -1, -1]]] - net_biases = [ - np.transpose(np.matrix([0, 0, 0])), - np.transpose(np.matrix([0, 0])) - ] - net_layer_types = ['ff_relu', 'ff'] - nn_params1 = nn.NeuralNetwork(net_weights, net_biases, net_layer_types) - - test_input = np.transpose(np.matrix([0, 0])) - true_class = 0 - adv_class = 1 - input_minval = 0 - input_maxval = 0 - epsilon = 0.1 - - # Creating dual variables to use for optimization - lambda_pos = [tf.get_variable('lambda_pos0', - initializer=np.random.uniform( - 0, 0.1, size=(2, 1)).astype(np.float32)), - tf.get_variable('lambda_pos1', - initializer=np.random.uniform( - 0, 0.1, size=(3, 1)).astype(np.float32))] - lambda_neg = [tf.get_variable('lambda_neg0', - initializer=np.random.uniform( - 0, 0.1, size=(2, 1)).astype(np.float32)), - tf.get_variable('lambda_neg1', - initializer=np.random.uniform( - 0, 0.1, size=(3, 1)).astype(np.float32))] - lambda_quad = [tf.get_variable('lambda_quad0', - initializer=np.random.uniform( - 0, 0.1, size=(2, 1)).astype(np.float32)), - tf.get_variable('lambda_quad1', - initializer=np.random.uniform( - 0, 0.1, size=(3, 1)).astype(np.float32))] - lambda_lu = [tf.get_variable('lambda_lu0', - initializer=np.random.uniform( - 0, 0.1, size=(2, 1)).astype(np.float32)), - tf.get_variable('lambda_lu1', - initializer=np.random.uniform( - 0, 0.1, size=(3, 1)).astype(np.float32))] - nu = tf.reshape(tf.get_variable('nu', initializer=200.0, - dtype=tf.float32), shape=(1, 1)) - dual_var = {'lambda_pos': lambda_pos, 'lambda_neg': lambda_neg, - 'lambda_quad': lambda_quad, 'lambda_lu': lambda_lu, 'nu': nu} - sess = tf.Session() - dual_formulation_object = dual_formulation.DualFormulation(sess, - dual_var, - nn_params1, - test_input, - true_class, - adv_class, - input_minval, - input_maxval, - epsilon) - return sess, dual_formulation_object - - def test_init(self): - """ Function to test initialization of OptimizationTest. """ - sess, dual_formulation_object = self.prepare_dual_object() - dual_formulation_object.set_differentiable_objective() - sess.run(tf.global_variables_initializer()) - optimization_params = { - 'init_learning_rate': 0.1, - 'learning_rate_decay': 0.9, - 'eig_num_iter': 10, - 'eig_learning_rate': 0.01, - 'init_smooth': 0.5, - 'smooth_decay': 0.9, - 'inner_num_steps': 10, - 'optimizer': 'adam', - 'momentum_parameter': 0.9, - 'eig_type': 'TF' - } - optimization_object = optimization.Optimization(dual_formulation_object, - sess, optimization_params) - self.assertIsNotNone(optimization_object) - - def test_get_min_eig_vec_proxy(self): - """ Function test computing min eigen value using matrix vector products.""" - sess, dual_formulation_object = self.prepare_dual_object() - _, matrix_m = dual_formulation_object.get_full_psd_matrix() - optimization_params = { - 'init_learning_rate': 0.1, - 'learning_rate_decay': 0.9, - 'eig_num_iter': 2000, - 'eig_learning_rate': 0.01, - 'init_smooth': 0.0, - 'smooth_decay': 0.9, - 'inner_num_steps': 10, - 'optimizer': 'adam', - 'momentum_parameter': 0.9, - 'eig_type': 'TF' - } - sess.run(tf.global_variables_initializer()) - optimization_object = optimization.Optimization(dual_formulation_object, - sess, optimization_params) - eig_vec = optimization_object.get_min_eig_vec_proxy() - tf_eig_vec = optimization_object.get_min_eig_vec_proxy(use_tf_eig=True) - self.assertIsNotNone(eig_vec) - - # Running the graphs and checking that minimum eigen value is correct - # ** No smoothing - tf_eig_vec_val, eig_vec_val, matrix_m_val = sess.run( - [tf_eig_vec, eig_vec, matrix_m], - feed_dict={ - optimization_object.eig_init_vec_placeholder: - np.random.rand(6, 1), - optimization_object.eig_num_iter_placeholder: - 2000, - optimization_object.smooth_placeholder: - 0.0 - }) - - # Eigen value corresponding to v is v^\top M v - eig_val = np.matmul( - np.transpose(eig_vec_val), np.matmul(matrix_m_val, eig_vec_val)) - tf_eig_val = np.matmul( - np.transpose(tf_eig_vec_val), np.matmul(matrix_m_val, tf_eig_vec_val)) - [np_eig_values, _] = np.linalg.eig(matrix_m_val) - self.assertLess(np.abs(np.min(np_eig_values) - eig_val), 1E-5) - self.assertLess(np.abs(np.min(np_eig_values) - tf_eig_val), 1E-5) - - # Running the graphs and checking that minimum eigen value is correct - # **Smoothing - optimization_params['init_smooth'] = 0.0001 - optimization_object = optimization.Optimization(dual_formulation_object, - sess, optimization_params) - eig_vec = optimization_object.get_min_eig_vec_proxy() - tf_eig_vec = optimization_object.get_min_eig_vec_proxy(use_tf_eig=True) - - tf_eig_vec_val, eig_vec_val, matrix_m_val = sess.run( - [tf_eig_vec, eig_vec, matrix_m], - feed_dict={ - optimization_object.eig_init_vec_placeholder: - np.random.rand(6, 1), - optimization_object.smooth_placeholder: - 0.1, - optimization_object.eig_num_iter_placeholder: - 2000 - }) - - # Eigen value corresponding to v is v^\top M v - eig_val = np.matmul( - np.transpose(eig_vec_val), np.matmul(matrix_m_val, eig_vec_val)) - tf_eig_val = np.matmul( - np.transpose(tf_eig_vec_val), np.matmul(matrix_m_val, tf_eig_vec_val)) - [np_eig_values, _] = np.linalg.eig(matrix_m_val) - self.assertLess(np.abs(np.min(np_eig_values) - eig_val), 1E-5) - # In general, smoothed version can be far off - self.assertLess(np.abs(np.min(np_eig_values) - tf_eig_val), 1E-1) - - def test_optimization(self): - """Function to test optimization.""" - sess, dual_formulation_object = self.prepare_dual_object() - optimization_params = { - 'init_penalty': 10000, - 'large_eig_num_steps': 1000, - 'small_eig_num_steps': 500, - 'inner_num_steps': 10, - 'outer_num_steps': 2, - 'beta': 2, - 'smoothness_parameter': 0.001, - 'eig_learning_rate': 0.01, - 'optimizer': 'adam', - 'init_learning_rate': 0.1, - 'learning_rate_decay': 0.9, - 'momentum_parameter': 0.9, - 'print_stats_steps': 1, - 'stats_folder': None, - 'projection_steps': 200, - 'eig_type': 'TF' - } - sess.run(tf.global_variables_initializer()) - optimization_object = optimization.Optimization(dual_formulation_object, - sess, optimization_params) - is_cert_found = optimization_object.run_optimization() - self.assertFalse(is_cert_found) - - -if __name__ == '__main__': - tf.test.main() + # pylint: disable=missing-docstring + + def prepare_dual_object(self): + # Function to prepare dual object to be used for testing optimization. + net_weights = [[[2, 2], [3, 3], [4, 4]], [[1, 1, 1], [-1, -1, -1]]] + net_biases = [ + np.transpose(np.matrix([0, 0, 0])), + np.transpose(np.matrix([0, 0])), + ] + net_layer_types = ["ff_relu", "ff"] + nn_params1 = nn.NeuralNetwork(net_weights, net_biases, net_layer_types) + + test_input = np.transpose(np.matrix([0, 0])) + true_class = 0 + adv_class = 1 + input_minval = 0 + input_maxval = 0 + epsilon = 0.1 + + # Creating dual variables to use for optimization + lambda_pos = [ + tf.get_variable( + "lambda_pos0", + initializer=np.random.uniform(0, 0.1, size=(2, 1)).astype(np.float32), + ), + tf.get_variable( + "lambda_pos1", + initializer=np.random.uniform(0, 0.1, size=(3, 1)).astype(np.float32), + ), + ] + lambda_neg = [ + tf.get_variable( + "lambda_neg0", + initializer=np.random.uniform(0, 0.1, size=(2, 1)).astype(np.float32), + ), + tf.get_variable( + "lambda_neg1", + initializer=np.random.uniform(0, 0.1, size=(3, 1)).astype(np.float32), + ), + ] + lambda_quad = [ + tf.get_variable( + "lambda_quad0", + initializer=np.random.uniform(0, 0.1, size=(2, 1)).astype(np.float32), + ), + tf.get_variable( + "lambda_quad1", + initializer=np.random.uniform(0, 0.1, size=(3, 1)).astype(np.float32), + ), + ] + lambda_lu = [ + tf.get_variable( + "lambda_lu0", + initializer=np.random.uniform(0, 0.1, size=(2, 1)).astype(np.float32), + ), + tf.get_variable( + "lambda_lu1", + initializer=np.random.uniform(0, 0.1, size=(3, 1)).astype(np.float32), + ), + ] + nu = tf.reshape( + tf.get_variable("nu", initializer=200.0, dtype=tf.float32), shape=(1, 1) + ) + dual_var = { + "lambda_pos": lambda_pos, + "lambda_neg": lambda_neg, + "lambda_quad": lambda_quad, + "lambda_lu": lambda_lu, + "nu": nu, + } + sess = tf.Session() + dual_formulation_object = dual_formulation.DualFormulation( + sess, + dual_var, + nn_params1, + test_input, + true_class, + adv_class, + input_minval, + input_maxval, + epsilon, + ) + return sess, dual_formulation_object + + def test_init(self): + """ Function to test initialization of OptimizationTest. """ + sess, dual_formulation_object = self.prepare_dual_object() + dual_formulation_object.set_differentiable_objective() + sess.run(tf.global_variables_initializer()) + optimization_params = { + "init_learning_rate": 0.1, + "learning_rate_decay": 0.9, + "eig_num_iter": 10, + "eig_learning_rate": 0.01, + "init_smooth": 0.5, + "smooth_decay": 0.9, + "inner_num_steps": 10, + "optimizer": "adam", + "momentum_parameter": 0.9, + "eig_type": "TF", + } + optimization_object = optimization.Optimization( + dual_formulation_object, sess, optimization_params + ) + self.assertIsNotNone(optimization_object) + + def test_get_min_eig_vec_proxy(self): + """ Function test computing min eigen value using matrix vector products.""" + sess, dual_formulation_object = self.prepare_dual_object() + _, matrix_m = dual_formulation_object.get_full_psd_matrix() + optimization_params = { + "init_learning_rate": 0.1, + "learning_rate_decay": 0.9, + "eig_num_iter": 2000, + "eig_learning_rate": 0.01, + "init_smooth": 0.0, + "smooth_decay": 0.9, + "inner_num_steps": 10, + "optimizer": "adam", + "momentum_parameter": 0.9, + "eig_type": "TF", + } + sess.run(tf.global_variables_initializer()) + optimization_object = optimization.Optimization( + dual_formulation_object, sess, optimization_params + ) + eig_vec = optimization_object.get_min_eig_vec_proxy() + tf_eig_vec = optimization_object.get_min_eig_vec_proxy(use_tf_eig=True) + self.assertIsNotNone(eig_vec) + + # Running the graphs and checking that minimum eigen value is correct + # ** No smoothing + tf_eig_vec_val, eig_vec_val, matrix_m_val = sess.run( + [tf_eig_vec, eig_vec, matrix_m], + feed_dict={ + optimization_object.eig_init_vec_placeholder: np.random.rand(6, 1), + optimization_object.eig_num_iter_placeholder: 2000, + optimization_object.smooth_placeholder: 0.0, + }, + ) + + # Eigen value corresponding to v is v^\top M v + eig_val = np.matmul( + np.transpose(eig_vec_val), np.matmul(matrix_m_val, eig_vec_val) + ) + tf_eig_val = np.matmul( + np.transpose(tf_eig_vec_val), np.matmul(matrix_m_val, tf_eig_vec_val) + ) + [np_eig_values, _] = np.linalg.eig(matrix_m_val) + self.assertLess(np.abs(np.min(np_eig_values) - eig_val), 1e-5) + self.assertLess(np.abs(np.min(np_eig_values) - tf_eig_val), 1e-5) + + # Running the graphs and checking that minimum eigen value is correct + # **Smoothing + optimization_params["init_smooth"] = 0.0001 + optimization_object = optimization.Optimization( + dual_formulation_object, sess, optimization_params + ) + eig_vec = optimization_object.get_min_eig_vec_proxy() + tf_eig_vec = optimization_object.get_min_eig_vec_proxy(use_tf_eig=True) + + tf_eig_vec_val, eig_vec_val, matrix_m_val = sess.run( + [tf_eig_vec, eig_vec, matrix_m], + feed_dict={ + optimization_object.eig_init_vec_placeholder: np.random.rand(6, 1), + optimization_object.smooth_placeholder: 0.1, + optimization_object.eig_num_iter_placeholder: 2000, + }, + ) + + # Eigen value corresponding to v is v^\top M v + eig_val = np.matmul( + np.transpose(eig_vec_val), np.matmul(matrix_m_val, eig_vec_val) + ) + tf_eig_val = np.matmul( + np.transpose(tf_eig_vec_val), np.matmul(matrix_m_val, tf_eig_vec_val) + ) + [np_eig_values, _] = np.linalg.eig(matrix_m_val) + self.assertLess(np.abs(np.min(np_eig_values) - eig_val), 1e-5) + # In general, smoothed version can be far off + self.assertLess(np.abs(np.min(np_eig_values) - tf_eig_val), 1e-1) + + def test_optimization(self): + """Function to test optimization.""" + sess, dual_formulation_object = self.prepare_dual_object() + optimization_params = { + "init_penalty": 10000, + "large_eig_num_steps": 1000, + "small_eig_num_steps": 500, + "inner_num_steps": 10, + "outer_num_steps": 2, + "beta": 2, + "smoothness_parameter": 0.001, + "eig_learning_rate": 0.01, + "optimizer": "adam", + "init_learning_rate": 0.1, + "learning_rate_decay": 0.9, + "momentum_parameter": 0.9, + "print_stats_steps": 1, + "stats_folder": None, + "projection_steps": 200, + "eig_type": "TF", + } + sess.run(tf.global_variables_initializer()) + optimization_object = optimization.Optimization( + dual_formulation_object, sess, optimization_params + ) + is_cert_found = optimization_object.run_optimization() + self.assertFalse(is_cert_found) + + +if __name__ == "__main__": + tf.test.main() diff --git a/cleverhans/experimental/certification/tests/utils_test.py b/cleverhans/experimental/certification/tests/utils_test.py index d5c85db75..dc70a59b5 100644 --- a/cleverhans/experimental/certification/tests/utils_test.py +++ b/cleverhans/experimental/certification/tests/utils_test.py @@ -18,55 +18,64 @@ class UtilsTest(tf.test.TestCase): - - def test_minimum_eigen_vector(self): - matrix = np.array([[1.0, 2.0], [2.0, 5.0]], dtype=np.float32) - initial_vec = np.array([[1.0], [-1.0]], dtype=np.float32) - - def _vector_prod_fn(x): - return tf.matmul(matrix, x) - - min_eigen_fn = autograph.to_graph(utils.minimum_eigen_vector) - x = tf.placeholder(tf.float32, shape=(2, 1)) - min_eig_vec = min_eigen_fn(x, 10, 0.1, _vector_prod_fn) - with self.test_session() as sess: - v = sess.run(min_eig_vec, feed_dict={x: initial_vec}) - if v.flatten()[0] < 0: - v = -v - np.testing.assert_almost_equal(v, [[0.9239], [-0.3827]], decimal=4) - - def test_tf_lanczos_smallest_eigval(self): - tf_num_iter = tf.placeholder(dtype=tf.int32, shape=()) - tf_matrix = tf.placeholder(dtype=tf.float32) - def _vector_prod_fn(x): - return tf.matmul(tf_matrix, tf.reshape(x, [-1, 1])) - - min_eigen_fn = autograph.to_graph(utils.tf_lanczos_smallest_eigval) - init_vec_ph = tf.placeholder(shape=(MATRIX_DIMENTION, 1), dtype=tf.float32) - tf_eigval, tf_eigvec = min_eigen_fn( - _vector_prod_fn, MATRIX_DIMENTION, init_vec_ph, tf_num_iter, dtype=tf.float32) - eigvec = np.zeros((MATRIX_DIMENTION, 1), dtype=np.float32) - - with self.test_session() as sess: - # run this test for a few random matrices - for _ in range(NUM_RANDOM_MATRICES): - matrix = np.random.random((MATRIX_DIMENTION, MATRIX_DIMENTION)) - matrix = matrix + matrix.T # symmetrizing matrix - eigval, eigvec = sess.run( - [tf_eigval, tf_eigvec], - feed_dict={tf_num_iter: NUM_LZS_ITERATIONS, tf_matrix: matrix, init_vec_ph: eigvec}) - - scipy_min_eigval, scipy_min_eigvec = eigs( - matrix, k=1, which='SR') - scipy_min_eigval = np.real(scipy_min_eigval) - scipy_min_eigvec = np.real(scipy_min_eigvec) - scipy_min_eigvec = scipy_min_eigvec / np.linalg.norm(scipy_min_eigvec) - - np.testing.assert_almost_equal(eigval, scipy_min_eigval, decimal=3) - np.testing.assert_almost_equal(np.linalg.norm(eigvec), 1.0, decimal=3) - abs_dot_prod = abs(np.dot(eigvec.flatten(), scipy_min_eigvec.flatten())) - np.testing.assert_almost_equal(abs_dot_prod, 1.0, decimal=3) - - -if __name__ == '__main__': - tf.test.main() + def test_minimum_eigen_vector(self): + matrix = np.array([[1.0, 2.0], [2.0, 5.0]], dtype=np.float32) + initial_vec = np.array([[1.0], [-1.0]], dtype=np.float32) + + def _vector_prod_fn(x): + return tf.matmul(matrix, x) + + min_eigen_fn = autograph.to_graph(utils.minimum_eigen_vector) + x = tf.placeholder(tf.float32, shape=(2, 1)) + min_eig_vec = min_eigen_fn(x, 10, 0.1, _vector_prod_fn) + with self.test_session() as sess: + v = sess.run(min_eig_vec, feed_dict={x: initial_vec}) + if v.flatten()[0] < 0: + v = -v + np.testing.assert_almost_equal(v, [[0.9239], [-0.3827]], decimal=4) + + def test_tf_lanczos_smallest_eigval(self): + tf_num_iter = tf.placeholder(dtype=tf.int32, shape=()) + tf_matrix = tf.placeholder(dtype=tf.float32) + + def _vector_prod_fn(x): + return tf.matmul(tf_matrix, tf.reshape(x, [-1, 1])) + + min_eigen_fn = autograph.to_graph(utils.tf_lanczos_smallest_eigval) + init_vec_ph = tf.placeholder(shape=(MATRIX_DIMENTION, 1), dtype=tf.float32) + tf_eigval, tf_eigvec = min_eigen_fn( + _vector_prod_fn, + MATRIX_DIMENTION, + init_vec_ph, + tf_num_iter, + dtype=tf.float32, + ) + eigvec = np.zeros((MATRIX_DIMENTION, 1), dtype=np.float32) + + with self.test_session() as sess: + # run this test for a few random matrices + for _ in range(NUM_RANDOM_MATRICES): + matrix = np.random.random((MATRIX_DIMENTION, MATRIX_DIMENTION)) + matrix = matrix + matrix.T # symmetrizing matrix + eigval, eigvec = sess.run( + [tf_eigval, tf_eigvec], + feed_dict={ + tf_num_iter: NUM_LZS_ITERATIONS, + tf_matrix: matrix, + init_vec_ph: eigvec, + }, + ) + + scipy_min_eigval, scipy_min_eigvec = eigs(matrix, k=1, which="SR") + scipy_min_eigval = np.real(scipy_min_eigval) + scipy_min_eigvec = np.real(scipy_min_eigvec) + scipy_min_eigvec = scipy_min_eigvec / np.linalg.norm(scipy_min_eigvec) + + np.testing.assert_almost_equal(eigval, scipy_min_eigval, decimal=3) + np.testing.assert_almost_equal(np.linalg.norm(eigvec), 1.0, decimal=3) + abs_dot_prod = abs(np.dot(eigvec.flatten(), scipy_min_eigvec.flatten())) + np.testing.assert_almost_equal(abs_dot_prod, 1.0, decimal=3) + + +if __name__ == "__main__": + tf.test.main() diff --git a/cleverhans/experimental/certification/utils.py b/cleverhans/experimental/certification/utils.py index e7bc32098..e931b7cb2 100644 --- a/cleverhans/experimental/certification/utils.py +++ b/cleverhans/experimental/certification/utils.py @@ -7,272 +7,320 @@ import numpy as np import tensorflow as tf + def diag(diag_elements): - """Function to create tensorflow diagonal matrix with input diagonal entries. - - Args: - diag_elements: tensor with diagonal elements - - Returns: - tf matrix with diagonal entries as diag_elements - """ - return tf.diag(tf.reshape(diag_elements, [-1])) - - -def initialize_dual(neural_net_params_object, init_dual_file=None, - random_init_variance=0.01, init_nu=200.0): - """Function to initialize the dual variables of the class. - - Args: - neural_net_params_object: Object with the neural net weights, biases - and types - init_dual_file: Path to file containing dual variables, if the path - is empty, perform random initialization - Expects numpy dictionary with - lambda_pos_0, lambda_pos_1, .. - lambda_neg_0, lambda_neg_1, .. - lambda_quad_0, lambda_quad_1, .. - lambda_lu_0, lambda_lu_1, .. - random_init_variance: variance for random initialization - init_nu: Value to initialize nu variable with - - Returns: - dual_var: dual variables initialized appropriately. - """ - lambda_pos = [] - lambda_neg = [] - lambda_quad = [] - lambda_lu = [] - - if init_dual_file is None: - for i in range(0, neural_net_params_object.num_hidden_layers + 1): - initializer = (np.random.uniform(0, random_init_variance, size=( - neural_net_params_object.sizes[i], 1))).astype(np.float32) - lambda_pos.append(tf.get_variable('lambda_pos_' + str(i), - initializer=initializer, - dtype=tf.float32)) - initializer = (np.random.uniform(0, random_init_variance, size=( - neural_net_params_object.sizes[i], 1))).astype(np.float32) - lambda_neg.append(tf.get_variable('lambda_neg_' + str(i), - initializer=initializer, - dtype=tf.float32)) - initializer = (np.random.uniform(0, random_init_variance, size=( - neural_net_params_object.sizes[i], 1))).astype(np.float32) - lambda_quad.append(tf.get_variable('lambda_quad_' + str(i), - initializer=initializer, - dtype=tf.float32)) - initializer = (np.random.uniform(0, random_init_variance, size=( - neural_net_params_object.sizes[i], 1))).astype(np.float32) - lambda_lu.append(tf.get_variable('lambda_lu_' + str(i), - initializer=initializer, - dtype=tf.float32)) - nu = tf.get_variable('nu', initializer=init_nu) - else: - # Loading from file - dual_var_init_val = np.load(init_dual_file).item() - for i in range(0, neural_net_params_object.num_hidden_layers + 1): - lambda_pos.append( - tf.get_variable('lambda_pos_' + str(i), - initializer=dual_var_init_val['lambda_pos'][i], - dtype=tf.float32)) - lambda_neg.append( - tf.get_variable('lambda_neg_' + str(i), - initializer=dual_var_init_val['lambda_neg'][i], - dtype=tf.float32)) - lambda_quad.append( - tf.get_variable('lambda_quad_' + str(i), - initializer=dual_var_init_val['lambda_quad'][i], - dtype=tf.float32)) - lambda_lu.append( - tf.get_variable('lambda_lu_' + str(i), - initializer=dual_var_init_val['lambda_lu'][i], - dtype=tf.float32)) - nu = tf.get_variable('nu', initializer=1.0*dual_var_init_val['nu']) - dual_var = {'lambda_pos': lambda_pos, 'lambda_neg': lambda_neg, - 'lambda_quad': lambda_quad, 'lambda_lu': lambda_lu, 'nu': nu} - return dual_var + """Function to create tensorflow diagonal matrix with input diagonal entries. + + Args: + diag_elements: tensor with diagonal elements + + Returns: + tf matrix with diagonal entries as diag_elements + """ + return tf.diag(tf.reshape(diag_elements, [-1])) + + +def initialize_dual( + neural_net_params_object, + init_dual_file=None, + random_init_variance=0.01, + init_nu=200.0, +): + """Function to initialize the dual variables of the class. + + Args: + neural_net_params_object: Object with the neural net weights, biases + and types + init_dual_file: Path to file containing dual variables, if the path + is empty, perform random initialization + Expects numpy dictionary with + lambda_pos_0, lambda_pos_1, .. + lambda_neg_0, lambda_neg_1, .. + lambda_quad_0, lambda_quad_1, .. + lambda_lu_0, lambda_lu_1, .. + random_init_variance: variance for random initialization + init_nu: Value to initialize nu variable with + + Returns: + dual_var: dual variables initialized appropriately. + """ + lambda_pos = [] + lambda_neg = [] + lambda_quad = [] + lambda_lu = [] + + if init_dual_file is None: + for i in range(0, neural_net_params_object.num_hidden_layers + 1): + initializer = ( + np.random.uniform( + 0, random_init_variance, size=(neural_net_params_object.sizes[i], 1) + ) + ).astype(np.float32) + lambda_pos.append( + tf.get_variable( + "lambda_pos_" + str(i), initializer=initializer, dtype=tf.float32 + ) + ) + initializer = ( + np.random.uniform( + 0, random_init_variance, size=(neural_net_params_object.sizes[i], 1) + ) + ).astype(np.float32) + lambda_neg.append( + tf.get_variable( + "lambda_neg_" + str(i), initializer=initializer, dtype=tf.float32 + ) + ) + initializer = ( + np.random.uniform( + 0, random_init_variance, size=(neural_net_params_object.sizes[i], 1) + ) + ).astype(np.float32) + lambda_quad.append( + tf.get_variable( + "lambda_quad_" + str(i), initializer=initializer, dtype=tf.float32 + ) + ) + initializer = ( + np.random.uniform( + 0, random_init_variance, size=(neural_net_params_object.sizes[i], 1) + ) + ).astype(np.float32) + lambda_lu.append( + tf.get_variable( + "lambda_lu_" + str(i), initializer=initializer, dtype=tf.float32 + ) + ) + nu = tf.get_variable("nu", initializer=init_nu) + else: + # Loading from file + dual_var_init_val = np.load(init_dual_file).item() + for i in range(0, neural_net_params_object.num_hidden_layers + 1): + lambda_pos.append( + tf.get_variable( + "lambda_pos_" + str(i), + initializer=dual_var_init_val["lambda_pos"][i], + dtype=tf.float32, + ) + ) + lambda_neg.append( + tf.get_variable( + "lambda_neg_" + str(i), + initializer=dual_var_init_val["lambda_neg"][i], + dtype=tf.float32, + ) + ) + lambda_quad.append( + tf.get_variable( + "lambda_quad_" + str(i), + initializer=dual_var_init_val["lambda_quad"][i], + dtype=tf.float32, + ) + ) + lambda_lu.append( + tf.get_variable( + "lambda_lu_" + str(i), + initializer=dual_var_init_val["lambda_lu"][i], + dtype=tf.float32, + ) + ) + nu = tf.get_variable("nu", initializer=1.0 * dual_var_init_val["nu"]) + dual_var = { + "lambda_pos": lambda_pos, + "lambda_neg": lambda_neg, + "lambda_quad": lambda_quad, + "lambda_lu": lambda_lu, + "nu": nu, + } + return dual_var + def eig_one_step(current_vector, learning_rate, vector_prod_fn): - """Function that performs one step of gd (variant) for min eigen value. - - Args: - current_vector: current estimate of the eigen vector with minimum eigen - value. - learning_rate: learning rate. - vector_prod_fn: function which returns product H*x, where H is a matrix for - which we computing eigenvector. - - Returns: - updated vector after one step - """ - grad = 2*vector_prod_fn(current_vector) - # Current objective = (1/2)*v^T (2*M*v); v = current_vector - # grad = 2*M*v - current_objective = tf.reshape(tf.matmul(tf.transpose(current_vector), - grad) / 2., shape=()) - - # Project the gradient into the tangent space of the constraint region. - # This way we do not waste time taking steps that try to change the - # norm of current_vector - grad = grad - current_vector*tf.matmul(tf.transpose(current_vector), grad) - grad_norm = tf.norm(grad) - grad_norm_sq = tf.square(grad_norm) - - # Computing normalized gradient of unit norm - norm_grad = grad / grad_norm - - # Computing directional second derivative (dsd) - # dsd = 2*g^T M g, where g is normalized gradient - directional_second_derivative = ( - tf.reshape(2*tf.matmul(tf.transpose(norm_grad), - vector_prod_fn(norm_grad)), - shape=())) - - # Computing grad^\top M grad [useful to compute step size later] - # Just a rescaling of the directional_second_derivative (which uses - # normalized gradient - grad_m_grad = directional_second_derivative*grad_norm_sq / 2 - - # Directional_second_derivative/2 = objective when vector is norm_grad - # If this is smaller than current objective, simply return that - if directional_second_derivative / 2. < current_objective: - return norm_grad - - # If curvature is positive, jump to the bottom of the bowl - if directional_second_derivative > 0.: - step = -1. * grad_norm / directional_second_derivative - else: - # If the gradient is very small, do not move - if grad_norm_sq <= 1e-16: - step = 0.0 + """Function that performs one step of gd (variant) for min eigen value. + + Args: + current_vector: current estimate of the eigen vector with minimum eigen + value. + learning_rate: learning rate. + vector_prod_fn: function which returns product H*x, where H is a matrix for + which we computing eigenvector. + + Returns: + updated vector after one step + """ + grad = 2 * vector_prod_fn(current_vector) + # Current objective = (1/2)*v^T (2*M*v); v = current_vector + # grad = 2*M*v + current_objective = tf.reshape( + tf.matmul(tf.transpose(current_vector), grad) / 2.0, shape=() + ) + + # Project the gradient into the tangent space of the constraint region. + # This way we do not waste time taking steps that try to change the + # norm of current_vector + grad = grad - current_vector * tf.matmul(tf.transpose(current_vector), grad) + grad_norm = tf.norm(grad) + grad_norm_sq = tf.square(grad_norm) + + # Computing normalized gradient of unit norm + norm_grad = grad / grad_norm + + # Computing directional second derivative (dsd) + # dsd = 2*g^T M g, where g is normalized gradient + directional_second_derivative = tf.reshape( + 2 * tf.matmul(tf.transpose(norm_grad), vector_prod_fn(norm_grad)), shape=() + ) + + # Computing grad^\top M grad [useful to compute step size later] + # Just a rescaling of the directional_second_derivative (which uses + # normalized gradient + grad_m_grad = directional_second_derivative * grad_norm_sq / 2 + + # Directional_second_derivative/2 = objective when vector is norm_grad + # If this is smaller than current objective, simply return that + if directional_second_derivative / 2.0 < current_objective: + return norm_grad + + # If curvature is positive, jump to the bottom of the bowl + if directional_second_derivative > 0.0: + step = -1.0 * grad_norm / directional_second_derivative else: - # Make a heuristic guess of the step size - step = -2. * tf.reduce_sum(current_vector*grad) / grad_norm_sq - # Computing gain using the gradient and second derivative - gain = -(2 * tf.reduce_sum(current_vector*grad) + - (step*step) * grad_m_grad) - - # Fall back to pre-determined learning rate if no gain - if gain < 0.: - step = -learning_rate * grad_norm - current_vector = current_vector + step * norm_grad - return tf.nn.l2_normalize(current_vector) + # If the gradient is very small, do not move + if grad_norm_sq <= 1e-16: + step = 0.0 + else: + # Make a heuristic guess of the step size + step = -2.0 * tf.reduce_sum(current_vector * grad) / grad_norm_sq + # Computing gain using the gradient and second derivative + gain = -( + 2 * tf.reduce_sum(current_vector * grad) + (step * step) * grad_m_grad + ) + + # Fall back to pre-determined learning rate if no gain + if gain < 0.0: + step = -learning_rate * grad_norm + current_vector = current_vector + step * norm_grad + return tf.nn.l2_normalize(current_vector) def minimum_eigen_vector(x, num_steps, learning_rate, vector_prod_fn): - """Computes eigenvector which corresponds to minimum eigenvalue. - - Args: - x: initial value of eigenvector. - num_steps: number of optimization steps. - learning_rate: learning rate. - vector_prod_fn: function which takes x and returns product H*x. - - Returns: - approximate value of eigenvector. - - This function finds approximate value of eigenvector of matrix H which - corresponds to smallest (by absolute value) eigenvalue of H. - It works by solving optimization problem x^{T}*H*x -> min. - """ - x = tf.nn.l2_normalize(x) - for _ in range(num_steps): - x = eig_one_step(x, learning_rate, vector_prod_fn) - return x - - -def tf_lanczos_smallest_eigval(vector_prod_fn, - matrix_dim, - initial_vector, - num_iter=1000, - max_iter=1000, - collapse_tol=1e-9, - dtype=tf.float32): - """Computes smallest eigenvector and eigenvalue using Lanczos in pure TF. - - This function computes smallest eigenvector and eigenvalue of the matrix - which is implicitly specified by `vector_prod_fn`. - `vector_prod_fn` is a function which takes `x` and returns a product of matrix - in consideration and `x`. - Computation is done using Lanczos algorithm, see - https://en.wikipedia.org/wiki/Lanczos_algorithm#The_algorithm - - Args: - vector_prod_fn: function which takes a vector as an input and returns - matrix vector product. - matrix_dim: dimentionality of the matrix. - initial_vector: guess vector to start the algorithm with - num_iter: user-defined number of iterations for the algorithm - max_iter: maximum number of iterations. - collapse_tol: tolerance to determine collapse of the Krylov subspace - dtype: type of data - - Returns: - tuple of (eigenvalue, eigenvector) of smallest eigenvalue and corresponding - eigenvector. - """ - - # alpha will store diagonal elements - alpha = tf.TensorArray(dtype, size=1, dynamic_size=True, element_shape=()) - # beta will store off diagonal elements - beta = tf.TensorArray(dtype, size=0, dynamic_size=True, element_shape=()) - # q will store Krylov space basis - q_vectors = tf.TensorArray( - dtype, size=1, dynamic_size=True, element_shape=(matrix_dim, 1)) - - # If start vector is all zeros, make it a random normal vector and run for max_iter - if tf.norm(initial_vector) < collapse_tol: - initial_vector = tf.random_normal(shape=(matrix_dim, 1), dtype=dtype) - num_iter = max_iter - - w = initial_vector / tf.norm(initial_vector) - - # Iteration 0 of Lanczos - q_vectors = q_vectors.write(0, w) - w_ = vector_prod_fn(w) - cur_alpha = tf.reduce_sum(w_ * w) - alpha = alpha.write(0, cur_alpha) - w_ = w_ - tf.scalar_mul(cur_alpha, w) - w_prev = w - w = w_ - - # Subsequent iterations of Lanczos - for i in tf.range(1, num_iter): - cur_beta = tf.norm(w) - if cur_beta < collapse_tol: - # return early if Krylov subspace collapsed - break - - # cur_beta is larger than collapse_tol, - # so division will return finite result. - w = w / cur_beta - + """Computes eigenvector which corresponds to minimum eigenvalue. + + Args: + x: initial value of eigenvector. + num_steps: number of optimization steps. + learning_rate: learning rate. + vector_prod_fn: function which takes x and returns product H*x. + + Returns: + approximate value of eigenvector. + + This function finds approximate value of eigenvector of matrix H which + corresponds to smallest (by absolute value) eigenvalue of H. + It works by solving optimization problem x^{T}*H*x -> min. + """ + x = tf.nn.l2_normalize(x) + for _ in range(num_steps): + x = eig_one_step(x, learning_rate, vector_prod_fn) + return x + + +def tf_lanczos_smallest_eigval( + vector_prod_fn, + matrix_dim, + initial_vector, + num_iter=1000, + max_iter=1000, + collapse_tol=1e-9, + dtype=tf.float32, +): + """Computes smallest eigenvector and eigenvalue using Lanczos in pure TF. + + This function computes smallest eigenvector and eigenvalue of the matrix + which is implicitly specified by `vector_prod_fn`. + `vector_prod_fn` is a function which takes `x` and returns a product of matrix + in consideration and `x`. + Computation is done using Lanczos algorithm, see + https://en.wikipedia.org/wiki/Lanczos_algorithm#The_algorithm + + Args: + vector_prod_fn: function which takes a vector as an input and returns + matrix vector product. + matrix_dim: dimentionality of the matrix. + initial_vector: guess vector to start the algorithm with + num_iter: user-defined number of iterations for the algorithm + max_iter: maximum number of iterations. + collapse_tol: tolerance to determine collapse of the Krylov subspace + dtype: type of data + + Returns: + tuple of (eigenvalue, eigenvector) of smallest eigenvalue and corresponding + eigenvector. + """ + + # alpha will store diagonal elements + alpha = tf.TensorArray(dtype, size=1, dynamic_size=True, element_shape=()) + # beta will store off diagonal elements + beta = tf.TensorArray(dtype, size=0, dynamic_size=True, element_shape=()) + # q will store Krylov space basis + q_vectors = tf.TensorArray( + dtype, size=1, dynamic_size=True, element_shape=(matrix_dim, 1) + ) + + # If start vector is all zeros, make it a random normal vector and run for max_iter + if tf.norm(initial_vector) < collapse_tol: + initial_vector = tf.random_normal(shape=(matrix_dim, 1), dtype=dtype) + num_iter = max_iter + + w = initial_vector / tf.norm(initial_vector) + + # Iteration 0 of Lanczos + q_vectors = q_vectors.write(0, w) w_ = vector_prod_fn(w) cur_alpha = tf.reduce_sum(w_ * w) - - q_vectors = q_vectors.write(i, w) - alpha = alpha.write(i, cur_alpha) - beta = beta.write(i-1, cur_beta) - - w_ = w_ - tf.scalar_mul(cur_alpha, w) - tf.scalar_mul(cur_beta, w_prev) + alpha = alpha.write(0, cur_alpha) + w_ = w_ - tf.scalar_mul(cur_alpha, w) w_prev = w w = w_ - alpha = alpha.stack() - beta = beta.stack() - q_vectors = tf.reshape(q_vectors.stack(), (-1, matrix_dim)) + # Subsequent iterations of Lanczos + for i in tf.range(1, num_iter): + cur_beta = tf.norm(w) + if cur_beta < collapse_tol: + # return early if Krylov subspace collapsed + break + + # cur_beta is larger than collapse_tol, + # so division will return finite result. + w = w / cur_beta + + w_ = vector_prod_fn(w) + cur_alpha = tf.reduce_sum(w_ * w) + + q_vectors = q_vectors.write(i, w) + alpha = alpha.write(i, cur_alpha) + beta = beta.write(i - 1, cur_beta) + + w_ = w_ - tf.scalar_mul(cur_alpha, w) - tf.scalar_mul(cur_beta, w_prev) + w_prev = w + w = w_ + + alpha = alpha.stack() + beta = beta.stack() + q_vectors = tf.reshape(q_vectors.stack(), (-1, matrix_dim)) - offdiag_submatrix = tf.linalg.diag(beta) - tridiag_matrix = (tf.linalg.diag(alpha) - + tf.pad(offdiag_submatrix, [[0, 1], [1, 0]]) - + tf.pad(offdiag_submatrix, [[1, 0], [0, 1]])) + offdiag_submatrix = tf.linalg.diag(beta) + tridiag_matrix = ( + tf.linalg.diag(alpha) + + tf.pad(offdiag_submatrix, [[0, 1], [1, 0]]) + + tf.pad(offdiag_submatrix, [[1, 0], [0, 1]]) + ) - eigvals, eigvecs = tf.linalg.eigh(tridiag_matrix) + eigvals, eigvecs = tf.linalg.eigh(tridiag_matrix) - smallest_eigval = eigvals[0] - smallest_eigvec = tf.matmul(tf.reshape(eigvecs[:, 0], (1, -1)), - q_vectors) - smallest_eigvec = smallest_eigvec / tf.norm(smallest_eigvec) - smallest_eigvec = tf.reshape(smallest_eigvec, (matrix_dim, 1)) + smallest_eigval = eigvals[0] + smallest_eigvec = tf.matmul(tf.reshape(eigvecs[:, 0], (1, -1)), q_vectors) + smallest_eigvec = smallest_eigvec / tf.norm(smallest_eigvec) + smallest_eigvec = tf.reshape(smallest_eigvec, (matrix_dim, 1)) - return smallest_eigval, smallest_eigvec + return smallest_eigval, smallest_eigvec diff --git a/cleverhans/future/README.md b/cleverhans/future/README.md deleted file mode 100644 index 426fd252d..000000000 --- a/cleverhans/future/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# CleverHans [Future] - -This folder contains (experimental) code that will eventually replace the -existing CleverHans package. In the future, CleverHans will support three -frameworks (JAX, PyTorch, and TF2). The package itself will focus on its -initial principle: reference implementation of attacks against machine learning -models to help with benchmarking models against adversarial examples. diff --git a/cleverhans/future/jax/attacks/__init__.py b/cleverhans/future/jax/attacks/__init__.py deleted file mode 100644 index efbdac562..000000000 --- a/cleverhans/future/jax/attacks/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from cleverhans.future.jax.attacks.fast_gradient_method import fast_gradient_method -from cleverhans.future.jax.attacks.projected_gradient_descent import projected_gradient_descent diff --git a/cleverhans/future/jax/attacks/fast_gradient_method.py b/cleverhans/future/jax/attacks/fast_gradient_method.py deleted file mode 100644 index 01c7207fc..000000000 --- a/cleverhans/future/jax/attacks/fast_gradient_method.py +++ /dev/null @@ -1,66 +0,0 @@ -import jax.numpy as np -from jax import grad, vmap -from jax.experimental.stax import logsoftmax - -from cleverhans.future.jax.utils import one_hot - - -def fast_gradient_method(model_fn, x, eps, norm, clip_min=None, clip_max=None, y=None, - targeted=False): - """ - JAX implementation of the Fast Gradient Method. - :param model_fn: a callable that takes an input tensor and returns the model logits. - :param x: input tensor. - :param eps: epsilon (input variation parameter); see https://arxiv.org/abs/1412.6572. - :param norm: Order of the norm (mimics NumPy). Possible values: np.inf or 2. - :param clip_min: (optional) float. Minimum float value for adversarial example components. - :param clip_max: (optional) float. Maximum float value for adversarial example components. - :param y: (optional) Tensor with one-hot true labels. If targeted is true, then provide the - target one-hot label. Otherwise, only provide this parameter if you'd like to use true - labels when crafting adversarial samples. Otherwise, model predictions are used - as labels to avoid the "label leaking" effect (explained in this paper: - https://arxiv.org/abs/1611.01236). Default is None. This argument does not have - to be a binary one-hot label (e.g., [0, 1, 0, 0]), it can be floating points values - that sum up to 1 (e.g., [0.05, 0.85, 0.05, 0.05]). - :param targeted: (optional) bool. Is the attack targeted or untargeted? - Untargeted, the default, will try to make the label incorrect. - Targeted will instead try to move in the direction of being more like y. - :return: a tensor for the adversarial example - """ - if norm not in [np.inf, 2]: - raise ValueError("Norm order must be either np.inf or 2.") - - if y is None: - # Using model predictions as ground truth to avoid label leaking - x_labels = np.argmax(model_fn(x), 1) - y = one_hot(x_labels, 10) - - def loss_adv(image, label): - pred = model_fn(image[None]) - loss = - np.sum(logsoftmax(pred) * label) - if targeted: - loss = -loss - return loss - - grads_fn = vmap(grad(loss_adv), in_axes=(0, 0), out_axes=0) - grads = grads_fn(x, y) - - axis = list(range(1, len(grads.shape))) - avoid_zero_div = 1e-12 - if norm == np.inf: - perturbation = eps * np.sign(grads) - elif norm == 1: - raise NotImplementedError("L_1 norm has not been implemented yet.") - elif norm == 2: - square = np.maximum(avoid_zero_div, np.sum(np.square(grads), axis=axis, keepdims=True)) - perturbation = grads / np.sqrt(square) - - adv_x = x + perturbation - - # If clipping is needed, reset all values outside of [clip_min, clip_max] - if (clip_min is not None) or (clip_max is not None): - # We don't currently support one-sided clipping - assert clip_min is not None and clip_max is not None - adv_x = np.clip(adv_x, a_min=clip_min, a_max=clip_max) - - return adv_x diff --git a/cleverhans/future/jax/attacks/projected_gradient_descent.py b/cleverhans/future/jax/attacks/projected_gradient_descent.py deleted file mode 100644 index a24014811..000000000 --- a/cleverhans/future/jax/attacks/projected_gradient_descent.py +++ /dev/null @@ -1,78 +0,0 @@ -import jax.numpy as np - -from cleverhans.future.jax.attacks import fast_gradient_method -from cleverhans.future.jax.utils import clip_eta, one_hot - - -def projected_gradient_descent(model_fn, x, eps, eps_iter, nb_iter, norm, - clip_min=None, clip_max=None, y=None, targeted=False, - rand_init=None, rand_minmax=0.3): - """ - This class implements either the Basic Iterative Method - (Kurakin et al. 2016) when rand_init is set to 0. or the - Madry et al. (2017) method when rand_minmax is larger than 0. - Paper link (Kurakin et al. 2016): https://arxiv.org/pdf/1607.02533.pdf - Paper link (Madry et al. 2017): https://arxiv.org/pdf/1706.06083.pdf - :param model_fn: a callable that takes an input tensor and returns the model logits. - :param x: input tensor. - :param eps: epsilon (input variation parameter); see https://arxiv.org/abs/1412.6572. - :param eps_iter: step size for each attack iteration - :param nb_iter: Number of attack iterations. - :param norm: Order of the norm (mimics NumPy). Possible values: np.inf or 2. - :param clip_min: (optional) float. Minimum float value for adversarial example components. - :param clip_max: (optional) float. Maximum float value for adversarial example components. - :param y: (optional) Tensor with true labels. If targeted is true, then provide the - target label. Otherwise, only provide this parameter if you'd like to use true - labels when crafting adversarial samples. Otherwise, model predictions are used - as labels to avoid the "label leaking" effect (explained in this paper: - https://arxiv.org/abs/1611.01236). Default is None. - :param targeted: (optional) bool. Is the attack targeted or untargeted? - Untargeted, the default, will try to make the label incorrect. - Targeted will instead try to move in the direction of being more like y. - :return: a tensor for the adversarial example - """ - - assert eps_iter <= eps, (eps_iter, eps) - if norm == 1: - raise NotImplementedError("It's not clear that FGM is a good inner loop" - " step for PGD when norm=1, because norm=1 FGM " - " changes only one pixel at a time. We need " - " to rigorously test a strong norm=1 PGD " - "before enabling this feature.") - if norm not in [np.inf, 2]: - raise ValueError("Norm order must be either np.inf or 2.") - - # Initialize loop variables - if rand_init: - rand_minmax = eps - eta = np.random.uniform(x.shape, -rand_minmax, rand_minmax) - else: - eta = np.zeros_like(x) - - # Clip eta - eta = clip_eta(eta, norm, eps) - adv_x = x + eta - if clip_min is not None or clip_max is not None: - adv_x = np.clip(adv_x, a_min=clip_min, a_max=clip_max) - - if y is None: - # Using model predictions as ground truth to avoid label leaking - x_labels = np.argmax(model_fn(x), 1) - y = one_hot(x_labels, 10) - - for _ in range(nb_iter): - adv_x = fast_gradient_method(model_fn, adv_x, eps_iter, norm, clip_min=clip_min, - clip_max=clip_max, y=y, targeted=targeted) - - # Clipping perturbation eta to norm norm ball - eta = adv_x - x - eta = clip_eta(eta, norm, eps) - adv_x = x + eta - - # Redo the clipping. - # FGM already did it, but subtracting and re-adding eta can add some - # small numerical error. - if clip_min is not None or clip_max is not None: - adv_x = np.clip(adv_x, a_min=clip_min, a_max=clip_max) - - return adv_x diff --git a/cleverhans/future/jax/utils.py b/cleverhans/future/jax/utils.py deleted file mode 100644 index 7b408ec3d..000000000 --- a/cleverhans/future/jax/utils.py +++ /dev/null @@ -1,37 +0,0 @@ -import jax.numpy as np - - -def one_hot(x, k, dtype=np.float32): - """Create a one-hot encoding of x of size k.""" - return np.array(x[:, None] == np.arange(k), dtype) - - -def partial_flatten(x): - """Flatten all but the first dimension of an ndarray.""" - return np.reshape(x, (x.shape[0], -1)) - - -def clip_eta(eta, norm, eps): - """ - Helper function to clip the perturbation to epsilon norm ball. - :param eta: A tensor with the current perturbation. - :param norm: Order of the norm (mimics Numpy). - Possible values: np.inf or 2. - :param eps: Epsilon, bound of the perturbation. - """ - - # Clipping perturbation eta to self.norm norm ball - if norm not in [np.inf, 2]: - raise ValueError('norm must be np.inf or 2.') - - axis = list(range(1, len(eta.shape))) - avoid_zero_div = 1e-12 - if norm == np.inf: - eta = np.clip(eta, a_min=-eps, a_max=eps) - elif norm == 2: - # avoid_zero_div must go inside sqrt to avoid a divide by zero in the gradient through this operation - norm = np.sqrt(np.maximum(avoid_zero_div, np.sum(np.square(eta), axis=axis, keepdims=True))) - # We must *clip* to within the norm ball, not *normalize* onto the surface of the ball - factor = np.minimum(1., np.divide(eps, norm)) - eta = eta * factor - return eta diff --git a/cleverhans/future/tf2/attacks/__init__.py b/cleverhans/future/tf2/attacks/__init__.py deleted file mode 100644 index e3e25989f..000000000 --- a/cleverhans/future/tf2/attacks/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -from cleverhans.future.tf2.attacks.fast_gradient_method import fast_gradient_method -from cleverhans.future.tf2.attacks.projected_gradient_descent import projected_gradient_descent -from cleverhans.future.tf2.attacks.spsa import spsa -from cleverhans.future.tf2.attacks.momentum_iterative_method import momentum_iterative_method -from cleverhans.future.tf2.attacks.madry_et_al import madry_et_al -from cleverhans.future.tf2.attacks.basic_iterative_method import basic_iterative_method diff --git a/cleverhans/future/tf2/attacks/basic_iterative_method.py b/cleverhans/future/tf2/attacks/basic_iterative_method.py deleted file mode 100644 index 0cb951f3b..000000000 --- a/cleverhans/future/tf2/attacks/basic_iterative_method.py +++ /dev/null @@ -1,16 +0,0 @@ -""" -The BasicIterativeMethod attack. -""" - -from cleverhans.future.tf2.attacks.projected_gradient_descent import projected_gradient_descent - - -def basic_iterative_method(model_fn, x, eps, eps_iter, nb_iter, norm, - clip_min=None, clip_max=None, y=None, targeted=False, - rand_init=None, rand_minmax=0.3, sanity_checks=True): - """ - The BasicIterativeMethod attack. - """ - return projected_gradient_descent(model_fn, x, eps, eps_iter, nb_iter, norm, - clip_min=clip_min, clip_max=clip_max, y=y, targeted=targeted, - rand_init=False, rand_minmax=rand_minmax, sanity_checks=sanity_checks) diff --git a/cleverhans/future/tf2/attacks/fast_gradient_method.py b/cleverhans/future/tf2/attacks/fast_gradient_method.py deleted file mode 100644 index 856008364..000000000 --- a/cleverhans/future/tf2/attacks/fast_gradient_method.py +++ /dev/null @@ -1,134 +0,0 @@ -"""The Fast Gradient Method attack.""" - -import numpy as np -import tensorflow as tf - - -def fast_gradient_method(model_fn, x, eps, norm, loss_fn=None, clip_min=None, clip_max=None, y=None, - targeted=False, sanity_checks=False): - """ - Tensorflow 2.0 implementation of the Fast Gradient Method. - :param model_fn: a callable that takes an input tensor and returns the model logits. - :param x: input tensor. - :param eps: epsilon (input variation parameter); see https://arxiv.org/abs/1412.6572. - :param norm: Order of the norm (mimics NumPy). Possible values: np.inf, 1 or 2. - :param loss_fn: (optional) callable. Loss function that takes (labels, logits) as arguments and returns loss. - default function is 'tf.nn.sparse_softmax_cross_entropy_with_logits' - :param clip_min: (optional) float. Minimum float value for adversarial example components. - :param clip_max: (optional) float. Maximum float value for adversarial example components. - :param y: (optional) Tensor with true labels. If targeted is true, then provide the - target label. Otherwise, only provide this parameter if you'd like to use true - labels when crafting adversarial samples. Otherwise, model predictions are used - as labels to avoid the "label leaking" effect (explained in this paper: - https://arxiv.org/abs/1611.01236). Default is None. - :param targeted: (optional) bool. Is the attack targeted or untargeted? - Untargeted, the default, will try to make the label incorrect. - Targeted will instead try to move in the direction of being more like y. - :param sanity_checks: bool, if True, include asserts (Turn them off to use less runtime / - memory or for unit tests that intentionally pass strange input) - :return: a tensor for the adversarial example - """ - if norm not in [np.inf, 1, 2]: - raise ValueError("Norm order must be either np.inf, 1, or 2.") - - if loss_fn is None: - loss_fn = tf.nn.sparse_softmax_cross_entropy_with_logits - - asserts = [] - - # If a data range was specified, check that the input was in that range - if clip_min is not None: - asserts.append(tf.math.greater_equal(x, clip_min)) - - if clip_max is not None: - asserts.append(tf.math.less_equal(x, clip_max)) - - if y is None: - # Using model predictions as ground truth to avoid label leaking - y = tf.argmax(model_fn(x), 1) - - grad = compute_gradient(model_fn, loss_fn, x, y, targeted) - - optimal_perturbation = optimize_linear(grad, eps, norm) - # Add perturbation to original example to obtain adversarial example - adv_x = x + optimal_perturbation - - # If clipping is needed, reset all values outside of [clip_min, clip_max] - if (clip_min is not None) or (clip_max is not None): - # We don't currently support one-sided clipping - assert clip_min is not None and clip_max is not None - adv_x = tf.clip_by_value(adv_x, clip_min, clip_max) - - if sanity_checks: - assert np.all(asserts) - return adv_x - - -# Due to performance reasons, this function is wrapped inside of tf.function decorator. -# Not using the decorator here, or letting the user wrap the attack in tf.function is way -# slower on Tensorflow 2.0.0-alpha0. -@tf.function -def compute_gradient(model_fn, loss_fn, x, y, targeted): - """ - Computes the gradient of the loss with respect to the input tensor. - :param model_fn: a callable that takes an input tensor and returns the model logits. - :param loss_fn: loss function that takes (labels, logits) as arguments and returns loss. - :param x: input tensor - :param y: Tensor with true labels. If targeted is true, then provide the target label. - :param targeted: bool. Is the attack targeted or untargeted? Untargeted, the default, will - try to make the label incorrect. Targeted will instead try to move in the - direction of being more like y. - :return: A tensor containing the gradient of the loss with respect to the input tensor. - """ - - with tf.GradientTape() as g: - g.watch(x) - # Compute loss - loss = loss_fn(labels=y, logits=model_fn(x)) - if targeted: # attack is targeted, minimize loss of target label rather than maximize loss of correct label - loss = -loss - - # Define gradient of loss wrt input - grad = g.gradient(loss, x) - return grad - - -def optimize_linear(grad, eps, norm=np.inf): - """ - Solves for the optimal input to a linear function under a norm constraint. - - Optimal_perturbation = argmax_{eta, ||eta||_{norm} < eps} dot(eta, grad) - - :param grad: tf tensor containing a batch of gradients - :param eps: float scalar specifying size of constraint region - :param norm: int specifying order of norm - :returns: - tf tensor containing optimal perturbation - """ - - # Convert the iterator returned by `range` into a list. - axis = list(range(1, len(grad.get_shape()))) - avoid_zero_div = 1e-12 - if norm == np.inf: - # Take sign of gradient - optimal_perturbation = tf.sign(grad) - # The following line should not change the numerical results. It applies only because - # `optimal_perturbation` is the output of a `sign` op, which has zero derivative anyway. - # It should not be applied for the other norms, where the perturbation has a non-zero derivative. - optimal_perturbation = tf.stop_gradient(optimal_perturbation) - elif norm == 1: - abs_grad = tf.abs(grad) - sign = tf.sign(grad) - max_abs_grad = tf.reduce_max(abs_grad, axis, keepdims=True) - tied_for_max = tf.dtypes.cast(tf.equal(abs_grad, max_abs_grad), dtype=tf.float32) - num_ties = tf.reduce_sum(tied_for_max, axis, keepdims=True) - optimal_perturbation = sign * tied_for_max / num_ties - elif norm == 2: - square = tf.maximum(avoid_zero_div, tf.reduce_sum(tf.square(grad), axis, keepdims=True)) - optimal_perturbation = grad / tf.sqrt(square) - else: - raise NotImplementedError("Only L-inf, L1 and L2 norms are currently implemented.") - - # Scale perturbation to be the solution for the norm=eps rather than norm=1 problem - scaled_perturbation = tf.multiply(eps, optimal_perturbation) - return scaled_perturbation diff --git a/cleverhans/future/tf2/attacks/madry_et_al.py b/cleverhans/future/tf2/attacks/madry_et_al.py deleted file mode 100644 index 216f98d0d..000000000 --- a/cleverhans/future/tf2/attacks/madry_et_al.py +++ /dev/null @@ -1,16 +0,0 @@ -""" -The MadryEtAl attack -""" - -from cleverhans.future.tf2.attacks.projected_gradient_descent import projected_gradient_descent - - -def madry_et_al(model_fn, x, eps, eps_iter, nb_iter, norm, - clip_min=None, clip_max=None, y=None, targeted=False, - rand_minmax=0.3, sanity_checks=True): - """ - The attack from Madry et al 2017 - """ - return projected_gradient_descent(model_fn, x, eps, eps_iter, nb_iter, norm, - clip_min=clip_min, clip_max=clip_max, y=y, targeted=targeted, - rand_init=True, rand_minmax=rand_minmax, sanity_checks=sanity_checks) diff --git a/cleverhans/future/tf2/attacks/momentum_iterative_method.py b/cleverhans/future/tf2/attacks/momentum_iterative_method.py deleted file mode 100644 index f1eb1ab8f..000000000 --- a/cleverhans/future/tf2/attacks/momentum_iterative_method.py +++ /dev/null @@ -1,97 +0,0 @@ -"""The MomentumIterativeMethod attack.""" - -import numpy as np -import tensorflow as tf - -from cleverhans.compat import reduce_mean - -from cleverhans.future.tf2.attacks.fast_gradient_method import optimize_linear, compute_gradient -from cleverhans.future.tf2.utils_tf import clip_eta - - -def momentum_iterative_method(model_fn, x, eps=0.3, eps_iter=0.06, nb_iter=10, norm=np.inf, - clip_min=None, clip_max=None, y=None, targeted=False, - decay_factor=1.0, sanity_checks=True): - """ - Tensorflow 2.0 implementation of Momentum Iterative Method (Dong et al. 2017). - This method won the first places in NIPS 2017 Non-targeted Adversarial Attacks - and Targeted Adversarial Attacks. The original paper used hard labels - for this attack; no label smoothing. - Paper link: https://arxiv.org/pdf/1710.06081.pdf - :param model_fn: a callable that takes an input tensor and returns the model logits. - :param x: input tensor. - :param eps: (optional float) maximum distortion of adversarial example - compared to original input - :param eps_iter: (optional float) step size for each attack iteration - :param nb_iter: (optional int) Number of attack iterations. - :param norm: (optional) Order of the norm (mimics Numpy). - Possible values: np.inf, 1 or 2. - :param clip_min: (optional float) Minimum input component value - :param clip_max: (optional float) Maximum input component value - :param y: (optional) Tensor with true labels. If targeted is true, then provide the - target label. Otherwise, only provide this parameter if you'd like to use true - labels when crafting adversarial samples. Otherwise, model predictions are used - as labels to avoid the "label leaking" effect (explained in this paper: - https://arxiv.org/abs/1611.01236). Default is None. - :param targeted: (optional) bool. Is the attack targeted or untargeted? - Untargeted, the default, will try to make the label incorrect. - Targeted will instead try to move in the direction of being more like y. - :param decay_factor: (optional) Decay factor for the momentum term. - :param sanity_checks: bool, if True, include asserts (Turn them off to use less runtime / - memory or for unit tests that intentionally pass strange input) - :return: a tensor for the adversarial example - """ - - if norm == 1: - raise NotImplementedError("This attack hasn't been tested for norm=1." - "It's not clear that FGM makes a good inner " - "loop step for iterative optimization since " - "it updates just one coordinate at a time.") - - # Check if order of the norm is acceptable given current implementation - if norm not in [np.inf, 1, 2]: - raise ValueError("Norm order must be either np.inf, 1, or 2.") - - asserts = [] - - # If a data range was specified, check that the input was in that range - if clip_min is not None: - asserts.append(tf.math.greater_equal(x, clip_min)) - - if clip_max is not None: - asserts.append(tf.math.less_equal(x, clip_max)) - - if y is None: - # Using model predictions as ground truth to avoid label leaking - y = tf.argmax(model_fn(x), 1) - - # Initialize loop variables - momentum = tf.zeros_like(x) - adv_x = x - - i = 0 - while i < nb_iter: - # Define gradient of loss wrt input - grad = compute_gradient(model_fn, adv_x, y, targeted) - - # Normalize current gradient and add it to the accumulated gradient - red_ind = list(range(1, len(grad.shape))) - avoid_zero_div = tf.cast(1e-12, grad.dtype) - grad = grad / tf.math.maximum( - avoid_zero_div, - tf.math.reduce_mean(tf.math.abs(grad), red_ind, keepdims=True)) - momentum = decay_factor * momentum + grad - - optimal_perturbation = optimize_linear(momentum, eps_iter, norm) - # Update and clip adversarial example in current iteration - adv_x = adv_x + optimal_perturbation - adv_x = x + clip_eta(adv_x - x, norm, eps) - - if clip_min is not None and clip_max is not None: - adv_x = tf.clip_by_value(adv_x, clip_min, clip_max) - i += 1 - - if sanity_checks: - assert np.all(asserts) - - return adv_x diff --git a/cleverhans/future/tf2/attacks/projected_gradient_descent.py b/cleverhans/future/tf2/attacks/projected_gradient_descent.py deleted file mode 100644 index e6d44e1d3..000000000 --- a/cleverhans/future/tf2/attacks/projected_gradient_descent.py +++ /dev/null @@ -1,112 +0,0 @@ -"""The Projected Gradient Descent attack.""" - -import numpy as np -import tensorflow as tf - -from cleverhans.future.tf2.attacks.fast_gradient_method import fast_gradient_method -from cleverhans.future.tf2.utils_tf import clip_eta, random_lp_vector - - -def projected_gradient_descent(model_fn, x, eps, eps_iter, nb_iter, norm, loss_fn=None, - clip_min=None, clip_max=None, y=None, targeted=False, - rand_init=None, rand_minmax=None, sanity_checks=False): - """ - This class implements either the Basic Iterative Method - (Kurakin et al. 2016) when rand_init is set to 0. or the - Madry et al. (2017) method when rand_minmax is larger than 0. - Paper link (Kurakin et al. 2016): https://arxiv.org/pdf/1607.02533.pdf - Paper link (Madry et al. 2017): https://arxiv.org/pdf/1706.06083.pdf - :param model_fn: a callable that takes an input tensor and returns the model logits. - :param x: input tensor. - :param eps: epsilon (input variation parameter); see https://arxiv.org/abs/1412.6572. - :param eps_iter: step size for each attack iteration - :param nb_iter: Number of attack iterations. - :param norm: Order of the norm (mimics NumPy). Possible values: np.inf, 1 or 2. - :param loss_fn: (optional) callable. loss function that takes (labels, logits) as arguments and returns loss. - default function is 'tf.nn.sparse_softmax_cross_entropy_with_logits' - :param clip_min: (optional) float. Minimum float value for adversarial example components. - :param clip_max: (optional) float. Maximum float value for adversarial example components. - :param y: (optional) Tensor with true labels. If targeted is true, then provide the - target label. Otherwise, only provide this parameter if you'd like to use true - labels when crafting adversarial samples. Otherwise, model predictions are used - as labels to avoid the "label leaking" effect (explained in this paper: - https://arxiv.org/abs/1611.01236). Default is None. - :param targeted: (optional) bool. Is the attack targeted or untargeted? - Untargeted, the default, will try to make the label incorrect. - Targeted will instead try to move in the direction of being more like y. - :param rand_init: (optional) float. Start the gradient descent from a point chosen - uniformly at random in the norm ball of radius - rand_init_eps - :param rand_minmax: (optional) float. Size of the norm ball from which - the initial starting point is chosen. Defaults to eps - :param sanity_checks: bool, if True, include asserts (Turn them off to use less runtime / - memory or for unit tests that intentionally pass strange input) - :return: a tensor for the adversarial example - """ - - assert eps_iter <= eps, (eps_iter, eps) - if norm == 1: - raise NotImplementedError("It's not clear that FGM is a good inner loop" - " step for PGD when norm=1, because norm=1 FGM " - " changes only one pixel at a time. We need " - " to rigorously test a strong norm=1 PGD " - "before enabling this feature.") - if norm not in [np.inf, 2]: - raise ValueError("Norm order must be either np.inf or 2.") - - if loss_fn is None: - loss_fn = tf.nn.sparse_softmax_cross_entropy_with_logits - - asserts = [] - - # If a data range was specified, check that the input was in that range - if clip_min is not None: - asserts.append(tf.math.greater_equal(x, clip_min)) - - if clip_max is not None: - asserts.append(tf.math.less_equal(x, clip_max)) - - # Initialize loop variables - if rand_minmax is None: - rand_minmax = eps - - if rand_init: - eta = random_lp_vector(tf.shape(x), norm, tf.cast(rand_minmax, x.dtype), dtype=x.dtype) - else: - eta = tf.zeros_like(x) - - # Clip eta - eta = clip_eta(eta, norm, eps) - adv_x = x + eta - if clip_min is not None or clip_max is not None: - adv_x = tf.clip_by_value(adv_x, clip_min, clip_max) - - if y is None: - # Using model predictions as ground truth to avoid label leaking - y = tf.argmax(model_fn(x), 1) - - i = 0 - while i < nb_iter: - adv_x = fast_gradient_method(model_fn, adv_x, eps_iter, norm, loss_fn, clip_min=clip_min, - clip_max=clip_max, y=y, targeted=targeted) - - # Clipping perturbation eta to norm norm ball - eta = adv_x - x - eta = clip_eta(eta, norm, eps) - adv_x = x + eta - - # Redo the clipping. - # FGM already did it, but subtracting and re-adding eta can add some - # small numerical error. - if clip_min is not None or clip_max is not None: - adv_x = tf.clip_by_value(adv_x, clip_min, clip_max) - i += 1 - - asserts.append(eps_iter <= eps) - if norm == np.inf and clip_min is not None: - # TODO necessary to cast to x.dtype? - asserts.append(eps + clip_min <= clip_max) - - if sanity_checks: - assert np.all(asserts) - return adv_x diff --git a/cleverhans/future/tf2/attacks/spsa.py b/cleverhans/future/tf2/attacks/spsa.py deleted file mode 100644 index 80a20312c..000000000 --- a/cleverhans/future/tf2/attacks/spsa.py +++ /dev/null @@ -1,356 +0,0 @@ -# pylint: disable=missing-docstring - -import tensorflow as tf - -tf_dtype = tf.as_dtype('float32') - - -def spsa(model_fn, x, y, eps, nb_iter, clip_min=None, clip_max=None, targeted=False, - early_stop_loss_threshold=None, learning_rate=0.01, delta=0.01, spsa_samples=128, - spsa_iters=1, is_debug=False): - """Tensorflow 2.0 implementation of SPSA. - - This implements the SPSA adversary, as in https://arxiv.org/abs/1802.05666 (Uesato et al. 2018). - SPSA is a gradient-free optimization method, which is useful when the model is non-differentiable, - or more generally, the gradients do not point in useful directions. - :param model_fn: A callable that takes an input tensor and returns the model logits. - :param x: Input tensor. - :param y: Tensor with true labels. If targeted is true, then provide the target label. - :param eps: The size of the maximum perturbation, measured in the L-infinity norm. - :param nb_iter: The number of optimization steps. - :param clip_min: If specified, the minimum input value. - :param clip_max: If specified, the maximum input value. - :param targeted: (optional) bool. Is the attack targeted or untargeted? Untargeted, the default, - will try to make the label incorrect. Targeted will instead try to move in the direction - of being more like y. - :param early_stop_loss_threshold: A float or None. If specified, the attack will end as soon as - the loss is below `early_stop_loss_threshold`. - :param learning_rate: Learning rate of ADAM optimizer. - :param delta: Perturbation size used for SPSA approximation. - :param spsa_samples: Number of inputs to evaluate at a single time. The true batch size - (the number of evaluated inputs for each update) is `spsa_samples * - spsa_iters` - :param spsa_iters: Number of model evaluations before performing an update, where each evaluation - is on `spsa_samples` different inputs. - :param is_debug: If True, print the adversarial loss after each update. - """ - if x.get_shape().as_list()[0] != 1: - raise ValueError("For SPSA, input tensor x must have batch_size of 1.") - - optimizer = SPSAAdam(lr=learning_rate, delta=delta, num_samples=spsa_samples, - num_iters=spsa_iters) - - def loss_fn(x, label): - """ - Margin logit loss, with correct sign for targeted vs untargeted loss. - """ - logits = model_fn(x) - loss_multiplier = 1 if targeted else -1 - return loss_multiplier * margin_logit_loss(logits, label, nb_classes=logits.get_shape()[-1]) - - adv_x = projected_optimization(loss_fn, x, y, eps, nb_iter, optimizer, clip_min, clip_max, - early_stop_loss_threshold, is_debug=is_debug) - - return adv_x - - -class SPSAAdam(tf.optimizers.Adam): - """Optimizer for gradient-free attacks in https://arxiv.org/abs/1802.05666. - - Gradients estimates are computed using Simultaneous Perturbation Stochastic Approximation (SPSA), - combined with the ADAM update rule (https://arxiv.org/abs/1412.6980). - """ - - def __init__(self, lr=0.01, delta=0.01, num_samples=128, num_iters=1, - compare_to_analytic_grad=False): - super(SPSAAdam, self).__init__(lr=lr) - assert num_samples % 2 == 0, "number of samples must be even" - self._delta = delta - self._num_samples = num_samples // 2 # Since we mirror +/- delta later - self._num_iters = num_iters - self._compare_to_analytic_grad = compare_to_analytic_grad - - def _get_delta(self, x, delta): - x_shape = x.get_shape().as_list() - delta_x = delta * tf.sign( - tf.random.uniform([self._num_samples] + x_shape[1:], minval=-1., maxval=1., dtype=tf_dtype) - ) - return delta_x - - def _compute_gradients(self, loss_fn, x): - """Compute a new value of `x` to minimize `loss_fn` using SPSA. - - Args: - loss_fn: a callable that takes `x`, a batch of images, and returns a batch of loss values. - `x` will be optimized to minimize `loss_fn(x)`. - x: A list of Tensors, the values to be updated. This is analogous to the `var_list` argument - in standard TF Optimizer. - - Returns: - new_x: A list of Tensors, the same length as `x`, which are updated - new_optim_state: A dict, with the same structure as `optim_state`, which have been updated. - """ - - # Assumes `x` is a list, containing a [1, H, W, C] image.If static batch dimension is None, - # tf.reshape to batch size 1 so that static shape can be inferred. - assert len(x) == 1 - static_x_shape = x[0].get_shape().as_list() - if static_x_shape[0] is None: - x[0] = tf.reshape(x[0], [1] + static_x_shape[1:]) - assert x[0].get_shape().as_list()[0] == 1 - x = x[0] - x_shape = x.get_shape().as_list() - - def body(i, grad_array): - delta = self._delta - delta_x = self._get_delta(x, delta) - delta_x = tf.concat([delta_x, -delta_x], axis=0) - loss_vals = tf.reshape( - loss_fn(x + delta_x), - [2 * self._num_samples] + [1] * (len(x_shape) - 1)) - avg_grad = tf.reduce_mean(loss_vals * delta_x, axis=0) / delta - avg_grad = tf.expand_dims(avg_grad, axis=0) - new_grad_array = grad_array.write(i, avg_grad) - return i + 1, new_grad_array - - def cond(i, _): - return i < self._num_iters - - _, all_grads = tf.while_loop( - cond, - body, - loop_vars=[ - 0, tf.TensorArray(size=self._num_iters, dtype=tf_dtype) - ], - back_prop=False, - parallel_iterations=1) - avg_grad = tf.reduce_sum(all_grads.stack(), axis=0) - return [avg_grad] - - def _apply_gradients(self, grads, x, optim_state): - """Given a gradient, make one optimization step. - - :param grads: list of tensors, same length as `x`, containing the corresponding gradients - :param x: list of tensors to update - :param optim_state: dict - - Returns: - new_x: list of tensors, updated version of `x` - new_optim_state: dict, updated version of `optim_state` - """ - - new_x = [None] * len(x) - new_optim_state = { - "t": optim_state["t"] + 1., - "m": [None] * len(x), - "u": [None] * len(x) - } - t = new_optim_state["t"] - for i in range(len(x)): - g = grads[i] - m_old = optim_state["m"][i] - u_old = optim_state["u"][i] - new_optim_state["m"][i] = (self.beta_1 * m_old + (1. - self.beta_1) * g) - new_optim_state["u"][i] = (self.beta_2 * u_old + (1. - self.beta_2) * g * g) - m_hat = new_optim_state["m"][i] / (1. - tf.pow(self.beta_1, t)) - u_hat = new_optim_state["u"][i] / (1. - tf.pow(self.beta_2, t)) - new_x[i] = (x[i] - self.lr * m_hat / (tf.sqrt(u_hat) + self.epsilon)) - return new_x, new_optim_state - - def init_state(self, x): - """Initialize t, m, and u""" - optim_state = { - "t": 0., - "m": [tf.zeros_like(v) for v in x], - "u": [tf.zeros_like(v) for v in x] - } - return optim_state - - def minimize(self, loss_fn, x, optim_state): - """Analogous to tf.Optimizer.minimize - - :param loss_fn: tf Tensor, representing the loss to minimize - :param x: list of Tensor, analogous to tf.Optimizer's var_list - :param optim_state: A possibly nested dict, containing any optimizer state. - - Returns: - new_x: list of Tensor, updated version of `x` - new_optim_state: dict, updated version of `optim_state` - """ - grads = self._compute_gradients(loss_fn, x) - return self._apply_gradients(grads, x, optim_state) - - -def margin_logit_loss(model_logits, label, nb_classes=10): - """Computes difference between logit for `label` and next highest logit. - - The loss is high when `label` is unlikely (targeted by default). This follows the same interface - as `loss_fn` for projected_optimization, i.e. it returns a batch of loss values. - """ - - if 'int' in str(label.dtype): - logit_mask = tf.one_hot(label, depth=nb_classes, axis=-1) - else: - logit_mask = label - if 'int' in str(logit_mask.dtype): - logit_mask = tf.cast(logit_mask, dtype=tf.float32) - try: - label_logits = tf.reduce_sum(logit_mask * model_logits, axis=-1) - except TypeError: - raise TypeError( - "Could not take row-wise dot product between logit mask, of dtype " + str(logit_mask.dtype) - + " and model_logits, of dtype " + str(model_logits.dtype) - ) - logits_with_target_label_neg_inf = model_logits - logit_mask * 99999 - highest_nonlabel_logits = tf.reduce_max(logits_with_target_label_neg_inf, axis=-1) - loss = highest_nonlabel_logits - label_logits - return loss - - -def _project_perturbation(perturbation, epsilon, input_image, clip_min=None, clip_max=None): - """ - Project `perturbation` onto L-infinity ball of radius `epsilon`. Also project into hypercube such - that the resulting adversarial example is between clip_min and clip_max, if applicable. - """ - - if clip_min is None or clip_max is None: - raise NotImplementedError("_project_perturbation currently has clipping hard-coded in.") - - # Ensure inputs are in the correct range - with tf.control_dependencies([ - tf.debugging.assert_less_equal(input_image, tf.cast(clip_max, input_image.dtype)), - tf.debugging.assert_greater_equal(input_image, tf.cast(clip_min, input_image.dtype)) - ]): - clipped_perturbation = tf.clip_by_value(perturbation, -epsilon, epsilon) - new_image = tf.clip_by_value(input_image + clipped_perturbation, clip_min, clip_max) - return new_image - input_image - - -def projected_optimization(loss_fn, input_image, label, epsilon, num_steps, optimizer, - clip_min=None, clip_max=None, early_stop_loss_threshold=None, - project_perturbation=_project_perturbation, - is_debug=False): - """ - Generic projected optimization, generalized to work with approximate gradients. Used for e.g. - the SPSA attack. - - Args: - :param loss_fn: A callable which takes `input_image` and `label` as - arguments, and returns a batch of loss values. - :param input_image: Tensor, a batch of images - :param label: Tensor, a batch of labels - :param epsilon: float, the L-infinity norm of the maximum allowable - perturbation - :param num_steps: int, the number of steps of gradient descent - :param optimizer: A `SPSAAdam` object - :param clip_min: float, minimum pixel value - :param clip_max: float, maximum pixel value - :param project_perturbation: A function, which will be used to enforce - some constraint. It should have the same - signature as `_project_perturbation`. - :param early_stop_loss_threshold: A float or None. If specified, the attack will end if the loss is below - `early_stop_loss_threshold`. - Enabling this option can have several different effects: - - Setting the threshold to 0. guarantees that if a successful attack is found, it is returned. - This increases the attack success rate, because without early stopping the optimizer can accidentally - bounce back to a point where the attack fails. - - Early stopping can make the attack run faster because it may run for fewer steps. - - Early stopping can make the attack run slower because the loss must be calculated at each step. - The loss is not calculated as part of the normal SPSA optimization procedure. - For most reasonable choices of hyperparameters, early stopping makes the attack much faster because - it decreases the number of steps dramatically. - :param is_debug: A bool. If True, print debug info for attack progress. - - Returns: - adversarial version of `input_image`, with L-infinity difference less than epsilon, which tries - to minimize loss_fn. - - Note that this function is not intended as an Attack by itself. Rather, it is designed as a helper - function which you can use to write your own attack methods. The method uses a tf.while_loop to - optimize a loss function in a single sess.run() call. - """ - assert num_steps is not None - if is_debug: - with tf.device("/cpu:0"): - tf.print("Starting PGD attack with epsilon: %s" % epsilon) - - init_perturbation = tf.random.uniform(tf.shape(input_image), - minval=tf.cast(-epsilon, input_image.dtype), - maxval=tf.cast(epsilon, input_image.dtype), - dtype=input_image.dtype) - init_perturbation = project_perturbation(init_perturbation, epsilon, input_image, - clip_min=clip_min, clip_max=clip_max) - init_optim_state = optimizer.init_state([init_perturbation]) - - def loop_body(i, perturbation, flat_optim_state): - """Update perturbation to input image.""" - optim_state = tf.nest.pack_sequence_as(structure=init_optim_state, - flat_sequence=flat_optim_state) - - def wrapped_loss_fn(x): - return loss_fn(input_image + x, label) - - new_perturbation_list, new_optim_state = optimizer.minimize(wrapped_loss_fn, [perturbation], - optim_state) - projected_perturbation = project_perturbation(new_perturbation_list[0], epsilon, input_image, - clip_min=clip_min, clip_max=clip_max) - - # Be careful with this bool. A value of 0. is a valid threshold but evaluates to False, so we - # must explicitly check whether the value is None. - early_stop = early_stop_loss_threshold is not None - compute_loss = is_debug or early_stop - # Don't waste time building the loss graph if we're not going to use it - if compute_loss: - # NOTE: this step is not actually redundant with the optimizer step. - # SPSA calculates the loss at randomly perturbed points but doesn't calculate the loss at the current point. - loss = tf.reduce_mean(wrapped_loss_fn(projected_perturbation), axis=0) - - if is_debug: - with tf.device("/cpu:0"): - tf.print(loss, "Total batch loss") - - if early_stop: - i = tf.cond(tf.less(loss, early_stop_loss_threshold), lambda: float(num_steps), lambda: i) - - return i + 1, projected_perturbation, tf.nest.flatten(new_optim_state) - - def cond(i, *_): - return tf.less(i, num_steps) - - flat_init_optim_state = tf.nest.flatten(init_optim_state) - _, final_perturbation, _ = tf.while_loop( - cond, - loop_body, - loop_vars=(tf.constant(0.), init_perturbation, flat_init_optim_state), - parallel_iterations=1, - back_prop=False, - maximum_iterations=num_steps - ) - - if project_perturbation is _project_perturbation: - # TODO: this assert looks totally wrong. - # Not bothering to fix it now because it's only an assert. - # 1) Multiplying by 1.1 gives a huge margin of error. This should probably take the difference - # and allow a tolerance of 1e-6 or something like that. - # 2) I think it should probably check the *absolute value* of final_perturbation - perturbation_max = epsilon * 1.1 - check_diff = tf.debugging.assert_less_equal( - final_perturbation, - tf.cast(perturbation_max, final_perturbation.dtype), - message="final_perturbation must change no pixel by more than %s" % perturbation_max - ) - else: - # TODO: let caller pass in a check_diff function as well as - # project_perturbation - check_diff = tf.no_op() - - if clip_min is None or clip_max is None: - raise NotImplementedError("This function only supports clipping for now") - check_range = [ - tf.debugging.assert_less_equal(input_image, tf.cast(clip_max, input_image.dtype)), - tf.debugging.assert_greater_equal(input_image, tf.cast(clip_min, input_image.dtype)) - ] - - with tf.control_dependencies([check_diff] + check_range): - adversarial_image = input_image + final_perturbation - return tf.stop_gradient(adversarial_image) diff --git a/cleverhans/future/tf2/utils_tf.py b/cleverhans/future/tf2/utils_tf.py deleted file mode 100644 index 443844856..000000000 --- a/cleverhans/future/tf2/utils_tf.py +++ /dev/null @@ -1,110 +0,0 @@ -import numpy as np -import tensorflow as tf - - -def clip_eta(eta, norm, eps): - """ - Helper function to clip the perturbation to epsilon norm ball. - :param eta: A tensor with the current perturbation. - :param norm: Order of the norm (mimics Numpy). - Possible values: np.inf, 1 or 2. - :param eps: Epsilon, bound of the perturbation. - """ - - # Clipping perturbation eta to self.norm norm ball - if norm not in [np.inf, 1, 2]: - raise ValueError('norm must be np.inf, 1, or 2.') - axis = list(range(1, len(eta.get_shape()))) - avoid_zero_div = 1e-12 - if norm == np.inf: - eta = tf.clip_by_value(eta, -eps, eps) - else: - if norm == 1: - raise NotImplementedError("") - # This is not the correct way to project on the L1 norm ball: - # norm = tf.maximum(avoid_zero_div, reduce_sum(tf.abs(eta), reduc_ind, keepdims=True)) - elif norm == 2: - # avoid_zero_div must go inside sqrt to avoid a divide by zero in the gradient through this operation - norm = tf.sqrt( - tf.maximum(avoid_zero_div, tf.reduce_sum(tf.square(eta), axis, keepdims=True))) - # We must *clip* to within the norm ball, not *normalize* onto the surface of the ball - factor = tf.minimum(1., tf.math.divide(eps, norm)) - eta = eta * factor - return eta - - -def random_exponential(shape, rate=1.0, dtype=tf.float32, seed=None): - """ - Helper function to sample from the exponential distribution, which is not - included in core TensorFlow. - - shape: shape of the sampled tensor. - :rate: (optional) rate parameter of the exponential distribution, defaults to 1.0. - :dtype: (optional) data type of the sempled tensor, defaults to tf.float32. - :seed: (optional) custom seed to be used for sampling. - """ - return tf.random.gamma(shape, alpha=1, beta=1. / rate, dtype=dtype, seed=seed) - - -def random_laplace(shape, loc=0.0, scale=1.0, dtype=tf.float32, seed=None): - """ - Helper function to sample from the Laplace distribution, which is not - included in core TensorFlow. - - :shape: shape of the sampled tensor. - :loc: (optional) mean of the laplace distribution, defaults to 0.0. - :scale: (optional) scale parameter of the laplace diustribution, defaults to 1.0. - :dtype: (optional) data type of the sempled tensor, defaults to tf.float32. - :seed: (optional) custom seed to be used for sampling. - """ - z1 = random_exponential(shape, 1. / scale, dtype=dtype, seed=seed) - z2 = random_exponential(shape, 1. / scale, dtype=dtype, seed=seed) - return z1 - z2 + loc - -def random_lp_vector(shape, ord, eps, dtype=tf.float32, seed=None): - """ - Helper function to generate uniformly random vectors from a norm ball of - radius epsilon. - :param shape: Output shape of the random sample. The shape is expected to be - of the form `(n, d1, d2, ..., dn)` where `n` is the number of - i.i.d. samples that will be drawn from a norm ball of dimension - `d1*d1*...*dn`. - :param ord: Order of the norm (mimics Numpy). - Possible values: np.inf, 1 or 2. - :param eps: Epsilon, radius of the norm ball. - :param dtype: (optional) type of the tensor. - :param seed: (optional) integer. - """ - if ord not in [np.inf, 1, 2]: - raise ValueError('ord must be np.inf, 1, or 2.') - - if ord == np.inf: - r = tf.random.uniform(shape, -eps, eps, dtype=dtype, seed=seed) - else: - - # For ord=1 and ord=2, we use the generic technique from - # (Calafiore et al. 1998) to sample uniformly from a norm ball. - # Paper link (Calafiore et al. 1998): - # https://ieeexplore.ieee.org/stamp/stamp.jsp?arnumber=758215&tag=1 - # We first sample from the surface of the norm ball, and then scale by - # a factor `w^(1/d)` where `w~U[0,1]` is a standard uniform random variable - # and `d` is the dimension of the ball. In high dimensions, this is roughly - # equivalent to sampling from the surface of the ball. - - dim = tf.reduce_prod(shape[1:]) - - if ord == 1: - x = random_laplace((shape[0], dim), loc=1.0, scale=1.0, dtype=dtype, - seed=seed) - norm = tf.reduce_sum(tf.abs(x), axis=-1, keepdims=True) - elif ord == 2: - x = tf.random.normal((shape[0], dim), dtype=dtype, seed=seed) - norm = tf.sqrt(tf.reduce_sum(tf.square(x), axis=-1, keepdims=True)) - else: - raise ValueError('ord must be np.inf, 1, or 2.') - - w = tf.pow(tf.random.uniform((shape[0], 1), dtype=dtype, seed=seed), - 1.0 / tf.cast(dim, dtype)) - r = eps * tf.reshape(w * x / norm, shape) - - return r diff --git a/cleverhans/future/torch/attacks/__init__.py b/cleverhans/future/torch/attacks/__init__.py deleted file mode 100644 index aca94e6d2..000000000 --- a/cleverhans/future/torch/attacks/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -# pylint: disable=missing-docstring -from cleverhans.future.torch.attacks.fast_gradient_method import fast_gradient_method -from cleverhans.future.torch.attacks.projected_gradient_descent import projected_gradient_descent -from cleverhans.future.torch.attacks.noise import noise -from cleverhans.future.torch.attacks.semantic import semantic -from cleverhans.future.torch.attacks.spsa import spsa -from cleverhans.future.torch.attacks.hop_skip_jump_attack import hop_skip_jump_attack -from cleverhans.future.torch.attacks.sparse_l1_descent import sparse_l1_descent -from cleverhans.future.torch.attacks.carlini_wagner_l2 import carlini_wagner_l2 diff --git a/cleverhans/future/torch/attacks/carlini_wagner_l2.py b/cleverhans/future/torch/attacks/carlini_wagner_l2.py deleted file mode 100644 index 687dc3bde..000000000 --- a/cleverhans/future/torch/attacks/carlini_wagner_l2.py +++ /dev/null @@ -1,209 +0,0 @@ -"""The CarliniWagnerL2 attack.""" -import torch - - -INF = float('inf') -def carlini_wagner_l2(model_fn, x, n_classes, - y=None, - targeted=False, - lr=5e-3, - confidence=0, - clip_min=0, - clip_max=1, - initial_const=1e-2, - binary_search_steps=5, - max_iterations=1000, - ): - """ - This attack was originally proposed by Carlini and Wagner. It is an - iterative attack that finds adversarial examples on many defenses that - are robust to other attacks. - Paper link: https://arxiv.org/abs/1608.04644 - - At a high level, this attack is an iterative attack using Adam and - a specially-chosen loss function to find adversarial examples with - lower distortion than other attacks. This comes at the cost of speed, - as this attack is often much slower than others. - - :param model_fn: a callable that takes an input tensor and returns - the model logits. The logits should be a tensor of shape - (n_examples, n_classes). - :param x: input tensor of shape (n_examples, ...), where ... can - be any arbitrary dimension that is compatible with - model_fn. - :param n_classes: the number of classes. - :param y: (optional) Tensor with true labels. If targeted is true, - then provide the target label. Otherwise, only provide - this parameter if you'd like to use true labels when - crafting adversarial samples. Otherwise, model predictions - are used as labels to avoid the "label leaking" effect - (explained in this paper: - https://arxiv.org/abs/1611.01236). If provide y, it - should be a 1D tensor of shape (n_examples, ). - Default is None. - :param targeted: (optional) bool. Is the attack targeted or - untargeted? Untargeted, the default, will try to make the - label incorrect. Targeted will instead try to move in the - direction of being more like y. - :param lr: (optional) float. The learning rate for the attack - algorithm. Default is 5e-3. - :param confidence: (optional) float. Confidence of adversarial - examples: higher produces examples with larger l2 - distortion, but more strongly classified as adversarial. - Default is 0. - :param clip_min: (optional) float. Minimum float value for - adversarial example components. Default is 0. - :param clip_max: (optional) float. Maximum float value for - adversarial example components. Default is 1. - :param initial_const: The initial tradeoff-constant to use to tune the - relative importance of size of the perturbation and - confidence of classification. If binary_search_steps is - large, the initial constant is not important. A smaller - value of this constant gives lower distortion results. - Default is 1e-2. - :param binary_search_steps: (optional) int. The number of times we - perform binary search to find the optimal tradeoff-constant - between norm of the perturbation and confidence of the - classification. Default is 5. - :param max_iterations: (optional) int. The maximum number of - iterations. Setting this to a larger value will produce - lower distortion results. Using only a few iterations - requires a larger learning rate, and will produce larger - distortion results. Default is 1000. - """ - def compare(pred, label, is_logits=False): - """ - A helper function to compare prediction against a label. - Returns true if the attack is considered successful. - - :param pred: can be either a 1D tensor of logits or a predicted - class (int). - :param label: int. A label to compare against. - :param is_logits: (optional) bool. If True, treat pred as an - array of logits. Default is False. - """ - - # Convert logits to predicted class if necessary - if is_logits: - pred_copy = pred.clone().detach() - pred_copy[label] += (-confidence if targeted else confidence) - pred = torch.argmax(pred_copy) - - return pred == label if targeted else pred != label - - - if y is None: - # Using model predictions as ground truth to avoid label leaking - pred = model_fn(x) - y = torch.argmax(pred, 1) - - # Initialize some values needed for binary search on const - lower_bound = [0.] * len(x) - upper_bound = [1e10] * len(x) - const = x.new_ones(len(x), 1) * initial_const - - o_bestl2 = [INF] * len(x) - o_bestscore = [-1.] * len(x) - x = torch.clamp(x, clip_min, clip_max) - ox = x.clone().detach() # save the original x - o_bestattack = x.clone().detach() - - # Map images into the tanh-space - # TODO as of 01/06/2020, PyTorch does not natively support - # arctanh (see, e.g., - # https://github.com/pytorch/pytorch/issues/10324). - # This particular implementation here is not numerically - # stable and should be substituted w/ PyTorch's native - # implementation when it comes out in the future - arctanh = lambda x: 0.5 * torch.log((1 + x) / (1 - x)) - x = (x - clip_min) / (clip_max - clip_min) - x = torch.clamp(x, 0, 1) - x = x * 2 - 1 - x = arctanh(x * .999999) - - # Prepare some variables - modifier = torch.zeros_like(x, requires_grad=True) - y_onehot = torch.nn.functional.one_hot(y, n_classes).to(torch.float) - - # Define loss functions and optimizer - f_fn = lambda real, other, targeted: torch.max( - ((other - real) if targeted else (real - other)) + confidence, - torch.tensor(0.).to(real.device) - ) - l2dist_fn = lambda x, y: torch.pow(x - y, 2).sum(list(range(len(x.size())))[1:]) - optimizer = torch.optim.Adam([modifier], lr=lr) - - # Outer loop performing binary search on const - for outer_step in range(binary_search_steps): - # Initialize some values needed for the inner loop - bestl2 = [INF] * len(x) - bestscore = [-1.] * len(x) - - # Inner loop performing attack iterations - for i in range(max_iterations): - # One attack step - new_x = (torch.tanh(modifier + x) + 1) / 2 - new_x = new_x * (clip_max - clip_min) + clip_min - logits = model_fn(new_x) - - real = torch.sum(y_onehot * logits, 1) - other, _ = torch.max((1 - y_onehot) * logits - y_onehot * 1e4, 1) - - optimizer.zero_grad() - f = f_fn(real, other, targeted) - l2 = l2dist_fn(new_x, ox) - loss = (const * f + l2).sum() - loss.backward() - optimizer.step() - - # Update best results - for n, (l2_n, logits_n, new_x_n) in enumerate(zip(l2, logits, new_x)): - y_n = y[n] - succeeded = compare(logits_n, y_n, is_logits=True) - if l2_n < o_bestl2[n] and succeeded: - pred_n = torch.argmax(logits_n) - o_bestl2[n] = l2_n - o_bestscore[n] = pred_n - o_bestattack[n] = new_x_n - # l2_n < o_bestl2[n] implies l2_n < bestl2[n] so we modify inner loop variables too - bestl2[n] = l2_n - bestscore[n] = pred_n - elif l2_n < bestl2[n] and succeeded: - bestl2[n] = l2_n - bestscore[n] = torch.argmax(logits_n) - - # Binary search step - for n in range(len(x)): - y_n = y[n] - - if compare(bestscore[n], y_n) and bestscore[n] != -1: - # Success, divide const by two - upper_bound[n] = min(upper_bound[n], const[n]) - if upper_bound[n] < 1e9: - const[n] = (lower_bound[n] + upper_bound[n]) / 2 - else: - # Failure, either multiply by 10 if no solution found yet - # or do binary search with the known upper bound - lower_bound[n] = max(lower_bound[n], const[n]) - if upper_bound[n] < 1e9: - const[n] = (lower_bound[n] + upper_bound[n]) / 2 - else: - const[n] *= 10 - - return o_bestattack.detach() - - -if __name__ == '__main__': - x = torch.clamp(torch.randn(5, 10), 0, 1) - y = torch.randint(0, 9, (5,)) - model_fn = lambda x: x - - # targeted - new_x = carlini_wagner_l2(model_fn, x, 10, targeted=True, y=y) - new_pred = model_fn(new_x) - new_pred = torch.argmax(new_pred, 1) - - # untargeted - new_x_untargeted = carlini_wagner_l2(model_fn, x, 10, targeted=False, y=y) - new_pred_untargeted = model_fn(new_x_untargeted) - new_pred_untargeted = torch.argmax(new_pred_untargeted, 1) diff --git a/cleverhans/future/torch/attacks/fast_gradient_method.py b/cleverhans/future/torch/attacks/fast_gradient_method.py deleted file mode 100644 index 610f0bd4e..000000000 --- a/cleverhans/future/torch/attacks/fast_gradient_method.py +++ /dev/null @@ -1,83 +0,0 @@ -"""The Fast Gradient Method attack.""" -import numpy as np -import torch - -from cleverhans.future.torch.utils import optimize_linear - - -def fast_gradient_method(model_fn, x, eps, norm, - clip_min=None, clip_max=None, y=None, targeted=False, sanity_checks=False): - """ - PyTorch implementation of the Fast Gradient Method. - :param model_fn: a callable that takes an input tensor and returns the model logits. - :param x: input tensor. - :param eps: epsilon (input variation parameter); see https://arxiv.org/abs/1412.6572. - :param norm: Order of the norm (mimics NumPy). Possible values: np.inf, 1 or 2. - :param clip_min: (optional) float. Minimum float value for adversarial example components. - :param clip_max: (optional) float. Maximum float value for adversarial example components. - :param y: (optional) Tensor with true labels. If targeted is true, then provide the - target label. Otherwise, only provide this parameter if you'd like to use true - labels when crafting adversarial samples. Otherwise, model predictions are used - as labels to avoid the "label leaking" effect (explained in this paper: - https://arxiv.org/abs/1611.01236). Default is None. - :param targeted: (optional) bool. Is the attack targeted or untargeted? - Untargeted, the default, will try to make the label incorrect. - Targeted will instead try to move in the direction of being more like y. - :param sanity_checks: bool, if True, include asserts (Turn them off to use less runtime / - memory or for unit tests that intentionally pass strange input) - :return: a tensor for the adversarial example - """ - if norm not in [np.inf, 1, 2]: - raise ValueError("Norm order must be either np.inf, 1, or 2, got {} instead.".format(norm)) - if eps < 0: - raise ValueError("eps must be greater than or equal to 0, got {} instead".format(eps)) - if eps == 0: - return x - if clip_min is not None and clip_max is not None: - if clip_min > clip_max: - raise ValueError( - "clip_min must be less than or equal to clip_max, got clip_min={} and clip_max={}".format( - clip_min, clip_max)) - - asserts = [] - - # If a data range was specified, check that the input was in that range - if clip_min is not None: - assert_ge = torch.all(torch.ge(x, torch.tensor(clip_min, device=x.device, dtype=x.dtype))) - asserts.append(assert_ge) - - if clip_max is not None: - assert_le = torch.all(torch.le(x, torch.tensor(clip_max, device=x.device, dtype=x.dtype))) - asserts.append(assert_le) - - # x needs to be a leaf variable, of floating point type and have requires_grad being True for - # its grad to be computed and stored properly in a backward call - x = x.clone().detach().to(torch.float).requires_grad_(True) - if y is None: - # Using model predictions as ground truth to avoid label leaking - _, y = torch.max(model_fn(x), 1) - - # Compute loss - loss_fn = torch.nn.CrossEntropyLoss() - loss = loss_fn(model_fn(x), y) - # If attack is targeted, minimize loss of target label rather than maximize loss of correct label - if targeted: - loss = -loss - - # Define gradient of loss wrt input - loss.backward() - optimal_perturbation = optimize_linear(x.grad, eps, norm) - - # Add perturbation to original example to obtain adversarial example - adv_x = x + optimal_perturbation - - # If clipping is needed, reset all values outside of [clip_min, clip_max] - if (clip_min is not None) or (clip_max is not None): - if clip_min is None or clip_max is None: - raise ValueError( - "One of clip_min and clip_max is None but we don't currently support one-sided clipping") - adv_x = torch.clamp(adv_x, clip_min, clip_max) - - if sanity_checks: - assert np.all(asserts) - return adv_x diff --git a/cleverhans/future/torch/attacks/hop_skip_jump_attack.py b/cleverhans/future/torch/attacks/hop_skip_jump_attack.py deleted file mode 100644 index c3b52dc79..000000000 --- a/cleverhans/future/torch/attacks/hop_skip_jump_attack.py +++ /dev/null @@ -1,352 +0,0 @@ -""" -Boundary Attack++ -""" -import numpy as np -import torch - -from cleverhans.future.torch.utils import get_or_guess_labels - - -def hop_skip_jump_attack(model_fn, x, norm, - y_target=None, image_target=None, initial_num_evals=100, - max_num_evals=10000, stepsize_search='geometric_progression', - num_iterations=64, gamma=1.0, constraint=2, batch_size=128, - verbose=True, clip_min=0, clip_max=1): - """ - PyTorch implementation of HopSkipJumpAttack. - HopSkipJumpAttack was originally proposed by Chen, Jordan and Wainwright. - It is a decision-based attack that requires access to output - labels of a model alone. - Paper link: https://arxiv.org/abs/1904.02144 - At a high level, this attack is an iterative attack composed of three - steps: Binary search to approach the boundary; gradient estimation; - stepsize search. HopSkipJumpAttack requires fewer model queries than - Boundary Attack which was based on rejective sampling. - - :param model_fn: a callable that takes an input tensor and returns the model logits. - :param x: input tensor with n samples. - :param norm: The distance to optimize. Possible values: 2 or np.inf. - :param y_target: A tensor of shape (n, nb_classes) for target labels. - Required for targeted attack. - :param image_target: A tensor of shape (n, **image shape) for initial - target images. Required for targeted attack. - :param initial_num_evals: initial number of evaluations for - gradient estimation. - :param max_num_evals: maximum number of evaluations for gradient estimation. - :param stepsize_search: How to search for stepsize; choices are - 'geometric_progression', 'grid_search'. - 'geometric progression' initializes the stepsize - by ||x_t - x||_p / sqrt(iteration), and keep - decreasing by half until reaching the target - side of the boundary. 'grid_search' chooses the - optimal epsilon over a grid, in the scale of - ||x_t - x||_p. - :param num_iterations: The number of iterations. - :param gamma: The binary search threshold theta is gamma / d^{3/2} for - l2 attack and gamma / d^2 for linf attack. - :param batch_size: batch_size for model prediction. - :param verbose: (boolean) Whether distance at each step is printed. - :param clip_min: (optional float) Minimum input component value - :param clip_max: (optional float) Maximum input component value - """ - shape = (1, ) + x.shape[1:] - if y_target is not None: - assert image_target is not None, \ - 'Require a target image for targeted attack.' - if clip_min is not None and clip_max is not None: - if clip_min > clip_max: - raise ValueError( - "clip_min must be less than or equal to clip_max, got clip_min={} and clip_max={}".format( - clip_min, clip_max)) - - d = int(np.prod(shape)) - - if constraint == 2: - theta = gamma / (np.sqrt(d) * d) - else: - theta = gamma / (d * d) - - def hsja(sample, target_label, target_image): - if target_label is None: - _, original_label = torch.max(model_fn(sample), 1) - - def decision_function(images): - """ - Decision function output 1 on the desired side of the boundary, - 0 otherwise. - """ - images = torch.clamp(images, clip_min, clip_max) - prob = [] - for i in range(0, len(images), batch_size): - batch = images[i:i+batch_size] - prob_i = model_fn(batch) - prob.append(prob_i) - prob = torch.cat(prob, dim=0) - if target_label is None: - return torch.max(prob, dim=1)[1] != original_label - else: - return torch.max(prob, dim=1)[1] == target_label - - # Initialize. - if target_image is None: - perturbed = initialize(decision_function, sample, shape, - clip_min, clip_max) - else: - perturbed = target_image.to(sample.device) - - # Project the initialization to the boundary. - perturbed, dist_post_update = binary_search_batch(sample, - perturbed, - decision_function, - shape, constraint, - theta) - dist = compute_distance(perturbed, sample, constraint) - - for j in np.arange(num_iterations): - current_iteration = j + 1 - - # Choose delta. - delta = select_delta(dist_post_update, current_iteration, - clip_max, clip_min, d, - theta, constraint) - - # Choose number of evaluations. - num_evals = int(min([initial_num_evals * np.sqrt(j+1), - max_num_evals])) - - # approximate gradient. - gradf = approximate_gradient(decision_function, perturbed, num_evals, - delta, constraint, shape[1:], - clip_min, clip_max) - if constraint == np.inf: - update = torch.sign(gradf) - else: - update = gradf - - # search step size. - if stepsize_search == 'geometric_progression': - # find step size. - epsilon = geometric_progression_for_stepsize(perturbed, - update, dist, decision_function, current_iteration) - - # Update the sample. - perturbed = torch.clamp(perturbed + epsilon * update, - clip_min, clip_max) - - # Binary search to return to the boundary. - perturbed, dist_post_update = binary_search_batch(sample, - perturbed, - decision_function, - shape, constraint, - theta) - - elif stepsize_search == 'grid_search': - # Grid search for stepsize. - epsilons = torch.from_numpy(np.logspace(-4, 0, num=20, endpoint=True)).to(perturbed.device).float() * dist - perturbeds = perturbed + epsilons.view((20,) + (1,) * (len(shape) - 1)) * update - perturbeds = torch.clamp(perturbeds, clip_min, clip_max) - idx_perturbed = decision_function(perturbeds) - - if torch.sum(idx_perturbed) > 0: - # Select the perturbation that yields the minimum distance # after binary search. - perturbed, dist_post_update = binary_search_batch(sample, - perturbeds[idx_perturbed], - decision_function, - shape, constraint, - theta) - - # compute new distance. - dist = compute_distance(perturbed, sample, constraint) - if verbose: - print('iteration: {:d}, {:s} distance {:.4E}'.format( - j+1, str(constraint), dist)) - - return perturbed - - # Perform attack on one instance at a time - adv_x = [] - for i, x_ in enumerate(x): - x_ = x_.unsqueeze(0) - if y_target is not None: - # targeted attack that requires target label and image. - pert = hsja(x_, y_target[i], image_target[i]) - else: - if image_target is not None: - pert = hsja(x_, None, image_target[i]) - else: - # untargeted attack without an initialized image. - pert = hsja(x_, None, None) - adv_x.append(pert) - return torch.cat(adv_x, 0) - -def compute_distance(x_ori, x_pert, constraint=2): - """ Compute the distance between two images. """ - if constraint == 2: - dist = torch.norm(x_ori - x_pert, p=2) - elif constraint == np.inf: - dist = torch.max(torch.abs(x_ori - x_pert)) - return dist - -def approximate_gradient(decision_function, sample, num_evals, - delta, constraint, shape, clip_min, clip_max): - """ Gradient direction estimation """ - # Generate random vectors. - noise_shape = [num_evals] + list(shape) - if constraint == 2: - rv = torch.randn(noise_shape) - elif constraint == np.inf: - rv = -1 + torch.rand(noise_shape) * 2 - - axis = tuple(range(1, 1 + len(shape))) - rv = rv / torch.sqrt(torch.sum(rv ** 2, dim=axis, keepdim=True)) - perturbed = sample + delta * rv.to(sample.device) - perturbed = torch.clamp(perturbed, clip_min, clip_max) - rv = (perturbed - sample) / delta - - # query the model. - decisions = decision_function(perturbed).float() - fval = 2.0 * decisions.view((decisions.shape[0],) + (1,) * len(shape)) - 1.0 - - # Baseline subtraction (when fval differs) - fval_mean = torch.mean(fval) - if fval_mean == 1.0: # label changes. - gradf = torch.mean(rv, dim=0) - elif fval_mean == -1.0: # label not change. - gradf = - torch.mean(rv, dim=0) - else: - fval = fval - fval_mean - gradf = torch.mean(fval * rv, dim=0) - - # Get the gradient direction. - gradf = gradf / torch.norm(gradf, p=2) - - return gradf - -def project(original_image, perturbed_images, alphas, shape, constraint): - """ Projection onto given l2 / linf balls in a batch. """ - alphas = alphas.view((alphas.shape[0],) + (1,) * (len(shape) - 1)) - if constraint == 2: - projected = (1-alphas) * original_image + alphas * perturbed_images - elif constraint == np.inf: - projected = torch.clamp(perturbed_images, - original_image - alphas, - original_image + alphas) - return projected - -def binary_search_batch(original_image, perturbed_images, decision_function, - shape, constraint, theta): - """ Binary search to approach the boundary. """ - - # Compute distance between each of perturbed image and original image. - dists_post_update = torch.stack([ - compute_distance( - original_image, - perturbed_image, - constraint - ) - for perturbed_image in perturbed_images]) - - # Choose upper thresholds in binary searchs based on constraint. - if constraint == np.inf: - highs = dists_post_update - # Stopping criteria. - thresholds = torch.min(dists_post_update * theta, theta) - else: - highs = torch.ones(len(perturbed_images)).to(original_image.device) - thresholds = theta - - lows = torch.zeros(len(perturbed_images)).to(original_image.device) - - while torch.max((highs - lows) / thresholds) > 1: - # projection to mids. - mids = (highs + lows) / 2.0 - mid_images = project(original_image, perturbed_images, - mids, shape, constraint) - - # Update highs and lows based on model decisions. - decisions = decision_function(mid_images) - lows = torch.where(decisions == 0, mids, lows) - highs = torch.where(decisions == 1, mids, highs) - - out_images = project(original_image, perturbed_images, - highs, shape, constraint) - - # Compute distance of the output image to select the best choice. - # (only used when stepsize_search is grid_search.) - dists = torch.stack([ - compute_distance( - original_image, - out_image, - constraint - ) - for out_image in out_images]) - _, idx = torch.min(dists, 0) - - dist = dists_post_update[idx] - out_image = out_images[idx].unsqueeze(0) - return out_image, dist - -def initialize(decision_function, sample, shape, clip_min, clip_max): - """ - Efficient Implementation of BlendedUniformNoiseAttack in Foolbox. - """ - success = 0 - num_evals = 0 - - # Find a misclassified random noise. - while True: - random_noise = clip_min + torch.rand(shape).to(sample.device) * (clip_max - clip_min) - success = decision_function(random_noise)[0] - if success: - break - num_evals += 1 - message = "Initialization failed! Try to use a misclassified image as `target_image`" - assert num_evals < 1e4, message - - # Binary search to minimize l2 distance to original image. - low = 0.0 - high = 1.0 - while high - low > 0.001: - mid = (high + low) / 2.0 - blended = (1 - mid) * sample + mid * random_noise - success = decision_function(blended)[0] - if success: - high = mid - else: - low = mid - - initialization = (1 - high) * sample + high * random_noise - return initialization - -def geometric_progression_for_stepsize(x, update, dist, decision_function, - current_iteration): - """ Geometric progression to search for stepsize. - Keep decreasing stepsize by half until reaching - the desired side of the boundary. - """ - epsilon = dist / np.sqrt(current_iteration) - while True: - updated = x + epsilon * update - success = decision_function(updated)[0] - if success: - break - else: - epsilon = epsilon / 2.0 - - return epsilon - -def select_delta(dist_post_update, current_iteration, - clip_max, clip_min, d, theta, constraint): - """ - Choose the delta at the scale of distance - between x and perturbed sample. - """ - if current_iteration == 1: - delta = 0.1 * (clip_max - clip_min) - else: - if constraint == 2: - delta = np.sqrt(d) * theta * dist_post_update - elif constraint == np.inf: - delta = d * theta * dist_post_update - - return delta diff --git a/cleverhans/future/torch/attacks/projected_gradient_descent.py b/cleverhans/future/torch/attacks/projected_gradient_descent.py deleted file mode 100644 index 9c39c1aed..000000000 --- a/cleverhans/future/torch/attacks/projected_gradient_descent.py +++ /dev/null @@ -1,121 +0,0 @@ -"""The Projected Gradient Descent attack.""" -import numpy as np -import torch - -from cleverhans.future.torch.attacks.fast_gradient_method import fast_gradient_method -from cleverhans.future.torch.utils import clip_eta - - -def projected_gradient_descent(model_fn, x, eps, eps_iter, nb_iter, norm, - clip_min=None, clip_max=None, y=None, targeted=False, - rand_init=True, rand_minmax=None, sanity_checks=True): - """ - This class implements either the Basic Iterative Method - (Kurakin et al. 2016) when rand_init is set to False. or the - Madry et al. (2017) method if rand_init is set to True. - Paper link (Kurakin et al. 2016): https://arxiv.org/pdf/1607.02533.pdf - Paper link (Madry et al. 2017): https://arxiv.org/pdf/1706.06083.pdf - :param model_fn: a callable that takes an input tensor and returns the model logits. - :param x: input tensor. - :param eps: epsilon (input variation parameter); see https://arxiv.org/abs/1412.6572. - :param eps_iter: step size for each attack iteration - :param nb_iter: Number of attack iterations. - :param norm: Order of the norm (mimics NumPy). Possible values: np.inf, 1 or 2. - :param clip_min: (optional) float. Minimum float value for adversarial example components. - :param clip_max: (optional) float. Maximum float value for adversarial example components. - :param y: (optional) Tensor with true labels. If targeted is true, then provide the - target label. Otherwise, only provide this parameter if you'd like to use true - labels when crafting adversarial samples. Otherwise, model predictions are used - as labels to avoid the "label leaking" effect (explained in this paper: - https://arxiv.org/abs/1611.01236). Default is None. - :param targeted: (optional) bool. Is the attack targeted or untargeted? - Untargeted, the default, will try to make the label incorrect. - Targeted will instead try to move in the direction of being more like y. - :param rand_init: (optional) bool. Whether to start the attack from a randomly perturbed x. - :param rand_minmax: (optional) bool. Support of the continuous uniform distribution from - which the random perturbation on x was drawn. Effective only when rand_init is - True. Default equals to eps. - :param sanity_checks: bool, if True, include asserts (Turn them off to use less runtime / - memory or for unit tests that intentionally pass strange input) - :return: a tensor for the adversarial example - """ - if norm == 1: - raise NotImplementedError("It's not clear that FGM is a good inner loop" - " step for PGD when norm=1, because norm=1 FGM " - " changes only one pixel at a time. We need " - " to rigorously test a strong norm=1 PGD " - "before enabling this feature.") - if norm not in [np.inf, 2]: - raise ValueError("Norm order must be either np.inf or 2.") - if eps < 0: - raise ValueError( - "eps must be greater than or equal to 0, got {} instead".format(eps)) - if eps == 0: - return x - if eps_iter < 0: - raise ValueError( - "eps_iter must be greater than or equal to 0, got {} instead".format(eps_iter)) - if eps_iter == 0: - return x - - assert eps_iter <= eps, (eps_iter, eps) - if clip_min is not None and clip_max is not None: - if clip_min > clip_max: - raise ValueError( - "clip_min must be less than or equal to clip_max, got clip_min={} and clip_max={}".format( - clip_min, clip_max)) - - asserts = [] - - # If a data range was specified, check that the input was in that range - if clip_min is not None: - assert_ge = torch.all(torch.ge(x, torch.tensor(clip_min, device=x.device, dtype=x.dtype))) - asserts.append(assert_ge) - - if clip_max is not None: - assert_le = torch.all(torch.le(x, torch.tensor(clip_max, device=x.device, dtype=x.dtype))) - asserts.append(assert_le) - - # Initialize loop variables - if rand_init: - if rand_minmax is None: - rand_minmax = eps - eta = torch.zeros_like(x).uniform_(-rand_minmax, rand_minmax) - else: - eta = torch.zeros_like(x) - - # Clip eta - eta = clip_eta(eta, norm, eps) - adv_x = x + eta - if clip_min is not None or clip_max is not None: - adv_x = torch.clamp(adv_x, clip_min, clip_max) - - if y is None: - # Using model predictions as ground truth to avoid label leaking - _, y = torch.max(model_fn(x), 1) - - i = 0 - while i < nb_iter: - adv_x = fast_gradient_method(model_fn, adv_x, eps_iter, norm, - clip_min=clip_min, clip_max=clip_max, y=y, targeted=targeted) - - # Clipping perturbation eta to norm norm ball - eta = adv_x - x - eta = clip_eta(eta, norm, eps) - adv_x = x + eta - - # Redo the clipping. - # FGM already did it, but subtracting and re-adding eta can add some - # small numerical error. - if clip_min is not None or clip_max is not None: - adv_x = torch.clamp(adv_x, clip_min, clip_max) - i += 1 - - asserts.append(eps_iter <= eps) - if norm == np.inf and clip_min is not None: - # TODO necessary to cast clip_min and clip_max to x.dtype? - asserts.append(eps + clip_min <= clip_max) - - if sanity_checks: - assert np.all(asserts) - return adv_x diff --git a/cleverhans/future/torch/attacks/semantic.py b/cleverhans/future/torch/attacks/semantic.py deleted file mode 100644 index 7567a2dc7..000000000 --- a/cleverhans/future/torch/attacks/semantic.py +++ /dev/null @@ -1,24 +0,0 @@ -""" -Semantic adversarial Examples -""" - -def semantic(x, center=True, max_val=1.): - """ - Semantic adversarial examples. - - https://arxiv.org/abs/1703.06857 - - Note: data must either be centered (so that the negative image can be - made by simple negation) or must be in the interval of [-1, 1] - - Arguments - --------- - center : bool - If true, assumes data has 0 mean so the negative image is just negation. - If false, assumes data is in interval [0, max_val] - max_val : float - Maximum value allowed in the input data. - """ - - if self.center: return x*-1 - return self.max_val - x diff --git a/cleverhans/future/torch/attacks/spsa.py b/cleverhans/future/torch/attacks/spsa.py deleted file mode 100644 index 16fc108da..000000000 --- a/cleverhans/future/torch/attacks/spsa.py +++ /dev/null @@ -1,194 +0,0 @@ -"""The SPSA attack.""" -import numpy as np -import torch -from torch import optim -from cleverhans.future.torch.utils import clip_eta - - -def spsa(model_fn, x, eps, nb_iter, norm=np.inf, clip_min=-np.inf, clip_max=np.inf, y=None, - targeted=False, early_stop_loss_threshold=None, learning_rate=0.01, delta=0.01, - spsa_samples=128, spsa_iters=1, is_debug=False, sanity_checks=True): - """ - This implements the SPSA adversary, as in https://arxiv.org/abs/1802.05666 - (Uesato et al. 2018). SPSA is a gradient-free optimization method, which is useful when - the model is non-differentiable, or more generally, the gradients do not point in useful - directions. - - :param model_fn: A callable that takes an input tensor and returns the model logits. - :param x: Input tensor. - :param eps: The size of the maximum perturbation, measured in the L-infinity norm. - :param nb_iter: The number of optimization steps. - :param norm: Order of the norm (mimics NumPy). Possible values: np.inf, 1 or 2. - :param clip_min: If specified, the minimum input value. - :param clip_max: If specified, the maximum input value. - :param y: (optional) Tensor with true labels. If targeted is true, then provide the - target label. Otherwise, only provide this parameter if you'd like to use true - labels when crafting adversarial samples. Otherwise, model predictions are used - as labels to avoid the "label leaking" effect (explained in this paper: - https://arxiv.org/abs/1611.01236). Default is None. - :param targeted: (optional) bool. Is the attack targeted or untargeted? Untargeted, the - default, will try to make the label incorrect. Targeted will instead try to - move in the direction of being more like y. - :param early_stop_loss_threshold: A float or None. If specified, the attack will end as - soon as the loss is below `early_stop_loss_threshold`. - :param learning_rate: Learning rate of ADAM optimizer. - :param delta: Perturbation size used for SPSA approximation. - :param spsa_samples: Number of inputs to evaluate at a single time. The true batch size - (the number of evaluated inputs for each update) is `spsa_samples * - spsa_iters` - :param spsa_iters: Number of model evaluations before performing an update, where each - evaluation is on `spsa_samples` different inputs. - :param is_debug: If True, print the adversarial loss after each update. - :param sanity_checks: bool, if True, include asserts (Turn them off to use less runtime / - memory or for unit tests that intentionally pass strange input) - :return: a tensor for the adversarial example - """ - - if y is not None and len(x) != len(y): - raise ValueError('number of inputs {} is different from number of labels {}' - .format(len(x), len(y))) - if y is None: - y = torch.argmax(model_fn(x), dim=1) - - # The rest of the function doesn't support batches of size greater than 1, - # so if the batch is bigger we split it up. - if len(x) != 1: - adv_x = [] - for x_single, y_single in zip(x, y): - adv_x_single = spsa(model_fn=model_fn, x=x_single.unsqueeze(0), eps=eps, - nb_iter=nb_iter, norm=norm, clip_min=clip_min, clip_max=clip_max, - y=y_single.unsqueeze(0), targeted=targeted, - early_stop_loss_threshold=early_stop_loss_threshold, - learning_rate=learning_rate, delta=delta, - spsa_samples=spsa_samples, spsa_iters=spsa_iters, - is_debug=is_debug, sanity_checks=sanity_checks) - adv_x.append(adv_x_single) - return torch.cat(adv_x) - - if eps < 0: - raise ValueError( - "eps must be greater than or equal to 0, got {} instead".format(eps)) - if eps == 0: - return x - - if clip_min is not None and clip_max is not None: - if clip_min > clip_max: - raise ValueError( - "clip_min must be less than or equal to clip_max, got clip_min={} and clip_max={}" - .format(clip_min, clip_max)) - - asserts = [] - - # If a data range was specified, check that the input was in that range - asserts.append(torch.all(x >= clip_min)) - asserts.append(torch.all(x <= clip_max)) - - if is_debug: - print("Starting SPSA attack with eps = {}".format(eps)) - - perturbation = (torch.rand_like(x) * 2 - 1) * eps - _project_perturbation(perturbation, norm, eps, x, clip_min, clip_max) - optimizer = optim.Adam([perturbation], lr=learning_rate) - - for i in range(nb_iter): - def loss_fn(pert): - """ - Margin logit loss, with correct sign for targeted vs untargeted loss. - """ - logits = model_fn(x + pert) - loss_multiplier = 1 if targeted else -1 - return loss_multiplier * _margin_logit_loss(logits, y.expand(len(pert))) - - spsa_grad = _compute_spsa_gradient(loss_fn, x, delta=delta, - samples=spsa_samples, iters=spsa_iters) - perturbation.grad = spsa_grad - optimizer.step() - - _project_perturbation(perturbation, norm, eps, x, clip_min, clip_max) - - loss = loss_fn(perturbation).item() - if is_debug: - print('Iteration {}: loss = {}'.format(i, loss)) - if early_stop_loss_threshold is not None and loss < early_stop_loss_threshold: - break - - adv_x = torch.clamp((x + perturbation).detach(), clip_min, clip_max) - - if norm == np.inf: - asserts.append(torch.all(torch.abs(adv_x - x) <= eps + 1e-6)) - else: - asserts.append(torch.all(torch.abs( - torch.renorm(adv_x - x, p=norm, dim=0, maxnorm=eps) - (adv_x - x)) < 1e-6)) - asserts.append(torch.all(adv_x >= clip_min)) - asserts.append(torch.all(adv_x <= clip_max)) - - if sanity_checks: - assert np.all(asserts) - - return adv_x - - -def _project_perturbation(perturbation, norm, epsilon, input_image, clip_min=-np.inf, - clip_max=np.inf): - """ - Project `perturbation` onto L-infinity ball of radius `epsilon`. Also project into - hypercube such that the resulting adversarial example is between clip_min and clip_max, - if applicable. This is an in-place operation. - """ - - clipped_perturbation = clip_eta(perturbation, norm, epsilon) - new_image = torch.clamp(input_image + clipped_perturbation, - clip_min, clip_max) - - perturbation.add_((new_image - input_image) - perturbation) - - -def _compute_spsa_gradient(loss_fn, x, delta, samples, iters): - """ - Approximately compute the gradient of `loss_fn` at `x` using SPSA with the - given parameters. The gradient is approximated by evaluating `iters` batches - of `samples` size each. - """ - - assert len(x) == 1 - num_dims = len(x.size()) - - x_batch = x.expand(samples, *([-1] * (num_dims - 1))) - - grad_list = [] - for i in range(iters): - delta_x = delta * torch.sign(torch.rand_like(x_batch) - 0.5) - delta_x = torch.cat([delta_x, -delta_x]) - with torch.no_grad(): - loss_vals = loss_fn(x + delta_x) - while len(loss_vals.size()) < num_dims: - loss_vals = loss_vals.unsqueeze(-1) - avg_grad = torch.mean(loss_vals * torch.sign(delta_x), dim=0, keepdim=True) / delta - grad_list.append(avg_grad) - - return torch.mean(torch.cat(grad_list), dim=0, keepdim=True) - - -def _margin_logit_loss(logits, labels): - """ - Computes difference between logits for `labels` and next highest logits. - - The loss is high when `label` is unlikely (targeted by default). - """ - - correct_logits = logits.gather(1, labels[:, None]).squeeze(1) - - logit_indices = torch.arange( - logits.size()[1], - dtype=labels.dtype, - device=labels.device, - )[None, :].expand(labels.size()[0], -1) - incorrect_logits = torch.where( - logit_indices == labels[:, None], - torch.full_like(logits, float('-inf')), - logits, - ) - max_incorrect_logits, _ = torch.max( - incorrect_logits, 1) - - return max_incorrect_logits - correct_logits diff --git a/cleverhans/future/torch/tests/test_attacks.py b/cleverhans/future/torch/tests/test_attacks.py deleted file mode 100644 index bb6bcc4ec..000000000 --- a/cleverhans/future/torch/tests/test_attacks.py +++ /dev/null @@ -1,828 +0,0 @@ -# pylint: disable=missing-docstring -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - -import numpy as np -from nose.plugins.skip import SkipTest -import torch - -from cleverhans.devtools.checks import CleverHansTest -from cleverhans.future.torch.attacks.fast_gradient_method import fast_gradient_method -from cleverhans.future.torch.attacks.projected_gradient_descent import projected_gradient_descent -from cleverhans.future.torch.attacks.carlini_wagner_l2 import carlini_wagner_l2 -from cleverhans.future.torch.attacks.spsa import spsa -from cleverhans.future.torch.attacks.hop_skip_jump_attack import hop_skip_jump_attack -from cleverhans.future.torch.attacks.sparse_l1_descent import sparse_l1_descent - - - -class TrivialModel(torch.nn.Module): - - def __init__(self): - super(TrivialModel, self).__init__() - self.w1 = torch.tensor([[1., -1]]) - - def forward(self, x, **kwargs): - return torch.matmul(x, self.w1) - - -class SimpleModel(torch.nn.Module): - - def __init__(self): - super(SimpleModel, self).__init__() - self.w1 = torch.tensor([[1.5, .3], [-2, .3]]) - self.w2 = torch.tensor([[-2.4, 1.2], [.5, -2.3]]) - - def forward(self, x): - x = torch.matmul(x, self.w1) - x = torch.sigmoid(x) - x = torch.matmul(x, self.w2) - return x - - -class DummyModel(torch.nn.Module): - - def __init__(self, n_features): - super(DummyModel, self).__init__() - self.model = torch.nn.Sequential( - torch.nn.Linear(n_features, 60), - torch.nn.ReLU(), - torch.nn.Linear(60, 10), - ) - - def forward(self, x): - x = x.view(x.shape[0], -1) - return self.model(x) - - -class CommonAttackProperties(CleverHansTest): - - def setUp(self): - super(CommonAttackProperties, self).setUp() - self.model = SimpleModel() - self.trivial_model = TrivialModel() - self.x = torch.randn(100, 2) - self.normalized_x = torch.rand(100, 2) # truncated between [0, 1) - self.trivial_x = torch.randn(100, 1) - self.trivial_normalized_x = torch.rand(100, 1) # truncated between [0, 1) - self.y_target = torch.randint(low=0, high=2, size=(100,)) - self.ord_list = [1, 2, np.inf] - - def help_adv_examples_success_rate(self, model, x, rate=.5, **kwargs): - x_adv = self.attack(model_fn=model, x=x, **kwargs) - _, ori_label = model(x).max(1) - _, adv_label = model(x_adv).max(1) - adv_acc = ( - adv_label.eq(ori_label).sum().to(torch.float) - / x.size(0)) - self.assertLess(adv_acc, rate) - - def help_targeted_adv_examples_success_rate(self, model, x, rate=.7, **kwargs): - x_adv = self.attack( - model_fn=model, x=x, - y=self.y_target, targeted=True, **kwargs) - - _, adv_label = model(x_adv).max(1) - adv_success = ( - adv_label.eq(self.y_target).sum().to(torch.float) - / x.size(0)) - self.assertGreater(adv_success, rate) - - - -class TestFastGradientMethod(CommonAttackProperties): - - def setUp(self): - super(TestFastGradientMethod, self).setUp() - self.attack = fast_gradient_method - self.eps_list = [0, .1, .3, 1., 3] - self.attack_param = { - 'eps': .5, - 'clip_min': -5, - 'clip_max': 5 - } - - def test_invalid_input(self): - x = torch.tensor([[-2., 3.]]) - for norm in self.ord_list: - self.assertRaises( - AssertionError, self.attack, model_fn=self.model, x=x, eps=.1, - norm=norm, clip_min=-1., clip_max=1., sanity_checks=True - ) - - def test_invalid_eps(self): - for norm in self.ord_list: - self.assertRaises( - ValueError, self.attack, model_fn=self.model, - x=self.x, eps=-.1, norm=norm) - - def test_eps_equals_zero(self): - for norm in self.ord_list: - self.assertClose( - self.attack(model_fn=self.model, x=self.x, eps=0, norm=norm), - self.x) - - def test_eps(self): - # test if the attack respects the norm constraint - # NOTE this has been tested with the optimize_linear function in - # test_utils, so duplicate tests are not needed here. - # Although, if ever switch the engine of the FGM method to some - # function other than optimize_linear. This test should be added. - raise SkipTest() - - def test_clips(self): - clip_min = -1. - clip_max = 1. - for norm in self.ord_list: - x_adv = self.attack( - model_fn=self.model, x=self.normalized_x, eps=.3, norm=norm, - clip_min=clip_min, clip_max=clip_max - ) - self.assertTrue(torch.all(x_adv <= clip_max)) - self.assertTrue(torch.all(x_adv >= clip_min)) - - def test_invalid_clips(self): - clip_min = .5 - clip_max = -.5 - for norm in self.ord_list: - self.assertRaises( - ValueError, self.attack, model_fn=self.model, x=self.x, eps=.1, - norm=norm, clip_min=clip_min, clip_max=clip_max - ) - - def test_adv_example_success_rate_linf(self): - # use normalized_x to make sure the same eps gives uniformly high attack - # success rate across randomized tests - self.help_adv_examples_success_rate(x=self.normalized_x, - model=self.model, norm=np.inf, **self.attack_param) - - def test_targeted_adv_example_success_rate_linf(self): - self.help_targeted_adv_examples_success_rate(x=self.normalized_x, - model=self.model, norm=np.inf, **self.attack_param) - - def test_adv_example_success_rate_l1(self): - self.help_adv_examples_success_rate(x=self.normalized_x, - model=self.model, norm=1, **self.attack_param) - - def test_targeted_adv_example_success_rate_l1(self): - self.help_targeted_adv_examples_success_rate(x=self.normalized_x, - model=self.model, norm=1, **self.attack_param) - - def test_adv_example_success_rate_l2(self): - self.help_adv_examples_success_rate(x=self.normalized_x, - model=self.model, norm=2, **self.attack_param) - - def test_targeted_adv_example_success_rate_l2(self): - self.help_targeted_adv_examples_success_rate(x=self.normalized_x, - model=self.model, norm=2, **self.attack_param) - - - -class TestProjectedGradientMethod(CommonAttackProperties): - - def setUp(self): - super(TestProjectedGradientMethod, self).setUp() - self.attack = projected_gradient_descent - self.attack_param = { - 'eps': .5, - 'clip_min': -5, - 'clip_max': 5, - 'eps_iter': .05, - 'nb_iter': 20, - } - - def test_invalid_input(self): - x = torch.tensor([[-2., 3.]]) - for norm in self.ord_list: - if norm == 1: - self.assertRaises( - NotImplementedError, self.attack, model_fn=self.model, x=x, eps=.1, - nb_iter=1, eps_iter=.05, norm=norm, clip_min=-1., clip_max=1., - sanity_checks=True) - else: - self.assertRaises( - AssertionError, self.attack, model_fn=self.model, x=x, eps=.1, - nb_iter=1, eps_iter=.05, norm=norm, clip_min=-1., clip_max=1., - sanity_checks=True) - - def test_invalid_eps(self): - for norm in self.ord_list: - if norm == 1: - self.assertRaises( - NotImplementedError, self.attack, model_fn=self.model, - x=self.x, eps=-.1, norm=norm, nb_iter=1, eps_iter=.01) - else: - self.assertRaises( - ValueError, self.attack, model_fn=self.model, - x=self.x, eps=-.1, norm=norm, nb_iter=1, eps_iter=.01) - - def test_invalid_eps_iter(self): - for norm in self.ord_list: - if norm == 1: - self.assertRaises( - NotImplementedError, self.attack, model_fn=self.model, - x=self.x, eps=.1, norm=norm, nb_iter=1, eps_iter=-.01) - else: - self.assertRaises( - ValueError, self.attack, model_fn=self.model, - x=self.x, eps=.1, norm=norm, nb_iter=1, eps_iter=-.01) - - def test_eps_equals_zero(self): - for norm in self.ord_list: - if norm == 1: - self.assertRaises( - NotImplementedError, self.attack, model_fn=self.model, - x=self.x, eps=0, norm=norm, nb_iter=10, eps_iter=.01) - else: - self.assertClose( - self.attack( - model_fn=self.model, x=self.x, eps=0, norm=norm, nb_iter=10, - eps_iter=.01), - self.x) - - def test_eps_iter_equals_zero(self): - for norm in self.ord_list: - if norm == 1: - self.assertRaises( - NotImplementedError, self.attack, model_fn=self.model, x=self.x, - eps=.5, norm=norm, nb_iter=10, eps_iter=0) - else: - self.assertClose( - self.attack( - model_fn=self.model, x=self.x, eps=.5, norm=norm, nb_iter=10, - eps_iter=0), - self.x) - - def test_invalid_clips(self): - clip_min = .5 - clip_max = -.5 - for norm in self.ord_list: - if norm == 1: - self.assertRaises( - NotImplementedError, self.attack, model_fn=self.model, x=self.x, eps=.1, - norm=norm, clip_min=clip_min, clip_max=clip_max, nb_iter=10, - eps_iter=.01) - else: - self.assertRaises( - ValueError, self.attack, model_fn=self.model, x=self.x, eps=.1, - norm=norm, clip_min=clip_min, clip_max=clip_max, nb_iter=10, - eps_iter=.01) - - def test_adv_example_success_rate_linf(self): - # use normalized_x to make sure the same eps gives uniformly high attack - # success rate across randomized tests - self.help_adv_examples_success_rate(x=self.normalized_x, - model=self.model, norm=np.inf, **self.attack_param) - - def test_targeted_adv_example_success_rate_linf(self): - self.help_targeted_adv_examples_success_rate(x=self.normalized_x, - model=self.model, norm=np.inf, **self.attack_param) - - def test_adv_example_success_rate_l1(self): - self.assertRaises( - NotImplementedError, self.help_adv_examples_success_rate, model=self.model, - x=self.normalized_x, norm=1, **self.attack_param) - # TODO uncomment the actual test below after we have implemented the L1 attack - # self.help_adv_examples_success_rate(x=self.normalized_x, - # model=self.model, norm=1, **self.attack_param) - - def test_targeted_adv_example_success_rate_l1(self): - self.assertRaises( - NotImplementedError, self.help_targeted_adv_examples_success_rate, - x=self.normalized_x, model=self.model, norm=1, **self.attack_param) - # TODO uncomment the actual test below after we have implemented the L1 attack - # self.help_targeted_adv_examples_success_rate(x=self.normalized_x, - # model=self.model, norm=1, **self.attack_param) - - def test_adv_example_success_rate_l2(self): - self.help_adv_examples_success_rate(model=self.model, - x=self.normalized_x, norm=2, **self.attack_param) - - def test_targeted_adv_example_success_rate_l2(self): - self.help_targeted_adv_examples_success_rate(model=self.model, - x=self.normalized_x, norm=2, **self.attack_param) - - def test_do_not_reach_lp_boundary(self): - for norm in self.ord_list: - if norm == 1: - self.assertRaises( - NotImplementedError, self.attack, model_fn=self.model, - x=self.normalized_x, eps=.5, nb_iter=10, norm=norm, eps_iter=.01) - continue - else: - x_adv = self.attack( - model_fn=self.model, x=self.normalized_x, eps=.5, nb_iter=10, - norm=norm, eps_iter=.01) - - if norm == np.inf: - delta, _ = torch.abs(x_adv - self.normalized_x).max(dim=1) - elif norm == 1: - delta = torch.abs(x_adv - self.normalized_x).sum(dim=1) - elif norm == 2: - delta = torch.pow(x_adv - self.normalized_x, 2).sum(dim=1).pow(.5) - diff = torch.max(.5 - delta) - self.assertTrue(diff > .25) - - def test_attack_strength(self): - x_adv = self.attack( - model_fn=self.model, x=self.normalized_x, eps=1., - eps_iter=.05, norm=np.inf, clip_min=.5, clip_max=.7, nb_iter=5, - sanity_checks=False) - _, ori_label = self.model(self.normalized_x).max(1) - _, adv_label = self.model(x_adv).max(1) - adv_acc = ( - adv_label.eq(ori_label).sum().to(torch.float) - / self.normalized_x.size(0)) - self.assertLess(adv_acc, .1) - - def test_eps(self): - # test if the attack respects the norm constraint - # NOTE clip_eta makes sure that at each step, adv_x respects the eps - # norm constraint. Therefore, this is essentially a test on clip_eta, - # which is implemented in a separate test_clip_eta - raise SkipTest() - - def test_clip_eta(self): - # NOTE: this has been tested with test_clip_eta in test_utils - raise SkipTest() - - def test_clips(self): - clip_min = -1. - clip_max = 1. - for norm in self.ord_list: - if norm == 1: - self.assertRaises( - NotImplementedError, model_fn=self.model, x=self.normalized_x, - eps=.3, eps_iter=.03, norm=norm, nb_iter=10, clip_min=clip_min, - clip_max=clip_max) - continue - else: - x_adv = self.attack( - model_fn=self.model, x=self.normalized_x, eps=.3, eps_iter=.03, - norm=norm, nb_iter=10, clip_min=clip_min, clip_max=clip_max) - self.assertTrue(torch.all(x_adv <= clip_max)) - self.assertTrue(torch.all(x_adv >= clip_min)) - - def test_attack_does_not_cache_graph_computation_for_nb_iter(self): - # TODO not sure what the original test does in tests_tf/test_attacks - pass - - def test_multiple_initial_random_step(self): - _, ori_label = self.model(self.normalized_x).max(1) - new_label_multi = ori_label.clone().detach() - - for _ in range(10): - x_adv = self.attack( - model_fn=self.model, x=self.normalized_x, eps=.5, eps_iter=.05, - norm=np.inf, clip_min=.5, clip_max=.7, nb_iter=2, sanity_checks=False) - _, new_label = self.model(x_adv).max(1) - - # examples for which we have not found adversarial examples - i = ori_label.eq(new_label_multi) - new_label_multi[i] = new_label[i] - - failed_attack = ( - ori_label.eq(new_label_multi).sum().to(torch.float) - / self.normalized_x.size(0)) - self.assertLess(failed_attack, .5) - - -class TestCarliniWagnerL2(CommonAttackProperties): - - def setUp(self): - super(TestCarliniWagnerL2, self).setUp() - self.attack = carlini_wagner_l2 - self.attack_param = { - 'n_classes': 2, - 'max_iterations': 100, - 'binary_search_steps': 3, - 'initial_const': 1, - } - - def test_adv_example_success_rate(self): - self.help_adv_examples_success_rate(model=self.model, - x=self.normalized_x, rate=.1, clip_min=-5, clip_max=5, - **self.attack_param) - - def test_targeted_adv_example_success_rate(self): - self.help_targeted_adv_examples_success_rate(model=self.model, - x=self.normalized_x, rate=.9, clip_min=-5, clip_max=5, - **self.attack_param) - - def test_adv_examples_clipped_successfully(self): - x_adv = self.attack(model_fn=self.model, - x=self.normalized_x, - clip_min=-.2, - clip_max=.3, - **self.attack_param - ) - self.assertGreater(torch.min(x_adv), -.201) - self.assertLess(torch.max(x_adv), .301) - - def test_high_confidence_adv_example(self): - from copy import copy - - attack_param_copy = copy(self.attack_param) - attack_param_copy['binary_search_steps'] = 2 - - x = self.trivial_normalized_x - .5 - _, y = self.trivial_model(x).max(1) - - for confidence in [0, 2.3]: - x_adv = self.attack(model_fn=self.trivial_model, - x=x, - lr=1e-2, - clip_min=-10, - clip_max=10, - confidence=confidence, - **attack_param_copy - ) - logits = self.trivial_model(x_adv) - target = logits[range(len(logits)), 1 - y] - other = logits[range(len(logits)), y] - self.assertClose(confidence, torch.min(target - other).detach(), atol=1e-1) - self.assertTrue( - torch.argmax(logits, 1).eq(y).sum().to(torch.float) - / len(logits) == 0 - ) - - def test_high_confidence_targeted_adv_example(self): - from copy import copy - - attack_param_copy = copy(self.attack_param) - attack_param_copy['binary_search_steps'] = 2 - - for confidence in [0, 2.3]: - x_adv = self.attack(model_fn=self.trivial_model, - x=self.trivial_normalized_x - .5, - lr=1e-2, - clip_min=-10, - clip_max=10, - targeted=True, - y=self.y_target, - confidence=confidence, - **attack_param_copy - ) - logits = self.trivial_model(x_adv) - target = logits[range(len(logits)), self.y_target] - other = logits[range(len(logits)), 1 - self.y_target] - self.assertClose(confidence, torch.min(target - other).detach(), atol=1e-1) - self.assertGreater( - torch.argmax(logits, 1).eq(self.y_target).sum().to(torch.float) / len(logits), - .9 - ) - - -class TestSPSA(CommonAttackProperties): - - def setUp(self): - super(TestSPSA, self).setUp() - self.attack = spsa - self.attack_param = { - 'eps': .5, - 'clip_min': -5, - 'clip_max': 5, - 'nb_iter': 50, - 'model': self.model, - 'x': self.normalized_x - } - - def test_invalid_input(self): - x = torch.tensor([[-20., 30.]]) - self.assertRaises(AssertionError, self.attack, model_fn=self.model, x=x, eps=.1, - nb_iter=1, clip_min=-1., clip_max=1., sanity_checks=True) - - def test_invalid_eps(self): - self.assertRaises( - ValueError, self.attack, model_fn=self.model, x=self.x, eps=-.1, nb_iter=1) - - def test_eps_equals_zero(self): - self.assertClose( - self.attack(model_fn=self.model, x=self.x, eps=0, nb_iter=10), - self.x) - - def test_invalid_clips(self): - self.assertRaises( - ValueError, self.attack, model_fn=self.model, x=self.x, eps=.1, - clip_min=.5, clip_max=-.5, nb_iter=10) - - def test_adv_example_success_rate_linf(self): - # use normalized_x to make sure the same eps gives uniformly high attack - # success rate across randomized tests - self.help_adv_examples_success_rate(**self.attack_param) - - def test_targeted_adv_example_success_rate_linf(self): - self.help_targeted_adv_examples_success_rate(**self.attack_param) - - def test_adv_example_success_rate_l1(self): - self.assertRaises( - NotImplementedError, self.help_adv_examples_success_rate, norm=1, - **self.attack_param) - # TODO uncomment the actual test below after we have implemented the L1 attack - # self.help_adv_examples_success_rate( - # norm=1, **self.attack_param) - - def test_targeted_adv_example_success_rate_l1(self): - self.assertRaises( - NotImplementedError, self.help_targeted_adv_examples_success_rate, - norm=1, **self.attack_param) - # TODO uncomment the actual test below after we have implemented the L1 attack - # self.help_targeted_adv_examples_success_rate( - # norm=1, **self.attack_param) - - def test_adv_example_success_rate_l2(self): - self.help_adv_examples_success_rate( - norm=2, **self.attack_param) - - def test_targeted_adv_example_success_rate_l2(self): - self.help_targeted_adv_examples_success_rate( - norm=2, **self.attack_param) - - def test_attack_strength(self): - x_adv = self.attack( - model_fn=self.model, x=self.normalized_x, eps=1., - clip_min=.5, clip_max=.7, nb_iter=20, - sanity_checks=False) - _, ori_label = self.model(self.normalized_x).max(1) - _, adv_label = self.model(x_adv).max(1) - adv_acc = ( - adv_label.eq(ori_label).sum().to(torch.float) - / self.normalized_x.size(0)) - self.assertLess(adv_acc, .1) - - def test_eps(self): - x_adv = self.attack( - model_fn=self.model, x=self.normalized_x, eps=.5, nb_iter=10) - delta, _ = torch.abs(x_adv - self.normalized_x).max(dim=1) - self.assertTrue(torch.all(delta <= .5 + 1e-6)) - - def test_clips(self): - clip_min = -1. - clip_max = 1. - x_adv = self.attack( - model_fn=self.model, x=self.normalized_x, eps=.3, - nb_iter=10, clip_min=clip_min, clip_max=clip_max) - self.assertTrue(torch.all(x_adv <= clip_max)) - self.assertTrue(torch.all(x_adv >= clip_min)) - - -class TestHopSkipJumpAttack(CommonAttackProperties): - - def setUp(self): - super(TestHopSkipJumpAttack, self).setUp() - self.attack = hop_skip_jump_attack - - def test_generate_np_untargeted_l2(self): - x_val = torch.rand(50, 2) - bapp_params = { - 'norm': 2, - 'stepsize_search': 'geometric_progression', - 'num_iterations': 10, - 'verbose': True, - } - x_adv = self.attack(model_fn=self.model, x=x_val, **bapp_params) - - _, ori_label = self.model(x_val).max(1) - _, adv_label = self.model(x_adv).max(1) - adv_acc = ( - adv_label.eq(ori_label).sum().to(torch.float) - / x_val.size(0)) - - self.assertLess(adv_acc, .1) - - def test_generate_untargeted_linf(self): - x_val = torch.rand(50, 2) - bapp_params = { - 'norm': np.inf, - 'stepsize_search': 'grid_search', - 'num_iterations': 10, - 'verbose': True, - } - x_adv = self.attack(model_fn=self.model, x=x_val, **bapp_params) - - _, ori_label = self.model(x_val).max(1) - _, adv_label = self.model(x_adv).max(1) - adv_acc = ( - adv_label.eq(ori_label).sum().to(torch.float) - / x_val.size(0)) - - self.assertLess(adv_acc, .1) - - def test_generate_np_targeted_linf(self): - x_val = torch.rand(200, 2) - - _, ori_label = self.model(x_val).max(1) - x_val_pos = x_val[ori_label == 1] - x_val_neg = x_val[ori_label == 0] - - x_val_under_attack = torch.cat( - (x_val_pos[:25], x_val_neg[:25]), dim=0) - y_target = torch.cat( - [torch.zeros(25, dtype=torch.int64), torch.ones(25, dtype=torch.int64)]) - image_target = torch.cat((x_val_neg[25:50], x_val_pos[25:50]), dim=0) - - bapp_params = { - 'norm': np.inf, - 'stepsize_search': 'geometric_progression', - 'num_iterations': 10, - 'verbose': True, - 'y_target': y_target, - 'image_target': image_target, - } - x_adv = self.attack(model_fn=self.model, - x=x_val_under_attack, **bapp_params) - - _, new_labs = self.model(x_adv).max(1) - - adv_acc = ( - new_labs.eq(y_target).sum().to(torch.float) - / y_target.size(0)) - - self.assertGreater(adv_acc, .9) - - def test_generate_targeted_l2(self): - # Create data in numpy arrays. - x_val = torch.rand(200, 2) - - _, ori_label = self.model(x_val).max(1) - x_val_pos = x_val[ori_label == 1] - x_val_neg = x_val[ori_label == 0] - - x_val_under_attack = torch.cat( - (x_val_pos[:25], x_val_neg[:25]), dim=0) - y_target = torch.cat( - [torch.zeros(25, dtype=torch.int64), torch.ones(25, dtype=torch.int64)]) - image_target = torch.cat((x_val_neg[25:50], x_val_pos[25:50]), dim=0) - - # Create graph. - bapp_params = { - 'norm': 'l2', - 'stepsize_search': 'grid_search', - 'num_iterations': 10, - 'verbose': True, - 'y_target': y_target, - 'image_target': image_target, - } - x_adv = self.attack(model_fn=self.model, - x=x_val_under_attack, **bapp_params) - - _, new_labs = self.model(x_adv).max(1) - - adv_acc = ( - new_labs.eq(y_target).sum().to(torch.float) - / y_target.size(0)) - - self.assertGreater(adv_acc, .9) - - -class TestSparseL1Descent(CommonAttackProperties): - - def setUp(self): - super(TestSparseL1Descent, self).setUp() - self.attack = sparse_l1_descent - - def generate_adversarial_examples(self, **kwargs): - x_adv = self.attack(model_fn=self.model, x=self.normalized_x, **kwargs) - _, ori_label = self.model(self.normalized_x).max(1) - _, adv_label = self.model(x_adv).max(1) - adv_acc = ( - adv_label.eq(ori_label).sum().to(torch.float) - / self.normalized_x.size(0)) - - delta = torch.sum(torch.abs(x_adv - self.normalized_x), dim=1) - return x_adv, delta, adv_acc - - def generate_targeted_adversarial_examples(self, **kwargs): - y_target = torch.randint(low=0, high=2, size=(self.normalized_x.size(0),)) - x_adv = self.attack( - model_fn=self.model, x=self.normalized_x, - y=y_target, targeted=True, **kwargs) - - _, adv_label = self.model(x_adv).max(1) - adv_success = ( - adv_label.eq(y_target).sum().to(torch.float) - / self.normalized_x.size(0)) - - delta = torch.sum(torch.abs(x_adv - self.normalized_x), dim=1) - return x_adv, delta, adv_success - - def test_invalid_input(self): - x_val = -torch.ones((2, 2)) - with self.assertRaises(AssertionError): - self.attack(self.model, x_val, eps=10.0, clip_min=0., clip_max=1.) - - def test_gives_adversarial_example(self): - _, delta, adv_acc = self.generate_adversarial_examples(eps=2, - clip_min=-5, clip_max=5) - self.assertLess(adv_acc, .5) - self.assertLess(torch.max(torch.abs(delta - 2)), 1e-3) - - def test_targeted_gives_adversarial_example(self): - _, delta, adv_acc = self.generate_targeted_adversarial_examples(eps=10, - clip_min=-5, clip_max=5) - self.assertGreater(adv_acc, .7) - self.assertLessEqual(torch.max(delta), 10.001) - - def test_can_be_called_with_different_eps(self): - for eps in [10, 20, 30, 40]: - _, delta, _ = self.generate_adversarial_examples(eps=eps, - clip_min=-5, clip_max=5) - self.assertLessEqual(torch.max(delta), eps + 1e-4) - - def test_clip_works_as_expected(self): - x_adv, _, _ = self.generate_adversarial_examples(eps=10, - nb_iter=20, rand_init=True, clip_min=-0.2, - clip_max=0.1, sanity_checks=False) - - self.assertClose(torch.min(x_adv), -0.2) - self.assertClose(torch.max(x_adv), 0.1) - - def test_do_not_reach_lp_boundary(self): - """ - Make sure that iterative attack don't reach boundary of Lp - neighbourhood if nb_iter * eps_iter is relatively small compared to - epsilon. - """ - _, delta, _ = self.generate_adversarial_examples(eps=.5, - clip_min=-5, clip_max=5, - nb_iter=10, eps_iter=.01) - self.assertTrue(torch.max(0.5 - delta) > 0.25) - - def test_generate_np_gives_clipped_adversarial_examples(self): - x_adv, _, _ = self.generate_adversarial_examples(eps=1.0, - eps_iter=0.1, nb_iter=5, - clip_min=-0.2, clip_max=0.3, - sanity_checks=False) - - self.assertLess(-0.201, torch.min(x_adv)) - self.assertLess(torch.max(x_adv), .301) - - def test_clip_eta(self): - _, delta, _ = self.generate_adversarial_examples(eps=1, - clip_min=-5, clip_max=5, - nb_iter=5, eps_iter=.1) - - # this projection is less numerically stable so give it some slack - self.assertLessEqual(torch.max(delta), 1. + 1e-6) - - def test_attack_strength(self): - # sanity checks turned off because this test initializes outside - # the valid range. - _, _, adv_acc = self.generate_adversarial_examples(eps=10, - rand_init=True, clip_min=0.5, clip_max=0.7, - nb_iter=10, sanity_checks=False) - - self.assertLess(adv_acc, 0.4) - - def test_grad_clip(self): - """ - With clipped gradients, we achieve - np.mean(orig_labels == new_labels) == 0.0 - """ - - # sanity checks turned off because this test initializes outside - # the valid range. - _, _, adv_acc = self.generate_adversarial_examples(eps=10, - rand_init=True, clip_grad=True, - clip_min=0.5, clip_max=0.7, - nb_iter=10, sanity_checks=False) - self.assertLess(adv_acc, 0.1) - - def test_sparsity(self): - # use a model with larger input dimensionality for this test. - model_fn = DummyModel(1000) - x_val = torch.rand(100, 1000) - - for q in [1, 9, 25.8, 50, 75.4, 90.2, 99, 99.9]: - x_adv = self.attack(model_fn, x_val, eps=5.0, grad_sparsity=q, - nb_iter=1, sanity_checks=False) - - numzero = torch.sum(x_adv - x_val == 0, dim=-1).float() - self.assertAlmostEqual(q * 1000.0 / 100.0, torch.mean(numzero), delta=1) - - def test_grad_sparsity_checks(self): - # test that the attacks allows `grad_sparsity` to be specified as a scalar - # in (0, 100) or as a vector. - - # scalar values out of range - with self.assertRaises(ValueError): - self.generate_adversarial_examples(grad_sparsity=0) - - with self.assertRaises(ValueError): - self.generate_adversarial_examples(grad_sparsity=100) - - # sparsity as 2D array should fail - with self.assertRaises(ValueError): - gs = torch.empty(100, 2).uniform_(90, 99) - self.generate_adversarial_examples(sanity_checks=False, grad_sparsity=gs) - - # sparsity as 1D array should succeed - gs = torch.empty(100).uniform_(90, 99) - self.generate_adversarial_examples(sanity_checks=False, grad_sparsity=gs) - - # sparsity vector of wrong size should fail - with self.assertRaises(ValueError) as context: - gs = torch.empty(101).uniform_(90, 99) - self.generate_adversarial_examples(sanity_checks=False, grad_sparsity=gs) diff --git a/cleverhans/future/torch/tests/test_utils.py b/cleverhans/future/torch/tests/test_utils.py deleted file mode 100644 index a4ee73150..000000000 --- a/cleverhans/future/torch/tests/test_utils.py +++ /dev/null @@ -1,108 +0,0 @@ -# pylint: disable=missing-docstring -import numpy as np -import torch - -import cleverhans.future.torch.utils as utils -from cleverhans.devtools.checks import CleverHansTest - -class TestOptimizeLinear(CleverHansTest): - """ - Identical to the TestOptimizeLinear in tests_tf/test_attacks. - """ - def setUp(self): - super(TestOptimizeLinear, self).setUp() - self.clip_eta = utils.clip_eta - self.rand_grad = torch.randn(100, 3, 2) - self.rand_eta = torch.randn(100, 3, 2) - self.red_ind = list(range(1, len(self.rand_grad.size()))) - # eps needs to be nonnegative - self.eps_list = [0, .1, 1., 3] - - def test_optimize_linear_linf(self): - grad = torch.tensor([[1., -2.]]) - eta = utils.optimize_linear(grad, eps=1., norm=np.inf) - objective = torch.sum(grad * eta) - - self.assertEqual(grad.size(), eta.size()) - self.assertClose(objective, grad.abs().sum()) - self.assertClose(eta.abs(), 1.) - - def test_optimize_linear_l2(self): - grad = torch.tensor([[.5 ** .5, -.5 ** .5]]) - eta = utils.optimize_linear(grad, eps=1., norm=2) - objective = torch.sum(grad * eta) - - self.assertEqual(grad.size(), eta.size()) - self.assertClose(objective, 1.) - self.assertClose(eta.pow(2).sum().sqrt(), 1.) - - def test_optimize_linear_l1(self): - grad = torch.tensor([[1., -2.]]) - eta = utils.optimize_linear(grad, eps=1., norm=1) - objective = torch.sum(grad * eta) - - self.assertEqual(grad.size(), eta.size()) - self.assertClose(objective, 2.) - self.assertClose(eta.abs().sum(), 1.) - - def test_optimize_linear_l1_ties(self): - grad = torch.tensor([[2., -2.]]) - eta = utils.optimize_linear(grad, eps=1., norm=1) - objective = torch.sum(grad * eta) - - self.assertEqual(grad.size(), eta.size()) - self.assertClose(objective, 2.) - self.assertClose(eta.abs().sum(), 1.) - - def test_optimize_linear_linf_satisfies_norm_constraint(self): - for eps in self.eps_list: - eta = utils.optimize_linear(self.rand_grad, eps=eps, norm=np.inf) - self.assertClose(eta.abs(), eps) - - def test_optimize_linear_l1_satisfies_norm_constraint(self): - for eps in self.eps_list: - eta = utils.optimize_linear(self.rand_grad, eps=eps, norm=1) - norm = eta.abs().sum(dim=self.red_ind) - self.assertTrue(torch.allclose(norm, eps * torch.ones_like(norm))) - - def test_optimize_linear_l2_satisfies_norm_constraint(self): - for eps in self.eps_list: - eta = utils.optimize_linear(self.rand_grad, eps=eps, norm=2) - # optimize_linear uses avoid_zero_div as the divisor for - # gradients with overly small l2 norms when performing norm - # normalizations on the gradients so as to safeguard against - # zero division error. Therefore, the replaced gradient vectors - # will not be l2-unit vectors after normalization. In this test, - # these gradients are filtered out by the one_mask - # below and are not tested. - # NOTE the value of avoid_zero_div should be the same as the - # avoid_zero_div used in the optimize_linear function - avoid_zero_div = torch.tensor(1e-12) - square = torch.max( - avoid_zero_div, - torch.sum(self.rand_grad ** 2, self.red_ind, keepdim=True) - ) - norm = eta.pow(2).sum(dim=self.red_ind, keepdim=True).sqrt() - one_mask = ( - (square <= avoid_zero_div).to(torch.float) * norm + - (square > avoid_zero_div).to(torch.float)) - self.assertTrue(torch.allclose(norm, eps * one_mask)) - - def test_clip_eta_linf(self): - clipped = self.clip_eta(eta=self.rand_eta, norm=np.inf, eps=.5) - self.assertTrue(torch.all(clipped <= .5)) - self.assertTrue(torch.all(clipped >= -.5)) - - def test_clip_eta_l1(self): - self.assertRaises( - NotImplementedError, self.clip_eta, eta=self.rand_eta, norm=1, eps=.5) - - # TODO uncomment the actual test below after we have implemented the L1 attack - # clipped = self.clip_eta(eta=self.rand_eta, norm=1, eps=.5) - # norm = clipped.abs().sum(dim=self.red_ind) - # self.assertTrue(torch.all(norm <= .5001)) - - def test_clip_eta_l2(self): - clipped = self.clip_eta(eta=self.rand_eta, norm=2, eps=.5) - norm = clipped.pow(2).sum(dim=self.red_ind).pow(.5) - self.assertTrue(torch.all(norm <= .5001)) diff --git a/cleverhans/future/torch/utils.py b/cleverhans/future/torch/utils.py deleted file mode 100644 index 4cfda422c..000000000 --- a/cleverhans/future/torch/utils.py +++ /dev/null @@ -1,147 +0,0 @@ -"""Utils for PyTorch""" - -import numpy as np - -import torch - - -def clip_eta(eta, norm, eps): - """ - PyTorch implementation of the clip_eta in utils_tf. - - :param eta: Tensor - :param norm: np.inf, 1, or 2 - :param eps: float - """ - if norm not in [np.inf, 1, 2]: - raise ValueError('norm must be np.inf, 1, or 2.') - - avoid_zero_div = torch.tensor(1e-12, dtype=eta.dtype, device=eta.device) - reduc_ind = list(range(1, len(eta.size()))) - if norm == np.inf: - eta = torch.clamp(eta, -eps, eps) - else: - if norm == 1: - raise NotImplementedError("L1 clip is not implemented.") - norm = torch.max( - avoid_zero_div, - torch.sum(torch.abs(eta), dim=reduc_ind, keepdim=True) - ) - elif norm == 2: - norm = torch.sqrt(torch.max( - avoid_zero_div, - torch.sum(eta ** 2, dim=reduc_ind, keepdim=True) - )) - factor = torch.min( - torch.tensor(1., dtype=eta.dtype, device=eta.device), - eps / norm - ) - eta *= factor - return eta - - -def get_or_guess_labels(model, x, **kwargs): - """ - Get the label to use in generating an adversarial example for x. - The kwargs are fed directly from the kwargs of the attack. - If 'y' is in kwargs, then assume it's an untargeted attack and - use that as the label. - If 'y_target' is in kwargs and is not none, then assume it's a - targeted attack and use that as the label. - Otherwise, use the model's prediction as the label and perform an - untargeted attack. - - :param model: PyTorch model. Do not add a softmax gate to the output. - :param x: Tensor, shape (N, d_1, ...). - :param y: (optional) Tensor, shape (N). - :param y_target: (optional) Tensor, shape (N). - """ - if 'y' in kwargs and 'y_target' in kwargs: - raise ValueError("Can not set both 'y' and 'y_target'.") - if 'y' in kwargs: - labels = kwargs['y'] - elif 'y_target' in kwargs and kwargs['y_target'] is not None: - labels = kwargs['y_target'] - else: - _, labels = torch.max(model(x), 1) - return labels - - -def optimize_linear(grad, eps, norm=np.inf): - """ - Solves for the optimal input to a linear function under a norm constraint. - - Optimal_perturbation = argmax_{eta, ||eta||_{norm} < eps} dot(eta, grad) - - :param grad: Tensor, shape (N, d_1, ...). Batch of gradients - :param eps: float. Scalar specifying size of constraint region - :param norm: np.inf, 1, or 2. Order of norm constraint. - :returns: Tensor, shape (N, d_1, ...). Optimal perturbation - """ - - red_ind = list(range(1, len(grad.size()))) - avoid_zero_div = torch.tensor(1e-12, dtype=grad.dtype, device=grad.device) - if norm == np.inf: - # Take sign of gradient - optimal_perturbation = torch.sign(grad) - elif norm == 1: - abs_grad = torch.abs(grad) - sign = torch.sign(grad) - red_ind = list(range(1, len(grad.size()))) - abs_grad = torch.abs(grad) - ori_shape = [1]*len(grad.size()) - ori_shape[0] = grad.size(0) - - max_abs_grad, _ = torch.max(abs_grad.view(grad.size(0), -1), 1) - max_mask = abs_grad.eq(max_abs_grad.view(ori_shape)).to(torch.float) - num_ties = max_mask - for red_scalar in red_ind: - num_ties = torch.sum(num_ties, red_scalar, keepdim=True) - optimal_perturbation = sign * max_mask / num_ties - # TODO integrate below to a test file - # check that the optimal perturbations have been correctly computed - opt_pert_norm = optimal_perturbation.abs().sum(dim=red_ind) - assert torch.all(opt_pert_norm == torch.ones_like(opt_pert_norm)) - elif norm == 2: - square = torch.max( - avoid_zero_div, - torch.sum(grad ** 2, red_ind, keepdim=True) - ) - optimal_perturbation = grad / torch.sqrt(square) - # TODO integrate below to a test file - # check that the optimal perturbations have been correctly computed - opt_pert_norm = optimal_perturbation.pow( - 2).sum(dim=red_ind, keepdim=True).sqrt() - one_mask = ( - (square <= avoid_zero_div).to(torch.float) * opt_pert_norm + - (square > avoid_zero_div).to(torch.float)) - assert torch.allclose(opt_pert_norm, one_mask, rtol=1e-05, atol=1e-08) - else: - raise NotImplementedError("Only L-inf, L1 and L2 norms are " - "currently implemented.") - - # Scale perturbation to be the solution for the norm=eps rather than - # norm=1 problem - scaled_perturbation = eps * optimal_perturbation - return scaled_perturbation - - -def zero_out_clipped_grads(grad, x, clip_min, clip_max): - """ - Helper function to erase entries in the gradient where the update would be - clipped. - :param grad: The gradient - :param x: The current input - :param clip_min: Minimum input component value - :param clip_max: Maximum input component value - """ - signed_grad = torch.sign(grad) - - # Find input components that lie at the boundary of the input range, and - # where the gradient points in the wrong direction. - clip_low = torch.le(x, clip_min) & torch.lt(signed_grad, 0) - clip_high = torch.ge(x, clip_max) & torch.gt(signed_grad, 0) - clip = clip_low | clip_high - grad = torch.where(clip, torch.zeros_like(grad), grad) - - return grad diff --git a/cleverhans/initializers.py b/cleverhans/initializers.py deleted file mode 100644 index 1ced86b50..000000000 --- a/cleverhans/initializers.py +++ /dev/null @@ -1,22 +0,0 @@ -""" -Initializers. -""" - -import tensorflow as tf - - -class HeReLuNormalInitializer(tf.initializers.random_normal): - """ - The initializer from He et al 2015 - """ - def __init__(self, dtype=tf.float32): - super(HeReLuNormalInitializer, self).__init__(dtype=dtype) - - def get_config(self): - return dict(dtype=self.dtype.name) - - def __call__(self, shape, dtype=None, partition_info=None): - del partition_info - dtype = self.dtype if dtype is None else dtype - std = tf.rsqrt(tf.cast(tf.reduce_prod(shape[:-1]), tf.float32) + 1e-7) - return tf.random_normal(shape, stddev=std, dtype=dtype) diff --git a/cleverhans/future/__init__.py b/cleverhans/jax/__init__.py similarity index 100% rename from cleverhans/future/__init__.py rename to cleverhans/jax/__init__.py diff --git a/cleverhans/jax/attacks/__init__.py b/cleverhans/jax/attacks/__init__.py new file mode 100644 index 000000000..f592a87c5 --- /dev/null +++ b/cleverhans/jax/attacks/__init__.py @@ -0,0 +1 @@ +from cleverhans.jax.attacks.projected_gradient_descent import projected_gradient_descent diff --git a/cleverhans/jax/attacks/fast_gradient_method.py b/cleverhans/jax/attacks/fast_gradient_method.py new file mode 100644 index 000000000..4fbc05cc5 --- /dev/null +++ b/cleverhans/jax/attacks/fast_gradient_method.py @@ -0,0 +1,69 @@ +import jax.numpy as np +from jax import grad, vmap +from jax.experimental.stax import logsoftmax + +from cleverhans.jax.utils import one_hot + + +def fast_gradient_method( + model_fn, x, eps, norm, clip_min=None, clip_max=None, y=None, targeted=False +): + """ + JAX implementation of the Fast Gradient Method. + :param model_fn: a callable that takes an input tensor and returns the model logits. + :param x: input tensor. + :param eps: epsilon (input variation parameter); see https://arxiv.org/abs/1412.6572. + :param norm: Order of the norm (mimics NumPy). Possible values: np.inf or 2. + :param clip_min: (optional) float. Minimum float value for adversarial example components. + :param clip_max: (optional) float. Maximum float value for adversarial example components. + :param y: (optional) Tensor with one-hot true labels. If targeted is true, then provide the + target one-hot label. Otherwise, only provide this parameter if you'd like to use true + labels when crafting adversarial samples. Otherwise, model predictions are used + as labels to avoid the "label leaking" effect (explained in this paper: + https://arxiv.org/abs/1611.01236). Default is None. This argument does not have + to be a binary one-hot label (e.g., [0, 1, 0, 0]), it can be floating points values + that sum up to 1 (e.g., [0.05, 0.85, 0.05, 0.05]). + :param targeted: (optional) bool. Is the attack targeted or untargeted? + Untargeted, the default, will try to make the label incorrect. + Targeted will instead try to move in the direction of being more like y. + :return: a tensor for the adversarial example + """ + if norm not in [np.inf, 2]: + raise ValueError("Norm order must be either np.inf or 2.") + + if y is None: + # Using model predictions as ground truth to avoid label leaking + x_labels = np.argmax(model_fn(x), 1) + y = one_hot(x_labels, 10) + + def loss_adv(image, label): + pred = model_fn(image[None]) + loss = -np.sum(logsoftmax(pred) * label) + if targeted: + loss = -loss + return loss + + grads_fn = vmap(grad(loss_adv), in_axes=(0, 0), out_axes=0) + grads = grads_fn(x, y) + + axis = list(range(1, len(grads.shape))) + avoid_zero_div = 1e-12 + if norm == np.inf: + perturbation = eps * np.sign(grads) + elif norm == 1: + raise NotImplementedError("L_1 norm has not been implemented yet.") + elif norm == 2: + square = np.maximum( + avoid_zero_div, np.sum(np.square(grads), axis=axis, keepdims=True) + ) + perturbation = grads / np.sqrt(square) + + adv_x = x + perturbation + + # If clipping is needed, reset all values outside of [clip_min, clip_max] + if (clip_min is not None) or (clip_max is not None): + # We don't currently support one-sided clipping + assert clip_min is not None and clip_max is not None + adv_x = np.clip(adv_x, a_min=clip_min, a_max=clip_max) + + return adv_x diff --git a/cleverhans/jax/attacks/projected_gradient_descent.py b/cleverhans/jax/attacks/projected_gradient_descent.py new file mode 100644 index 000000000..e5dbdde4f --- /dev/null +++ b/cleverhans/jax/attacks/projected_gradient_descent.py @@ -0,0 +1,99 @@ +import jax.numpy as np + +from cleverhans.jax.attacks.fast_gradient_method import fast_gradient_method +from cleverhans.jax.utils import clip_eta, one_hot + + +def projected_gradient_descent( + model_fn, + x, + eps, + eps_iter, + nb_iter, + norm, + clip_min=None, + clip_max=None, + y=None, + targeted=False, + rand_init=None, + rand_minmax=0.3, +): + """ + This class implements either the Basic Iterative Method + (Kurakin et al. 2016) when rand_init is set to 0. or the + Madry et al. (2017) method when rand_minmax is larger than 0. + Paper link (Kurakin et al. 2016): https://arxiv.org/pdf/1607.02533.pdf + Paper link (Madry et al. 2017): https://arxiv.org/pdf/1706.06083.pdf + :param model_fn: a callable that takes an input tensor and returns the model logits. + :param x: input tensor. + :param eps: epsilon (input variation parameter); see https://arxiv.org/abs/1412.6572. + :param eps_iter: step size for each attack iteration + :param nb_iter: Number of attack iterations. + :param norm: Order of the norm (mimics NumPy). Possible values: np.inf or 2. + :param clip_min: (optional) float. Minimum float value for adversarial example components. + :param clip_max: (optional) float. Maximum float value for adversarial example components. + :param y: (optional) Tensor with true labels. If targeted is true, then provide the + target label. Otherwise, only provide this parameter if you'd like to use true + labels when crafting adversarial samples. Otherwise, model predictions are used + as labels to avoid the "label leaking" effect (explained in this paper: + https://arxiv.org/abs/1611.01236). Default is None. + :param targeted: (optional) bool. Is the attack targeted or untargeted? + Untargeted, the default, will try to make the label incorrect. + Targeted will instead try to move in the direction of being more like y. + :return: a tensor for the adversarial example + """ + + assert eps_iter <= eps, (eps_iter, eps) + if norm == 1: + raise NotImplementedError( + "It's not clear that FGM is a good inner loop" + " step for PGD when norm=1, because norm=1 FGM " + " changes only one pixel at a time. We need " + " to rigorously test a strong norm=1 PGD " + "before enabling this feature." + ) + if norm not in [np.inf, 2]: + raise ValueError("Norm order must be either np.inf or 2.") + + # Initialize loop variables + if rand_init: + rand_minmax = eps + eta = np.random.uniform(x.shape, -rand_minmax, rand_minmax) + else: + eta = np.zeros_like(x) + + # Clip eta + eta = clip_eta(eta, norm, eps) + adv_x = x + eta + if clip_min is not None or clip_max is not None: + adv_x = np.clip(adv_x, a_min=clip_min, a_max=clip_max) + + if y is None: + # Using model predictions as ground truth to avoid label leaking + x_labels = np.argmax(model_fn(x), 1) + y = one_hot(x_labels, 10) + + for _ in range(nb_iter): + adv_x = fast_gradient_method( + model_fn, + adv_x, + eps_iter, + norm, + clip_min=clip_min, + clip_max=clip_max, + y=y, + targeted=targeted, + ) + + # Clipping perturbation eta to norm norm ball + eta = adv_x - x + eta = clip_eta(eta, norm, eps) + adv_x = x + eta + + # Redo the clipping. + # FGM already did it, but subtracting and re-adding eta can add some + # small numerical error. + if clip_min is not None or clip_max is not None: + adv_x = np.clip(adv_x, a_min=clip_min, a_max=clip_max) + + return adv_x diff --git a/cleverhans/jax/utils.py b/cleverhans/jax/utils.py new file mode 100644 index 000000000..08164cb43 --- /dev/null +++ b/cleverhans/jax/utils.py @@ -0,0 +1,39 @@ +import jax.numpy as np + + +def one_hot(x, k, dtype=np.float32): + """Create a one-hot encoding of x of size k.""" + return np.array(x[:, None] == np.arange(k), dtype) + + +def partial_flatten(x): + """Flatten all but the first dimension of an ndarray.""" + return np.reshape(x, (x.shape[0], -1)) + + +def clip_eta(eta, norm, eps): + """ + Helper function to clip the perturbation to epsilon norm ball. + :param eta: A tensor with the current perturbation. + :param norm: Order of the norm (mimics Numpy). + Possible values: np.inf or 2. + :param eps: Epsilon, bound of the perturbation. + """ + + # Clipping perturbation eta to self.norm norm ball + if norm not in [np.inf, 2]: + raise ValueError("norm must be np.inf or 2.") + + axis = list(range(1, len(eta.shape))) + avoid_zero_div = 1e-12 + if norm == np.inf: + eta = np.clip(eta, a_min=-eps, a_max=eps) + elif norm == 2: + # avoid_zero_div must go inside sqrt to avoid a divide by zero in the gradient through this operation + norm = np.sqrt( + np.maximum(avoid_zero_div, np.sum(np.square(eta), axis=axis, keepdims=True)) + ) + # We must *clip* to within the norm ball, not *normalize* onto the surface of the ball + factor = np.minimum(1.0, np.divide(eps, norm)) + eta = eta * factor + return eta diff --git a/cleverhans/loss.py b/cleverhans/loss.py deleted file mode 100644 index d87f32876..000000000 --- a/cleverhans/loss.py +++ /dev/null @@ -1,518 +0,0 @@ -"""Loss functions for training models.""" -import copy -import json -import os -import warnings - -import numpy as np -import tensorflow as tf - -from cleverhans.attacks import Attack -from cleverhans.compat import softmax_cross_entropy_with_logits -from cleverhans.model import Model -from cleverhans.utils import safe_zip - -try: - import tensorflow_probability as tfp - tf_distributions = tfp.distributions -except ImportError: - tf_distributions = tf.distributions - - -class Loss(object): - """ - An abstract interface for loss wrappers that allows flexible control of - real examples, adversarial examples and labels. These losses are used - for defenses (during model training). - """ - - def __init__(self, model, hparams=None, attack=None): - """ - :param model: Model instance, the model on which to apply the loss. - :param hparams: dict, hyper-parameters for the loss. - :param attack: cleverhans.attacks.Attack instance - """ - assert isinstance(model, Model) - standard = attack is None or isinstance(attack, Attack) - deprecated = callable(attack) - if not standard and not deprecated: - raise TypeError("`attack` must be `None` or `Attack` subclass instance") - if deprecated: - warnings.warn("callable attacks are deprecated, switch to an Attack " - "subclass. callable attacks will not be supported after " - "2019-05-05.") - - class Wrapper(Attack): - """ - Temporary wrapper class to be removed when deprecated callable - arguments are removed. - - :param f: a callable object implementing the attack - """ - - def __init__(self, f): - dummy_model = Model() - super(Wrapper, self).__init__(model=dummy_model) - self.f = f - - def generate(self, x): - return self.f(x) - - attack = Wrapper(attack) - self.model = model - self.hparams = hparams - self.attack = attack - - def save(self, path): - """Save loss in json format - """ - json.dump(dict(loss=self.__class__.__name__, - params=self.hparams), - open(os.path.join(path, 'loss.json'), 'wb')) - - def fprop(self, x, y): - """Forward propagate the loss. - Loss should be a scalar value, independent of batch size (i.e. use - reduce_mean over batch axis, don't use reduce_sum or return a tensor). - Scalar losses are easier to add together, e.g. through `WeightedSum`. - Mean losses are easier to redistribute across multiple replicas without - needing to change learning rates, etc. - :param x: tensor, a batch of inputs. - :param y: tensor, a batch of outputs (1-hot labels typically). - """ - raise NotImplementedError - - -class WeightedSum(Loss): - """ - A Loss that adds up a weighted sum of other losses. - """ - - def __init__(self, model, terms): - self.terms = terms - - Loss.__init__(self, model, locals()) - - def fprop(self, x, y, **kwargs): - weights, loss_objects = safe_zip(*self.terms) - for weight in weights: - if isinstance(weight, float): - continue - if hasattr(weight, 'ndim'): - assert weight.ndim == 0 - continue - raise TypeError("weight of %s is not a type that this function " - "knows it can accept yet" % str(weight)) - losses = [loss.fprop(x, y, **kwargs) for loss in loss_objects] - for loss, loss_object in safe_zip(losses, loss_objects): - if len(loss.get_shape()) > 0: - raise ValueError("%s.fprop returned a non-scalar value" % - str(loss_object)) - terms = [weight * loss for weight, loss in safe_zip(weights, losses)] - - return tf.add_n(terms) - - -class CrossEntropy(Loss): - """Cross-entropy loss for a multiclass softmax classifier. - :param model: Model instance, the model on which to apply the loss. - :param smoothing: float, amount of label smoothing for cross-entropy. - :param attack: function, given an input x, return an attacked x'. - :param pass_y: bool, if True pass y to the attack - :param adv_coeff: Coefficient to put on the cross-entropy for - adversarial examples, if adversarial examples are used. - The coefficient on the cross-entropy for clean examples is - 1. - adv_coeff. - :param attack_params: dict, keyword arguments passed to `attack.generate` - """ - - def __init__(self, model, smoothing=0., attack=None, pass_y=False, - adv_coeff=0.5, attack_params=None, - **kwargs): - if smoothing < 0 or smoothing > 1: - raise ValueError('Smoothing must be in [0, 1]', smoothing) - self.kwargs = kwargs - Loss.__init__(self, model, locals(), attack) - self.smoothing = smoothing - self.adv_coeff = adv_coeff - self.pass_y = pass_y - self.attack_params = attack_params - - def fprop(self, x, y, **kwargs): - kwargs.update(self.kwargs) - if self.attack is not None: - attack_params = copy.copy(self.attack_params) - if attack_params is None: - attack_params = {} - if self.pass_y: - attack_params['y'] = y - x = x, self.attack.generate(x, **attack_params) - coeffs = [1. - self.adv_coeff, self.adv_coeff] - if self.adv_coeff == 1.: - x = (x[1],) - coeffs = (coeffs[1],) - else: - x = tuple([x]) - coeffs = [1.] - assert np.allclose(sum(coeffs), 1.) - - # Catching RuntimeError: Variable -= value not supported by tf.eager. - try: - y -= self.smoothing * (y - 1. / tf.cast(y.shape[-1], y.dtype)) - except RuntimeError: - y.assign_sub(self.smoothing * (y - 1. / tf.cast(y.shape[-1], - y.dtype))) - - logits = [self.model.get_logits(x, **kwargs) for x in x] - loss = sum( - coeff * tf.reduce_mean(softmax_cross_entropy_with_logits(labels=y, - logits=logit)) - for coeff, logit in safe_zip(coeffs, logits)) - return loss - - -class MixUp(Loss): - """Mixup ( https://arxiv.org/abs/1710.09412 ) - :param model: Model instance, the model on which to apply the loss. - :param beta: float, beta distribution parameter for MixUp. - """ - - def __init__(self, model, beta, **kwargs): - del kwargs - Loss.__init__(self, model, locals()) - self.beta = beta - - def fprop(self, x, y, **kwargs): - with tf.device('/CPU:0'): - # Prevent error complaining GPU kernels unavailable for this. - mix = tf_distributions.Beta(self.beta, self.beta) - mix = mix.sample([tf.shape(x)[0]] + [1] * (len(x.shape) - 1)) - mix = tf.maximum(mix, 1 - mix) - mix_label = tf.reshape(mix, [-1, 1]) - xm = x + mix * (x[::-1] - x) - ym = y + mix_label * (y[::-1] - y) - logits = self.model.get_logits(xm, **kwargs) - loss = tf.reduce_mean(softmax_cross_entropy_with_logits(labels=ym, - logits=logits)) - return loss - - -class FeaturePairing(Loss): - """Feature pairing loss. - :param model: Model instance, the model on which to apply the loss. - :param weight: float, with of logic pairing loss. - :param attack: function, given an input x, return an attacked x'. - """ - - def __init__(self, model, weight, attack, **kwargs): - del kwargs - Loss.__init__(self, model, locals(), attack) - self.weight = weight - - def fprop(self, x, y, **kwargs): - x_adv = self.attack.generate(x) - d1 = self.model.fprop(x, **kwargs) - d2 = self.model.fprop(x_adv, **kwargs) - pairing_loss = [tf.reduce_mean(tf.square(a - b)) - for a, b in - zip(d1[Model.O_FEATURES], d2[Model.O_FEATURES])] - pairing_loss = tf.reduce_mean(pairing_loss) - loss = tf.reduce_mean(softmax_cross_entropy_with_logits( - labels=y, logits=d1[Model.O_LOGITS])) - loss += tf.reduce_mean(softmax_cross_entropy_with_logits( - labels=y, logits=d2[Model.O_LOGITS])) - return loss + self.weight * pairing_loss - - -class WeightDecay(Loss): - """Weight decay""" - - def fprop(self, x, y, **kwargs): - terms = [tf.nn.l2_loss(param) - for param in self.model.get_params() - if len(param.get_shape()) > 1] - out = tf.add_n(terms) - assert len(out.get_shape()) == 0 - return out - - -class LossCrossEntropy(Loss): - """ - Deprecated version of `CrossEntropy` that returns per-example loss rather - than mean loss. - """ - - def __init__(self, model, smoothing=0., attack=None, **kwargs): - """Constructor. - :param model: Model instance, the model on which to apply the loss. - :param smoothing: float, amount of label smoothing for cross-entropy. - :param attack: function, given an input x, return an attacked x'. - """ - if smoothing < 0 or smoothing > 1: - raise ValueError('Smoothing must be in [0, 1]', smoothing) - del kwargs - Loss.__init__(self, model, locals(), attack) - self.smoothing = smoothing - - def fprop(self, x, y, **kwargs): - if self.attack is not None: - x = x, self.attack(x) - else: - x = tuple([x]) - - # Catching RuntimeError: Variable -= value not supported by tf.eager. - try: - y -= self.smoothing * (y - 1. / tf.cast(y.shape[-1], tf.float32)) - except RuntimeError: - y.assign_sub(self.smoothing * (y - 1. / tf.cast(y.shape[-1], - tf.float32))) - - logits = [self.model.get_logits(x, **kwargs) for x in x] - loss = sum( - softmax_cross_entropy_with_logits(labels=y, - logits=logit) - for logit in logits) - warnings.warn("LossCrossEntropy is deprecated, switch to " - "CrossEntropy. LossCrossEntropy may be removed on " - "or after 2019-03-06.") - return loss - - -class LossFeaturePairing(Loss): - """Deprecated version of `FeaturePairing` that returns per-example loss - rather than mean loss.""" - - def __init__(self, model, weight, attack, **kwargs): - """Constructor. - :param model: Model instance, the model on which to apply the loss. - :param weight: float, with of logic pairing loss. - :param attack: function, given an input x, return an attacked x'. - """ - del kwargs - Loss.__init__(self, model, locals(), attack) - self.weight = weight - - def fprop(self, x, y, **kwargs): - x_adv = self.attack(x) - d1 = self.model.fprop(x, **kwargs) - d2 = self.model.fprop(x_adv, **kwargs) - pairing_loss = [tf.reduce_mean(tf.square(a - b)) - for a, b in - zip(d1[Model.O_FEATURES], d2[Model.O_FEATURES])] - pairing_loss = tf.reduce_mean(pairing_loss) - loss = softmax_cross_entropy_with_logits( - labels=y, logits=d1[Model.O_LOGITS]) - loss += softmax_cross_entropy_with_logits( - labels=y, logits=d2[Model.O_LOGITS]) - warnings.warn("LossFeaturePairing is deprecated, switch to " - "FeaturePairing. LossFeaturePairing may be removed " - "on or after 2019-03-06.") - return loss + self.weight * pairing_loss - - -class LossMixUp(Loss): - """Deprecated version of `MixUp` that returns per-example loss - rather than mean loss.""" - - def __init__(self, model, beta, **kwargs): - """Constructor. - :param model: Model instance, the model on which to apply the loss. - :param beta: float, beta distribution parameter for MixUp. - """ - del kwargs - Loss.__init__(self, model, locals()) - self.beta = beta - - def fprop(self, x, y, **kwargs): - mix = tf_distributions.Beta(self.beta, self.beta) - mix = mix.sample([tf.shape(x)[0]] + [1] * (len(x.shape) - 1)) - xm = x + mix * (x[::-1] - x) - ym = y + mix * (y[::-1] - y) - logits = self.model.get_logits(xm, **kwargs) - loss = softmax_cross_entropy_with_logits(labels=ym, logits=logits) - warnings.warn("LossMixUp is deprecated, switch to " - "MixUp. LossFeaturePairing may be removed " - "on or after 2019-03-06.") - return loss - - -class SNNLCrossEntropy(CrossEntropy): - """A combination loss of Soft Nearest Neighbor Loss calculated at every layer - in the network, and standard cross entropy of the logits. Presented in - "Analyzing and Improving Representations with the Soft Nearest Neighbor Loss" - by Nicholas Frosst, Nicolas Papernot, and Geoffrey Hinton. - arXiv preprint arXiv:1902.01889 (2019).""" - STABILITY_EPS = 0.00001 # used to make the calculation of SNNL more stable - - def __init__(self, - model, - temperature=100., - layer_names=None, - factor=-10., - optimize_temperature=True, - cos_distance=False): - """Constructor. - :param model: Model instance, the model on which to apply the loss. - :param temperature: Temperature used for SNNL. - :layer_names: The names of the layers at which to calculate SNNL. - If not provided, then SNNL is applied to each internal layer. - :factor: The balance factor between SNNL and ross Entropy. If factor is - negative, then SNNL will be maximized. - :optimize_temperature: Optimize temperature at each calculation to minimize - the loss. This makes the loss more stable. - :cos_distance: Use cosine distance when calculating SNNL. - """ - CrossEntropy.__init__(self, model, smoothing=0.) - self.temperature = temperature - self.factor = factor - self.optimize_temperature = optimize_temperature - self.cos_distance = cos_distance - self.layer_names = layer_names - if not layer_names: - # omit the final layer, the classification layer - self.layer_names = model.get_layer_names()[:-1] - - @staticmethod - def pairwise_euclid_distance(A, B): - """Pairwise Euclidean distance between two matrices. - :param A: a matrix. - :param B: a matrix. - - :returns: A tensor for the pairwise Euclidean between A and B. - """ - batchA = tf.shape(A)[0] - batchB = tf.shape(B)[0] - - sqr_norm_A = tf.reshape(tf.reduce_sum(tf.pow(A, 2), 1), [1, batchA]) - sqr_norm_B = tf.reshape(tf.reduce_sum(tf.pow(B, 2), 1), [batchB, 1]) - inner_prod = tf.matmul(B, A, transpose_b=True) - - tile_1 = tf.tile(sqr_norm_A, [batchB, 1]) - tile_2 = tf.tile(sqr_norm_B, [1, batchA]) - return (tile_1 + tile_2 - 2 * inner_prod) - - @staticmethod - def pairwise_cos_distance(A, B): - """Pairwise cosine distance between two matrices. - :param A: a matrix. - :param B: a matrix. - - :returns: A tensor for the pairwise cosine between A and B. - """ - normalized_A = tf.nn.l2_normalize(A, dim=1) - normalized_B = tf.nn.l2_normalize(B, dim=1) - prod = tf.matmul(normalized_A, normalized_B, adjoint_b=True) - return 1 - prod - - @staticmethod - def fits(A, B, temp, cos_distance): - """Exponentiated pairwise distance between each element of A and - all those of B. - :param A: a matrix. - :param B: a matrix. - :param temp: Temperature - :cos_distance: Boolean for using cosine or Euclidean distance. - - :returns: A tensor for the exponentiated pairwise distance between - each element and A and all those of B. - """ - if cos_distance: - distance_matrix = SNNLCrossEntropy.pairwise_cos_distance(A, B) - else: - distance_matrix = SNNLCrossEntropy.pairwise_euclid_distance(A, B) - return tf.exp(-(distance_matrix / temp)) - - @staticmethod - def pick_probability(x, temp, cos_distance): - """Row normalized exponentiated pairwise distance between all the elements - of x. Conceptualized as the probability of sampling a neighbor point for - every element of x, proportional to the distance between the points. - :param x: a matrix - :param temp: Temperature - :cos_distance: Boolean for using cosine or euclidean distance - - :returns: A tensor for the row normalized exponentiated pairwise distance - between all the elements of x. - """ - f = SNNLCrossEntropy.fits( - x, x, temp, cos_distance) - tf.eye(tf.shape(x)[0]) - return f / ( - SNNLCrossEntropy.STABILITY_EPS + tf.expand_dims(tf.reduce_sum(f, 1), 1)) - - @staticmethod - def same_label_mask(y, y2): - """Masking matrix such that element i,j is 1 iff y[i] == y2[i]. - :param y: a list of labels - :param y2: a list of labels - - :returns: A tensor for the masking matrix. - """ - return tf.cast(tf.squeeze(tf.equal(y, tf.expand_dims(y2, 1))), tf.float32) - - @staticmethod - def masked_pick_probability(x, y, temp, cos_distance): - """The pairwise sampling probabilities for the elements of x for neighbor - points which share labels. - :param x: a matrix - :param y: a list of labels for each element of x - :param temp: Temperature - :cos_distance: Boolean for using cosine or Euclidean distance - - :returns: A tensor for the pairwise sampling probabilities. - """ - return SNNLCrossEntropy.pick_probability(x, temp, cos_distance) * \ - SNNLCrossEntropy.same_label_mask(y, y) - - @staticmethod - def SNNL(x, y, temp, cos_distance): - """Soft Nearest Neighbor Loss - :param x: a matrix. - :param y: a list of labels for each element of x. - :param temp: Temperature. - :cos_distance: Boolean for using cosine or Euclidean distance. - - :returns: A tensor for the Soft Nearest Neighbor Loss of the points - in x with labels y. - """ - summed_masked_pick_prob = tf.reduce_sum( - SNNLCrossEntropy.masked_pick_probability(x, y, temp, cos_distance), 1) - return tf.reduce_mean( - -tf.log(SNNLCrossEntropy.STABILITY_EPS + summed_masked_pick_prob)) - - @staticmethod - def optimized_temp_SNNL(x, y, initial_temp, cos_distance): - """The optimized variant of Soft Nearest Neighbor Loss. Every time this - tensor is evaluated, the temperature is optimized to minimize the loss - value, this results in more numerically stable calculations of the SNNL. - :param x: a matrix. - :param y: a list of labels for each element of x. - :param initial_temp: Temperature. - :cos_distance: Boolean for using cosine or Euclidean distance. - - :returns: A tensor for the Soft Nearest Neighbor Loss of the points - in x with labels y, optimized for temperature. - """ - t = tf.Variable(1, dtype=tf.float32, trainable=False, name="temp") - - def inverse_temp(t): - # pylint: disable=missing-docstring - # we use inverse_temp because it was observed to be more stable when optimizing. - return tf.div(initial_temp, t) - - ent_loss = SNNLCrossEntropy.SNNL(x, y, inverse_temp(t), cos_distance) - updated_t = tf.assign(t, tf.subtract(t, 0.1*tf.gradients(ent_loss, t)[0])) - inverse_t = inverse_temp(updated_t) - return SNNLCrossEntropy.SNNL(x, y, inverse_t, cos_distance) - - def fprop(self, x, y, **kwargs): - cross_entropy = CrossEntropy.fprop(self, x, y, **kwargs) - self.layers = [self.model.get_layer(x, name) for name in self.layer_names] - loss_fn = self.SNNL - if self.optimize_temperature: - loss_fn = self.optimized_temp_SNNL - layers_SNNL = [loss_fn(tf.layers.flatten(layer), - tf.argmax(y, axis=1), - self.temperature, - self.cos_distance) - for layer in self.layers] - return cross_entropy + self.factor * tf.add_n(layers_SNNL) diff --git a/cleverhans/model.py b/cleverhans/model.py deleted file mode 100644 index cacfe44fc..000000000 --- a/cleverhans/model.py +++ /dev/null @@ -1,288 +0,0 @@ -""" -The Model class and related functionality. -""" -from abc import ABCMeta -import warnings - -import tensorflow as tf - -from cleverhans import utils_tf - - -class Model(object): - """ - An abstract interface for model wrappers that exposes model symbols - needed for making an attack. This abstraction removes the dependency on - any specific neural network package (e.g. Keras) from the core - code of CleverHans. It can also simplify exposing the hidden features of a - model when a specific package does not directly expose them. - """ - __metaclass__ = ABCMeta - O_LOGITS, O_PROBS, O_FEATURES = 'logits probs features'.split() - - def __init__(self, scope=None, nb_classes=None, hparams=None, - needs_dummy_fprop=False): - """ - Constructor. - :param scope: str, the name of model. - :param nb_classes: integer, the number of classes. - :param hparams: dict, hyper-parameters for the model. - :needs_dummy_fprop: bool, if True the model's parameters are not - created until fprop is called. - """ - self.scope = scope or self.__class__.__name__ - self.nb_classes = nb_classes - self.hparams = hparams or {} - self.needs_dummy_fprop = needs_dummy_fprop - - def __call__(self, *args, **kwargs): - """ - For compatibility with functions used as model definitions (taking - an input tensor and returning the tensor giving the output - of the model on that input). - """ - - warnings.warn("Model.__call__ is deprecated. " - "The call is ambiguous as to whether the output should " - "be logits or probabilities, and getting the wrong one " - "can cause serious problems. " - "The output actually is probabilities, which are a very " - "dangerous thing to use as part of any interface for " - "cleverhans, because softmax probabilities are prone " - "to gradient masking." - "On or after 2019-04-24, this method will change to raise " - "an exception explaining why Model.__call__ should not be " - "used.") - - return self.get_probs(*args, **kwargs) - - def get_logits(self, x, **kwargs): - """ - :param x: A symbolic representation (Tensor) of the network input - :return: A symbolic representation (Tensor) of the output logits - (i.e., the values fed as inputs to the softmax layer). - """ - outputs = self.fprop(x, **kwargs) - if self.O_LOGITS in outputs: - return outputs[self.O_LOGITS] - raise NotImplementedError(str(type(self)) + "must implement `get_logits`" - " or must define a " + self.O_LOGITS + - " output in `fprop`") - - def get_predicted_class(self, x, **kwargs): - """ - :param x: A symbolic representation (Tensor) of the network input - :return: A symbolic representation (Tensor) of the predicted label - """ - return tf.argmax(self.get_logits(x, **kwargs), axis=1) - - def get_probs(self, x, **kwargs): - """ - :param x: A symbolic representation (Tensor) of the network input - :return: A symbolic representation (Tensor) of the output - probabilities (i.e., the output values produced by the softmax layer). - """ - d = self.fprop(x, **kwargs) - if self.O_PROBS in d: - output = d[self.O_PROBS] - min_prob = tf.reduce_min(output) - max_prob = tf.reduce_max(output) - asserts = [utils_tf.assert_greater_equal(min_prob, - tf.cast(0., min_prob.dtype)), - utils_tf.assert_less_equal(max_prob, - tf.cast(1., min_prob.dtype))] - with tf.control_dependencies(asserts): - output = tf.identity(output) - return output - elif self.O_LOGITS in d: - return tf.nn.softmax(logits=d[self.O_LOGITS]) - else: - raise ValueError('Cannot find probs or logits.') - - def fprop(self, x, **kwargs): - """ - Forward propagation to compute the model outputs. - :param x: A symbolic representation of the network input - :return: A dictionary mapping layer names to the symbolic - representation of their output. - """ - raise NotImplementedError('`fprop` not implemented.') - - def get_params(self): - """ - Provides access to the model's parameters. - :return: A list of all Variables defining the model parameters. - """ - - if hasattr(self, 'params'): - return list(self.params) - - # Catch eager execution and assert function overload. - try: - if tf.executing_eagerly(): - raise NotImplementedError("For Eager execution - get_params " - "must be overridden.") - except AttributeError: - pass - - # For graph-based execution - scope_vars = tf.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES, - self.scope + "/") - - if len(scope_vars) == 0: - self.make_params() - scope_vars = tf.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES, - self.scope + "/") - assert len(scope_vars) > 0 - - # Make sure no parameters have been added or removed - if hasattr(self, "num_params"): - if self.num_params != len(scope_vars): - print("Scope: ", self.scope) - print("Expected " + str(self.num_params) + " variables") - print("Got " + str(len(scope_vars))) - for var in scope_vars: - print("\t" + str(var)) - assert False - else: - self.num_params = len(scope_vars) - - return scope_vars - - def make_params(self): - """ - Create all Variables to be returned later by get_params. - By default this is a no-op. - Models that need their fprop to be called for their params to be - created can set `needs_dummy_fprop=True` in the constructor. - """ - - if self.needs_dummy_fprop: - if hasattr(self, "_dummy_input"): - return - self._dummy_input = self.make_input_placeholder() - self.fprop(self._dummy_input) - - def get_layer_names(self): - """Return the list of exposed layers for this model.""" - raise NotImplementedError - - def get_layer(self, x, layer, **kwargs): - """Return a layer output. - :param x: tensor, the input to the network. - :param layer: str, the name of the layer to compute. - :param **kwargs: dict, extra optional params to pass to self.fprop. - :return: the content of layer `layer` - """ - return self.fprop(x, **kwargs)[layer] - - def make_input_placeholder(self): - """Create and return a placeholder representing an input to the model. - - This method should respect context managers (e.g. "with tf.device") - and should not just return a reference to a single pre-created - placeholder. - """ - - raise NotImplementedError(str(type(self)) + " does not implement " - "make_input_placeholder") - - def make_label_placeholder(self): - """Create and return a placeholder representing class labels. - - This method should respect context managers (e.g. "with tf.device") - and should not just return a reference to a single pre-created - placeholder. - """ - - raise NotImplementedError(str(type(self)) + " does not implement " - "make_label_placeholder") - - def __hash__(self): - return hash(id(self)) - - def __eq__(self, other): - return self is other - - -class CallableModelWrapper(Model): - """A wrapper that turns a callable into a valid Model""" - - def __init__(self, callable_fn, output_layer): - """ - Wrap a callable function that takes a tensor as input and returns - a tensor as output with the given layer name. - :param callable_fn: The callable function taking a tensor and - returning a given layer as output. - :param output_layer: A string of the output layer returned by the - function. (Usually either "probs" or "logits".) - """ - - super(CallableModelWrapper, self).__init__() - self.output_layer = output_layer - self.callable_fn = callable_fn - - def fprop(self, x, **kwargs): - output = self.callable_fn(x, **kwargs) - - # Do some sanity checking to reduce the chance that probs are used - # as logits accidentally or vice versa - if self.output_layer == 'probs': - assert output.op.type == "Softmax" - min_prob = tf.reduce_min(output) - max_prob = tf.reduce_max(output) - asserts = [utils_tf.assert_greater_equal(min_prob, - tf.cast(0., min_prob.dtype)), - utils_tf.assert_less_equal(max_prob, - tf.cast(1., max_prob.dtype))] - with tf.control_dependencies(asserts): - output = tf.identity(output) - elif self.output_layer == 'logits': - assert output.op.type != 'Softmax' - - return {self.output_layer: output} - -def wrapper_warning(): - """ - Issue a deprecation warning. Used in multiple places that implemented - attacks by automatically wrapping a user-supplied callable with a - CallableModelWrapper with output_layer="probs". - Using "probs" as any part of the attack interface is dangerous. - We can't just change output_layer to logits because: - - that would be a silent interface change. We'd have no way of detecting - code that still means to use probs. Note that we can't just check whether - the final output op is a softmax---for example, Inception puts a reshape - after the softmax. - - automatically wrapping user-supplied callables with output_layer='logits' - is even worse, see `wrapper_warning_logits` - Note: this function will be removed at the same time as the code that - calls it. - """ - warnings.warn("Passing a callable is deprecated, because using" - " probabilities is dangerous. It has a high risk " - " of causing gradient masking due to loss of precision " - " in the softmax op. Passing a callable rather than a " - " Model subclass will become an error on or after " - " 2019-04-24.") - -def wrapper_warning_logits(): - """ - Issue a deprecation warning. Used in multiple places that implemented - attacks by automatically wrapping a user-supplied callable with a - CallableModelWrapper with output_layer="logits". - This is dangerous because it is under-the-hood automagic that the user - may not realize has been invoked for them. If they pass a callable - that actually outputs probs, the probs will be treated as logits, - resulting in an incorrect cross-entropy loss and severe gradient - masking. - """ - warnings.warn("Passing a callable is deprecated, because it runs the " - "risk of accidentally using probabilities in the place " - "of logits. Please switch to passing a Model subclass " - "so that you clearly specify which values are the logits. " - "Passing a callable rather than a Model subclass will become " - "an error on or after 2019-04-24.") - - -class NoSuchLayerError(ValueError): - """Raised when a layer that does not exist is requested.""" diff --git a/cleverhans/model_zoo/all_convolutional.py b/cleverhans/model_zoo/all_convolutional.py deleted file mode 100644 index a568daadf..000000000 --- a/cleverhans/model_zoo/all_convolutional.py +++ /dev/null @@ -1,46 +0,0 @@ -"""Extremely simple model where all parameters are from convolutions. -""" - -import math -import tensorflow as tf - -from cleverhans import initializers -from cleverhans.serial import NoRefModel - - -class ModelAllConvolutional(NoRefModel): - """ - A simple model that uses only convolution and downsampling---no batch norm or other techniques that can complicate - adversarial training. - """ - def __init__(self, scope, nb_classes, nb_filters, input_shape, **kwargs): - del kwargs - NoRefModel.__init__(self, scope, nb_classes, locals()) - self.nb_filters = nb_filters - self.input_shape = input_shape - - # Do a dummy run of fprop to create the variables from the start - self.fprop(tf.placeholder(tf.float32, [32] + input_shape)) - # Put a reference to the params in self so that the params get pickled - self.params = self.get_params() - - def fprop(self, x, **kwargs): - del kwargs - conv_args = dict( - activation=tf.nn.leaky_relu, - kernel_initializer=initializers.HeReLuNormalInitializer, - kernel_size=3, - padding='same') - y = x - - with tf.variable_scope(self.scope, reuse=tf.AUTO_REUSE): - log_resolution = int(round( - math.log(self.input_shape[0]) / math.log(2))) - for scale in range(log_resolution - 2): - y = tf.layers.conv2d(y, self.nb_filters << scale, **conv_args) - y = tf.layers.conv2d(y, self.nb_filters << (scale + 1), **conv_args) - y = tf.layers.average_pooling2d(y, 2, 2) - y = tf.layers.conv2d(y, self.nb_classes, **conv_args) - logits = tf.reduce_mean(y, [1, 2]) - return {self.O_LOGITS: logits, - self.O_PROBS: tf.nn.softmax(logits=logits)} diff --git a/cleverhans/model_zoo/basic_cnn.py b/cleverhans/model_zoo/basic_cnn.py deleted file mode 100644 index 894840025..000000000 --- a/cleverhans/model_zoo/basic_cnn.py +++ /dev/null @@ -1,43 +0,0 @@ -""" -A pure TensorFlow implementation of a convolutional neural network. -""" -# pylint: disable=missing-docstring -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - -import functools - -import tensorflow as tf - -from cleverhans import initializers -from cleverhans.model import Model - - -class ModelBasicCNN(Model): - def __init__(self, scope, nb_classes, nb_filters, **kwargs): - del kwargs - Model.__init__(self, scope, nb_classes, locals()) - self.nb_filters = nb_filters - - # Do a dummy run of fprop to make sure the variables are created from - # the start - self.fprop(tf.placeholder(tf.float32, [128, 28, 28, 1])) - # Put a reference to the params in self so that the params get pickled - self.params = self.get_params() - - def fprop(self, x, **kwargs): - del kwargs - my_conv = functools.partial( - tf.layers.conv2d, activation=tf.nn.relu, - kernel_initializer=initializers.HeReLuNormalInitializer) - with tf.variable_scope(self.scope, reuse=tf.AUTO_REUSE): - y = my_conv(x, self.nb_filters, 8, strides=2, padding='same') - y = my_conv(y, 2 * self.nb_filters, 6, strides=2, padding='valid') - y = my_conv(y, 2 * self.nb_filters, 5, strides=1, padding='valid') - logits = tf.layers.dense( - tf.layers.flatten(y), self.nb_classes, - kernel_initializer=initializers.HeReLuNormalInitializer) - return {self.O_LOGITS: logits, - self.O_PROBS: tf.nn.softmax(logits=logits)} diff --git a/cleverhans/model_zoo/deep_k_nearest_neighbors/dknn.py b/cleverhans/model_zoo/deep_k_nearest_neighbors/dknn.py deleted file mode 100644 index 4582e8cb7..000000000 --- a/cleverhans/model_zoo/deep_k_nearest_neighbors/dknn.py +++ /dev/null @@ -1,595 +0,0 @@ -""" -This code reproduces the MNIST results from the paper -Deep k-Nearest Neighbors: Towards Confident, Interpretable and Robust Deep Learning -https://arxiv.org/abs/1803.04765 - -The LSH backend used in the paper is FALCONN. This script also demonstrates -how to use an alternative backend called FAISS. -""" -# pylint: disable=missing-docstring -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - -import copy -import os -from bisect import bisect_left -import matplotlib -import matplotlib.pyplot as plt -import numpy as np -from six.moves import xrange -import enum -import tensorflow as tf -from cleverhans.attacks import FastGradientMethod -from cleverhans.loss import CrossEntropy -from cleverhans.dataset import MNIST -from cleverhans.model import Model -from cleverhans.picklable_model import MLP, Conv2D, ReLU, Flatten, Linear, Softmax -from cleverhans.train import train -from cleverhans.utils_tf import batch_eval, model_eval - -if 'DISPLAY' not in os.environ: - matplotlib.use('Agg') - - -FLAGS = tf.flags.FLAGS - - -def make_basic_picklable_cnn(nb_filters=64, nb_classes=10, - input_shape=(None, 28, 28, 1)): - """The model for the picklable models tutorial. - """ - layers = [Conv2D(nb_filters, (8, 8), (2, 2), "SAME"), - ReLU(), - Conv2D(nb_filters * 2, (6, 6), (2, 2), "VALID"), - ReLU(), - Conv2D(nb_filters * 2, (5, 5), (1, 1), "VALID"), - ReLU(), - Flatten(), - Linear(nb_classes), - Softmax()] - model = MLP(layers, input_shape) - return model - - -class NearestNeighbor: - class BACKEND(enum.Enum): - FALCONN = 1 - FAISS = 2 - - def __init__( - self, - backend, - dimension, - neighbors, - number_bits, - nb_tables=None, - ): - assert backend in NearestNeighbor.BACKEND - - self._NEIGHBORS = neighbors - self._BACKEND = backend - - if self._BACKEND is NearestNeighbor.BACKEND.FALCONN: - self._init_falconn( - dimension, - number_bits, - nb_tables - ) - elif self._BACKEND is NearestNeighbor.BACKEND.FAISS: - self._init_faiss( - dimension, - ) - else: - raise NotImplementedError - - def _init_falconn( - self, - dimension, - number_bits, - nb_tables, - ): - import falconn - - assert nb_tables >= self._NEIGHBORS - - # LSH parameters - params_cp = falconn.LSHConstructionParameters() - params_cp.dimension = dimension - params_cp.lsh_family = falconn.LSHFamily.CrossPolytope - params_cp.distance_function = falconn.DistanceFunction.EuclideanSquared - params_cp.l = nb_tables - params_cp.num_rotations = 2 # for dense set it to 1; for sparse data set it to 2 - params_cp.seed = 5721840 - # we want to use all the available threads to set up - params_cp.num_setup_threads = 0 - params_cp.storage_hash_table = falconn.StorageHashTable.BitPackedFlatHashTable - - # we build number_bits-bit hashes so that each table has - # 2^number_bits bins; a rule of thumb is to have the number - # of bins be the same order of magnitude as the number of data points - falconn.compute_number_of_hash_functions(number_bits, params_cp) - self._falconn_table = falconn.LSHIndex(params_cp) - self._falconn_query_object = None - self._FALCONN_NB_TABLES = nb_tables - - def _init_faiss( - self, - dimension, - ): - import faiss - - res = faiss.StandardGpuResources() - - self._faiss_index = faiss.GpuIndexFlatL2( - res, - dimension, - ) - - def _find_knns_falconn(self, x, output): - # Late falconn query_object construction - # Since I suppose there might be an error - # if table.setup() will be called after - if self._falconn_query_object is None: - self._falconn_query_object = self._falconn_table.construct_query_object() - self._falconn_query_object.set_num_probes( - self._FALCONN_NB_TABLES - ) - - missing_indices = np.zeros(output.shape, dtype=np.bool) - - for i in range(x.shape[0]): - query_res = self._falconn_query_object.find_k_nearest_neighbors( - x[i], - self._NEIGHBORS - ) - try: - output[i, :] = query_res - except: # pylint: disable-msg=W0702 - # mark missing indices - missing_indices[i, len(query_res):] = True - - output[i, :len(query_res)] = query_res - - return missing_indices - - def _find_knns_faiss(self, x, output): - neighbor_distance, neighbor_index = self._faiss_index.search( - x, - self._NEIGHBORS - ) - - missing_indices = neighbor_distance == -1 - - d1 = neighbor_index.reshape(-1) - - output.reshape(-1)[ - np.logical_not(missing_indices.flatten()) - ] = d1[ - np.logical_not(missing_indices.flatten()) - ] - - return missing_indices - - def add(self, x): - if self._BACKEND is NearestNeighbor.BACKEND.FALCONN: - self._falconn_table.setup(x) - elif self._BACKEND is NearestNeighbor.BACKEND.FAISS: - self._faiss_index.add(x) - else: - raise NotImplementedError - - def find_knns(self, x, output): - if self._BACKEND is NearestNeighbor.BACKEND.FALCONN: - return self._find_knns_falconn(x, output) - elif self._BACKEND is NearestNeighbor.BACKEND.FAISS: - return self._find_knns_faiss(x, output) - else: - raise NotImplementedError - - -class DkNNModel(Model): - def __init__(self, neighbors, layers, get_activations, train_data, train_labels, - nb_classes, scope=None, nb_tables=200, number_bits=17): - """ - Implements the DkNN algorithm. See https://arxiv.org/abs/1803.04765 for more details. - - :param neighbors: number of neighbors to find per layer. - :param layers: a list of layer names to include in the DkNN. - :param get_activations: a callable that takes a np array and a layer name and returns its activations on the data. - :param train_data: a np array of training data. - :param train_labels: a np vector of training labels. - :param nb_classes: the number of classes in the task. - :param scope: a TF scope that was used to create the underlying model. - :param nb_tables: number of tables used by FALCONN to perform locality-sensitive hashing. - :param number_bits: number of hash bits used by LSH. - """ - super(DkNNModel, self).__init__(nb_classes=nb_classes, scope=scope) - self.neighbors = neighbors - self.nb_tables = nb_tables - self.layers = layers - self.get_activations = get_activations - self.nb_cali = -1 - self.calibrated = False - self.number_bits = number_bits - - # Compute training data activations - self.nb_train = train_labels.shape[0] - assert self.nb_train == train_data.shape[0] - self.train_activations = get_activations(train_data) - self.train_labels = train_labels - - # Build locality-sensitive hashing tables for training representations - self.train_activations_lsh = copy.copy(self.train_activations) - self.init_lsh() - - def init_lsh(self): - """ - Initializes locality-sensitive hashing with FALCONN to find nearest neighbors in training data. - """ - self.query_objects = { - } # contains the object that can be queried to find nearest neighbors at each layer. - # mean of training data representation per layer (that needs to be substracted before - # NearestNeighbor). - self.centers = {} - for layer in self.layers: - # Normalize all the lenghts, since we care about the cosine similarity. - self.train_activations_lsh[layer] /= np.linalg.norm( - self.train_activations_lsh[layer], axis=1).reshape(-1, 1) - - # Center the dataset and the queries: this improves the performance of LSH quite a bit. - center = np.mean(self.train_activations_lsh[layer], axis=0) - self.train_activations_lsh[layer] -= center - self.centers[layer] = center - - print('Constructing the NearestNeighbor table') - self.query_objects[layer] = NearestNeighbor( - backend=FLAGS.nearest_neighbor_backend, - dimension=self.train_activations_lsh[layer].shape[1], - number_bits=self.number_bits, - neighbors=self.neighbors, - nb_tables=self.nb_tables - ) - - self.query_objects[layer].add(self.train_activations_lsh[layer]) - - def find_train_knns(self, data_activations): - """ - Given a data_activation dictionary that contains a np array with activations for each layer, - find the knns in the training data. - """ - knns_ind = {} - knns_labels = {} - - for layer in self.layers: - # Pre-process representations of data to normalize and remove training data mean. - data_activations_layer = copy.copy(data_activations[layer]) - nb_data = data_activations_layer.shape[0] - data_activations_layer /= np.linalg.norm( - data_activations_layer, axis=1).reshape(-1, 1) - data_activations_layer -= self.centers[layer] - - # Use FALCONN to find indices of nearest neighbors in training data. - knns_ind[layer] = np.zeros( - (data_activations_layer.shape[0], self.neighbors), dtype=np.int32) - knn_errors = 0 - - knn_missing_indices = self.query_objects[layer].find_knns( - data_activations_layer, - knns_ind[layer], - ) - - knn_errors += knn_missing_indices.flatten().sum() - - # Find labels of neighbors found in the training data. - knns_labels[layer] = np.zeros((nb_data, self.neighbors), dtype=np.int32) - - knns_labels[layer].reshape(-1)[ - np.logical_not( - knn_missing_indices.flatten() - ) - ] = self.train_labels[ - knns_ind[layer].reshape(-1)[ - np.logical_not( - knn_missing_indices.flatten() - ) - ] - ] - - return knns_ind, knns_labels - - def nonconformity(self, knns_labels): - """ - Given an dictionary of nb_data x nb_classes dimension, compute the nonconformity of - each candidate label for each data point: i.e. the number of knns whose label is - different from the candidate label. - """ - nb_data = knns_labels[self.layers[0]].shape[0] - knns_not_in_class = np.zeros((nb_data, self.nb_classes), dtype=np.int32) - for i in range(nb_data): - # Compute number of nearest neighbors per class - knns_in_class = np.zeros( - (len(self.layers), self.nb_classes), dtype=np.int32) - for layer_id, layer in enumerate(self.layers): - knns_in_class[layer_id, :] = np.bincount( - knns_labels[layer][i], minlength=self.nb_classes) - - # Compute number of knns in other class than class_id - for class_id in range(self.nb_classes): - knns_not_in_class[i, class_id] = np.sum( - knns_in_class) - np.sum(knns_in_class[:, class_id]) - return knns_not_in_class - - def preds_conf_cred(self, knns_not_in_class): - """ - Given an array of nb_data x nb_classes dimensions, use conformal prediction to compute - the DkNN's prediction, confidence and credibility. - """ - nb_data = knns_not_in_class.shape[0] - preds_knn = np.zeros(nb_data, dtype=np.int32) - confs = np.zeros((nb_data, self.nb_classes), dtype=np.float32) - creds = np.zeros((nb_data, self.nb_classes), dtype=np.float32) - - for i in range(nb_data): - # p-value of test input for each class - p_value = np.zeros(self.nb_classes, dtype=np.float32) - - for class_id in range(self.nb_classes): - # p-value of (test point, candidate label) - p_value[class_id] = (float(self.nb_cali) - bisect_left( - self.cali_nonconformity, knns_not_in_class[i, class_id])) / float(self.nb_cali) - - preds_knn[i] = np.argmax(p_value) - confs[i, preds_knn[i]] = 1. - np.sort(p_value)[-2] - creds[i, preds_knn[i]] = p_value[preds_knn[i]] - return preds_knn, confs, creds - - def fprop_np(self, data_np): - """ - Performs a forward pass through the DkNN on an numpy array of data. - """ - if not self.calibrated: - raise ValueError( - "DkNN needs to be calibrated by calling DkNNModel.calibrate method once before inferring.") - data_activations = self.get_activations(data_np) - _, knns_labels = self.find_train_knns(data_activations) - knns_not_in_class = self.nonconformity(knns_labels) - _, _, creds = self.preds_conf_cred(knns_not_in_class) - return creds - - def fprop(self, x): - """ - Performs a forward pass through the DkNN on a TF tensor by wrapping - the fprop_np method. - """ - logits = tf.py_func(self.fprop_np, [x], tf.float32) - return {self.O_LOGITS: logits} - - def calibrate(self, cali_data, cali_labels): - """ - Runs the DkNN on holdout data to calibrate the credibility metric. - :param cali_data: np array of calibration data. - :param cali_labels: np vector of calibration labels. - """ - self.nb_cali = cali_labels.shape[0] - self.cali_activations = self.get_activations(cali_data) - self.cali_labels = cali_labels - - print("Starting calibration of DkNN.") - cali_knns_ind, cali_knns_labels = self.find_train_knns( - self.cali_activations) - assert all([v.shape == (self.nb_cali, self.neighbors) - for v in cali_knns_ind.values()]) - assert all([v.shape == (self.nb_cali, self.neighbors) - for v in cali_knns_labels.values()]) - - cali_knns_not_in_class = self.nonconformity(cali_knns_labels) - cali_knns_not_in_l = np.zeros(self.nb_cali, dtype=np.int32) - for i in range(self.nb_cali): - cali_knns_not_in_l[i] = cali_knns_not_in_class[i, cali_labels[i]] - cali_knns_not_in_l_sorted = np.sort(cali_knns_not_in_l) - self.cali_nonconformity = np.trim_zeros(cali_knns_not_in_l_sorted, trim='f') - self.nb_cali = self.cali_nonconformity.shape[0] - self.calibrated = True - print("DkNN calibration complete.") - - -def plot_reliability_diagram(confidence, labels, filepath): - """ - Takes in confidence values for predictions and correct - labels for the data, plots a reliability diagram. - :param confidence: nb_samples x nb_classes (e.g., output of softmax) - :param labels: vector of nb_samples - :param filepath: where to save the diagram - :return: - """ - assert len(confidence.shape) == 2 - assert len(labels.shape) == 1 - assert confidence.shape[0] == labels.shape[0] - print('Saving reliability diagram at: ' + str(filepath)) - if confidence.max() <= 1.: - # confidence array is output of softmax - bins_start = [b / 10. for b in xrange(0, 10)] - bins_end = [b / 10. for b in xrange(1, 11)] - bins_center = [(b + .5) / 10. for b in xrange(0, 10)] - preds_conf = np.max(confidence, axis=1) - preds_l = np.argmax(confidence, axis=1) - else: - raise ValueError('Confidence values go above 1.') - - print(preds_conf.shape, preds_l.shape) - - # Create var for reliability diagram - # Will contain mean accuracies for each bin - reliability_diag = [] - num_points = [] # keeps the number of points in each bar - - # Find average accuracy per confidence bin - for bin_start, bin_end in zip(bins_start, bins_end): - above = preds_conf >= bin_start - if bin_end == 1.: - below = preds_conf <= bin_end - else: - below = preds_conf < bin_end - mask = np.multiply(above, below) - num_points.append(np.sum(mask)) - bin_mean_acc = max(0, np.mean(preds_l[mask] == labels[mask])) - reliability_diag.append(bin_mean_acc) - - # Plot diagram - assert len(reliability_diag) == len(bins_center) - print(reliability_diag) - print(bins_center) - print(num_points) - fig, ax1 = plt.subplots() - _ = ax1.bar(bins_center, reliability_diag, width=.1, alpha=0.8) - plt.xlim([0, 1.]) - ax1.set_ylim([0, 1.]) - - ax2 = ax1.twinx() - print(sum(num_points)) - ax2.plot(bins_center, num_points, color='r', linestyle='-', linewidth=7.0) - ax2.set_ylabel('Number of points in the data', fontsize=16, color='r') - - if len(np.argwhere(confidence[0] != 0.)) == 1: - # This is a DkNN diagram - ax1.set_xlabel('Prediction Credibility', fontsize=16) - else: - # This is a softmax diagram - ax1.set_xlabel('Prediction Confidence', fontsize=16) - ax1.set_ylabel('Prediction Accuracy', fontsize=16) - ax1.tick_params(axis='both', labelsize=14) - ax2.tick_params(axis='both', labelsize=14, colors='r') - fig.tight_layout() - plt.savefig(filepath, bbox_inches='tight') - - -def get_tensorflow_session(): - gpu_options = tf.GPUOptions() - gpu_options.per_process_gpu_memory_fraction=FLAGS.tensorflow_gpu_memory_fraction - sess = tf.Session( - config=tf.ConfigProto( - gpu_options=gpu_options - ) - ) - - return sess - - -def dknn_tutorial(): - # Get MNIST data. - mnist = MNIST() - x_train, y_train = mnist.get_set('train') - x_test, y_test = mnist.get_set('test') - - # Use Image Parameters. - img_rows, img_cols, nchannels = x_train.shape[1:4] - nb_classes = y_train.shape[1] - - with get_tensorflow_session() as sess: - with tf.variable_scope('dknn'): - # Define input TF placeholder. - x = tf.placeholder(tf.float32, shape=( - None, img_rows, img_cols, nchannels)) - y = tf.placeholder(tf.float32, shape=(None, nb_classes)) - - # Define a model. - model = make_basic_picklable_cnn() - preds = model.get_logits(x) - loss = CrossEntropy(model, smoothing=0.) - - # Define the test set accuracy evaluation. - def evaluate(): - acc = model_eval(sess, x, y, preds, x_test, y_test, - args={'batch_size': FLAGS.batch_size}) - print('Test accuracy on test examples: %0.4f' % acc) - - # Train the model - train_params = {'nb_epochs': FLAGS.nb_epochs, - 'batch_size': FLAGS.batch_size, 'learning_rate': FLAGS.lr} - train(sess, loss, x_train, y_train, evaluate=evaluate, - args=train_params, var_list=model.get_params()) - - # Define callable that returns a dictionary of all activations for a dataset - def get_activations(data): - data_activations = {} - for layer in layers: - layer_sym = tf.layers.flatten(model.get_layer(x, layer)) - data_activations[layer] = batch_eval(sess, [x], [layer_sym], [data], - args={'batch_size': FLAGS.batch_size})[0] - return data_activations - - # Use a holdout of the test set to simulate calibration data for the DkNN. - train_data = x_train - train_labels = np.argmax(y_train, axis=1) - cali_data = x_test[:FLAGS.nb_cali] - y_cali = y_test[:FLAGS.nb_cali] - cali_labels = np.argmax(y_cali, axis=1) - test_data = x_test[FLAGS.nb_cali:] - y_test = y_test[FLAGS.nb_cali:] - - # Extract representations for the training and calibration data at each layer of interest to the DkNN. - layers = ['ReLU1', 'ReLU3', 'ReLU5', 'logits'] - - # Wrap the model into a DkNNModel - dknn = DkNNModel( - FLAGS.neighbors, - layers, - get_activations, - train_data, - train_labels, - nb_classes, - scope='dknn', - number_bits=FLAGS.number_bits - ) - dknn.calibrate(cali_data, cali_labels) - - # Generate adversarial examples - fgsm = FastGradientMethod(model, sess=sess) - attack_params = {'eps': .25, 'clip_min': 0., 'clip_max': 1.} - adv = sess.run(fgsm.generate(x, **attack_params), - feed_dict={x: test_data}) - - # Test the DkNN on clean test data and FGSM test data - for data_in, fname in zip([test_data, adv], ['test', 'adv']): - dknn_preds = dknn.fprop_np(data_in) - print(dknn_preds.shape) - print(np.mean(np.argmax(dknn_preds, axis=1) == np.argmax(y_test, axis=1))) - plot_reliability_diagram(dknn_preds, np.argmax( - y_test, axis=1), '/tmp/dknn_' + fname + '.pdf') - - return True - - -def main(argv=None): - assert dknn_tutorial() - - -if __name__ == '__main__': - tf.flags.DEFINE_integer( - 'number_bits', - 17, - 'number of hash bits used by LSH Index' - ) - tf.flags.DEFINE_float( - 'tensorflow_gpu_memory_fraction', - 0.25, - 'amount of the GPU memory to allocate for a tensorflow Session' - ) - tf.flags.DEFINE_enum_class( - 'nearest_neighbor_backend', - NearestNeighbor.BACKEND.FALCONN, - NearestNeighbor.BACKEND, - 'NearestNeighbor backend' - ) - tf.flags.DEFINE_integer('nb_epochs', 6, 'Number of epochs to train model') - tf.flags.DEFINE_integer('batch_size', 500, 'Size of training batches') - tf.flags.DEFINE_float('lr', 0.001, 'Learning rate for training') - - tf.flags.DEFINE_integer( - 'nb_cali', 750, 'Number of calibration points for the DkNN') - tf.flags.DEFINE_integer( - 'neighbors', 75, 'Number of neighbors per layer for the DkNN') - - tf.app.run() diff --git a/cleverhans/model_zoo/madry_lab_challenges/cifar10_model.py b/cleverhans/model_zoo/madry_lab_challenges/cifar10_model.py deleted file mode 100644 index 228a833c0..000000000 --- a/cleverhans/model_zoo/madry_lab_challenges/cifar10_model.py +++ /dev/null @@ -1,340 +0,0 @@ -"""cleverhans.model.Model implementation of cifar10_challenge.model.Model - -This re-implementation factors variable creation apart from forward -propagation so it is possible to run forward propagation more than once -in the same model. - -based on https://github.com/tensorflow/models/tree/master/resnet -""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import numpy as np -import tensorflow as tf -from cleverhans.serial import NoRefModel - - -class Layer(object): - - def get_output_shape(self): - return self.output_shape - - -class ResNet(NoRefModel): - """ResNet model.""" - - def __init__(self, layers, input_shape, scope=None): - """ResNet constructor. - - :param layers: a list of layers in CleverHans format - each with set_input_shape() and fprop() methods. - :param input_shape: 4-tuple describing input shape (e.g None, 32, 32, 3) - :param scope: string name of scope for Variables - This works in two ways. - If scope is None, the variables are not put in a scope, and the - model is compatible with Saver.restore from the public downloads - for the CIFAR10 Challenge. - If the scope is a string, then Saver.restore won't work, but the - model functions as a picklable NoRefModels that finds its variables - based on the scope. - """ - super(ResNet, self).__init__(scope, 10, {}, scope is not None) - if scope is None: - before = list(tf.trainable_variables()) - before_vars = list(tf.global_variables()) - self.build(layers, input_shape) - after = list(tf.trainable_variables()) - after_vars = list(tf.global_variables()) - self.params = [param for param in after if param not in before] - self.vars = [var for var in after_vars if var not in before_vars] - else: - with tf.variable_scope(self.scope): - self.build(layers, input_shape) - - def get_vars(self): - if hasattr(self, "vars"): - return self.vars - return super(ResNet, self).get_vars() - - def build(self, layers, input_shape): - self.layer_names = [] - self.layers = layers - self.input_shape = input_shape - if isinstance(layers[-1], Softmax): - layers[-1].name = 'probs' - layers[-2].name = 'logits' - else: - layers[-1].name = 'logits' - for i, layer in enumerate(self.layers): - if hasattr(layer, 'name'): - name = layer.name - else: - name = layer.__class__.__name__ + str(i) - layer.name = name - self.layer_names.append(name) - - layer.set_input_shape(input_shape) - input_shape = layer.get_output_shape() - - def make_input_placeholder(self): - return tf.placeholder(tf.float32, (None, 32, 32, 3)) - - def make_label_placeholder(self): - return tf.placeholder(tf.float32, (None, 10)) - - def fprop(self, x, set_ref=False): - if self.scope is not None: - with tf.variable_scope(self.scope, reuse=tf.AUTO_REUSE): - return self._fprop(x, set_ref) - return self._prop(x, set_ref) - - def _fprop(self, x, set_ref=False): - states = [] - for layer in self.layers: - if set_ref: - layer.ref = x - x = layer.fprop(x) - assert x is not None - states.append(x) - states = dict(zip(self.layer_names, states)) - return states - - def add_internal_summaries(self): - pass - - -def _stride_arr(stride): - """Map a stride scalar to the stride array for tf.nn.conv2d.""" - return [1, stride, stride, 1] - - -class Input(Layer): - - def __init__(self): - pass - - def set_input_shape(self, input_shape): - batch_size, rows, cols, input_channels = input_shape - # assert self.mode == 'train' or self.mode == 'eval' - """Build the core model within the graph.""" - input_shape = list(input_shape) - input_shape[0] = 1 - dummy_batch = tf.zeros(input_shape) - dummy_output = self.fprop(dummy_batch) - output_shape = [int(e) for e in dummy_output.get_shape()] - output_shape[0] = batch_size - self.output_shape = tuple(output_shape) - - def fprop(self, x): - with tf.variable_scope('input', reuse=tf.AUTO_REUSE): - input_standardized = tf.map_fn( - lambda img: tf.image.per_image_standardization(img), x) - return _conv('init_conv', input_standardized, - 3, 3, 16, _stride_arr(1)) - - -class Conv2D(Layer): - - def __init__(self): - pass - - def set_input_shape(self, input_shape): - batch_size, rows, cols, input_channels = input_shape - - # Uncomment the following codes to use w28-10 wide residual network. - # It is more memory efficient than very deep residual network and has - # comparably good performance. - # https://arxiv.org/pdf/1605.07146v1.pdf - input_shape = list(input_shape) - input_shape[0] = 1 - dummy_batch = tf.zeros(input_shape) - dummy_output = self.fprop(dummy_batch) - output_shape = [int(e) for e in dummy_output.get_shape()] - output_shape[0] = batch_size - self.output_shape = tuple(output_shape) - - def fprop(self, x): - - # Update hps.num_residual_units to 9 - strides = [1, 2, 2] - activate_before_residual = [True, False, False] - filters = [16, 160, 320, 640] - res_func = _residual - with tf.variable_scope('unit_1_0', reuse=tf.AUTO_REUSE): - x = res_func(x, filters[0], filters[1], _stride_arr(strides[0]), - activate_before_residual[0]) - for i in range(1, 5): - with tf.variable_scope(('unit_1_%d' % i), reuse=tf.AUTO_REUSE): - x = res_func(x, filters[1], filters[1], - _stride_arr(1), False) - - with tf.variable_scope(('unit_2_0'), reuse=tf.AUTO_REUSE): - x = res_func(x, filters[1], filters[2], _stride_arr(strides[1]), - activate_before_residual[1]) - for i in range(1, 5): - with tf.variable_scope(('unit_2_%d' % i), reuse=tf.AUTO_REUSE): - x = res_func(x, filters[2], filters[2], - _stride_arr(1), False) - - with tf.variable_scope(('unit_3_0'), reuse=tf.AUTO_REUSE): - x = res_func(x, filters[2], filters[3], _stride_arr(strides[2]), - activate_before_residual[2]) - for i in range(1, 5): - with tf.variable_scope(('unit_3_%d' % i), reuse=tf.AUTO_REUSE): - x = res_func(x, filters[3], filters[3], - _stride_arr(1), False) - - with tf.variable_scope(('unit_last'), reuse=tf.AUTO_REUSE): - x = _batch_norm('final_bn', x) - x = _relu(x, 0.1) - x = _global_avg_pool(x) - - return x - - -class Linear(Layer): - - def __init__(self, num_hid): - self.num_hid = num_hid - - def set_input_shape(self, input_shape): - batch_size, dim = input_shape - self.input_shape = [batch_size, dim] - self.dim = dim - self.output_shape = [batch_size, self.num_hid] - self.make_vars() - - def make_vars(self): - with tf.variable_scope('logit', reuse=tf.AUTO_REUSE): - w = tf.get_variable( - 'DW', [self.dim, self.num_hid], - initializer=tf.initializers.variance_scaling( - distribution='uniform')) - b = tf.get_variable('biases', [self.num_hid], - initializer=tf.initializers.constant()) - return w, b - - def fprop(self, x): - w, b = self.make_vars() - return tf.nn.xw_plus_b(x, w, b) - - -def _batch_norm(name, x): - """Batch normalization.""" - with tf.name_scope(name): - return tf.contrib.layers.batch_norm( - inputs=x, - decay=.9, - center=True, - scale=True, - activation_fn=None, - updates_collections=None, - is_training=False) - - -def _residual(x, in_filter, out_filter, stride, - activate_before_residual=False): - """Residual unit with 2 sub layers.""" - if activate_before_residual: - with tf.variable_scope('shared_activation'): - x = _batch_norm('init_bn', x) - x = _relu(x, 0.1) - orig_x = x - else: - with tf.variable_scope('residual_only_activation'): - orig_x = x - x = _batch_norm('init_bn', x) - x = _relu(x, 0.1) - - with tf.variable_scope('sub1'): - x = _conv('conv1', x, 3, in_filter, out_filter, stride) - - with tf.variable_scope('sub2'): - x = _batch_norm('bn2', x) - x = _relu(x, 0.1) - x = _conv('conv2', x, 3, out_filter, out_filter, [1, 1, 1, 1]) - - with tf.variable_scope('sub_add'): - if in_filter != out_filter: - orig_x = tf.nn.avg_pool(orig_x, stride, stride, 'VALID') - orig_x = tf.pad( - orig_x, [[0, 0], [0, 0], - [0, 0], [(out_filter - in_filter) // 2, - (out_filter - in_filter) // 2]]) - x += orig_x - - tf.logging.debug('image after unit %s', x.get_shape()) - return x - - -def _decay(): - """L2 weight decay loss.""" - costs = [] - for var in tf.trainable_variables(): - if var.op.name.find('DW') > 0: - costs.append(tf.nn.l2_loss(var)) - return tf.add_n(costs) - - -def _conv(name, x, filter_size, in_filters, out_filters, strides): - """Convolution.""" - with tf.variable_scope(name, reuse=tf.AUTO_REUSE): - n = filter_size * filter_size * out_filters - kernel = tf.get_variable( - 'DW', [filter_size, filter_size, in_filters, out_filters], - tf.float32, initializer=tf.random_normal_initializer( - stddev=np.sqrt(2.0 / n))) - return tf.nn.conv2d(x, kernel, strides, padding='SAME') - - -def _relu(x, leakiness=0.0): - """Relu, with optional leaky support.""" - return tf.where(tf.less(x, 0.0), leakiness * x, x, name='leaky_relu') - - -def _global_avg_pool(x): - assert x.get_shape().ndims == 4 - return tf.reduce_mean(x, [1, 2]) - - -class Softmax(Layer): - - def __init__(self): - pass - - def set_input_shape(self, shape): - self.input_shape = shape - self.output_shape = shape - - def fprop(self, x): - return tf.nn.softmax(x) - - -class Flatten(Layer): - - def __init__(self): - pass - - def set_input_shape(self, shape): - self.input_shape = shape - output_width = 1 - for factor in shape[1:]: - output_width *= factor - self.output_width = output_width - self.output_shape = [None, output_width] - - def fprop(self, x): - return tf.reshape(x, [-1, self.output_width]) - - -def make_wresnet(nb_classes=10, input_shape=(None, 32, 32, 3), scope=None): - layers = [Input(), - Conv2D(), # the whole ResNet is basically created in this layer - Flatten(), - Linear(nb_classes), - Softmax()] - - model = ResNet(layers, input_shape, scope) - return model diff --git a/cleverhans/model_zoo/madry_lab_challenges/make_cifar10_joblib.py b/cleverhans/model_zoo/madry_lab_challenges/make_cifar10_joblib.py deleted file mode 100644 index 3a8fc800f..000000000 --- a/cleverhans/model_zoo/madry_lab_challenges/make_cifar10_joblib.py +++ /dev/null @@ -1,74 +0,0 @@ -"""Makes a .joblib file containing the trained model - -""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import os -import sys -import time -import numpy as np -import logging - -import tensorflow as tf -from tensorflow.python.platform import app, flags -from cleverhans.utils import set_log_level, to_categorical, safe_zip -from cleverhans.utils_tf import model_eval -from cleverhans import serial -from cleverhans.dataset import CIFAR10, Factory -from cleverhans.model_zoo.madry_lab_challenges.cifar10_model import make_wresnet - - -FLAGS = flags.FLAGS - - -def main(argv): - - model_file = tf.train.latest_checkpoint(FLAGS.checkpoint_dir) - - if model_file is None: - print('No model found') - sys.exit() - - set_log_level(logging.DEBUG) - - sess = tf.Session() - with sess.as_default(): - - model = make_wresnet() - saver = tf.train.Saver() - # Restore the checkpoint - saver.restore(sess, model_file) - SCOPE = "cifar10_challenge" - model2 = make_wresnet(scope=SCOPE) - assert len(model.get_vars()) == len(model2.get_vars()) - found = [False] * len(model2.get_vars()) - for var1 in model.get_vars(): - var1_found = False - var2_name = SCOPE + "/" + var1.name - for idx, var2 in enumerate(model2.get_vars()): - if var2.name == var2_name: - var1_found = True - found[idx] = True - sess.run(tf.assign(var2, var1)) - break - assert var1_found, var1.name - assert all(found) - - model2.dataset_factory = Factory(CIFAR10, {"max_val": 255}) - - serial.save("model.joblib", model2) - - -if __name__ == '__main__': - - cifar10_root = os.environ['CIFAR10_CHALLENGE_DIR'] - default_ckpt_dir = os.path.join(cifar10_root, 'models/model_0') - - - flags.DEFINE_string('checkpoint_dir', default_ckpt_dir, - 'Checkpoint directory to load') - - app.run(main) diff --git a/cleverhans/model_zoo/soft_nearest_neighbor_loss/SNNL_regularized_model.py b/cleverhans/model_zoo/soft_nearest_neighbor_loss/SNNL_regularized_model.py deleted file mode 100644 index 5cee1aa51..000000000 --- a/cleverhans/model_zoo/soft_nearest_neighbor_loss/SNNL_regularized_model.py +++ /dev/null @@ -1,52 +0,0 @@ -""" -A Simple Neural Net to optimize with SNNL and Cross Entropy -""" -# pylint: disable=missing-docstring -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - -import functools - -import tensorflow as tf - -from cleverhans import initializers -from cleverhans.model import Model - - -class ModelBasicCNN(Model): - def __init__(self, scope, nb_classes, nb_filters, **kwargs): - del kwargs - Model.__init__(self, scope, nb_classes, locals()) - self.nb_filters = nb_filters - - self.fprop(self.make_input_placeholder()) - - self.params = self.get_params() - - def make_input_placeholder(self): - return tf.placeholder(tf.float32, [128, 28, 28, 1]) - - def get_layer_names(self): - return ["conv1", "conv2", "conv3", "logits"] - - def fprop(self, x, **kwargs): - del kwargs - my_conv = functools.partial( - tf.layers.conv2d, activation=tf.nn.relu, - kernel_initializer=initializers.HeReLuNormalInitializer) - with tf.variable_scope(self.scope, reuse=tf.AUTO_REUSE): - conv1 = my_conv(x, self.nb_filters, 8, strides=2, padding='same') - conv2 = my_conv(conv1, 2 * self.nb_filters, - 6, strides=2, padding='valid') - conv3 = my_conv(conv2, 2 * self.nb_filters, 5, - strides=1, padding='valid',) - logits = tf.layers.dense( - tf.layers.flatten(conv3), self.nb_classes, - kernel_initializer=initializers.HeReLuNormalInitializer) - return {self.O_LOGITS: logits, - self.O_PROBS: tf.nn.softmax(logits=logits), - "conv1": conv1, - "conv2": conv2, - "conv3": conv3} diff --git a/cleverhans/model_zoo/soft_nearest_neighbor_loss/SNNL_regularized_train.py b/cleverhans/model_zoo/soft_nearest_neighbor_loss/SNNL_regularized_train.py deleted file mode 100644 index eadda84e7..000000000 --- a/cleverhans/model_zoo/soft_nearest_neighbor_loss/SNNL_regularized_train.py +++ /dev/null @@ -1,174 +0,0 @@ -""" -This model shows how to train a model with Soft Nearest Neighbor Loss -regularization. The paper which presents this method can be found at -https://arxiv.org/abs/1902.01889 -""" -# pylint: disable=missing-docstring -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - -import logging -import numpy as np -import tensorflow as tf -import matplotlib.pyplot as plt -from matplotlib.offsetbox import OffsetImage, AnnotationBbox -from sklearn.manifold import TSNE - -from cleverhans.compat import flags -from cleverhans.loss import SNNLCrossEntropy, CrossEntropy -from cleverhans.dataset import MNIST -from cleverhans.utils_tf import model_eval -from cleverhans.train import train -from cleverhans.utils import AccuracyReport, set_log_level -from cleverhans.model_zoo.soft_nearest_neighbor_loss.SNNL_regularized_model import ModelBasicCNN - -FLAGS = flags.FLAGS - -NB_EPOCHS = 6 -BATCH_SIZE = 128 -LEARNING_RATE = 0.001 -NB_FILTERS = 64 -SNNL_FACTOR = -10.0 -OUTPUT_DIR = '/tmp/' - - -def SNNL_example(train_start=0, train_end=60000, test_start=0, - test_end=10000, nb_epochs=NB_EPOCHS, batch_size=BATCH_SIZE, - learning_rate=LEARNING_RATE, - nb_filters=NB_FILTERS, - SNNL_factor=SNNL_FACTOR, - output_dir=OUTPUT_DIR): - """ - A simple model trained to minimize Cross Entropy and Maximize Soft Nearest - Neighbor Loss at each internal layer. This outputs a TSNE of the sign of - the adversarial gradients of a trained model. A model with a negative - SNNL_factor will show little or no class clusters, while a model with a - 0 SNNL_factor will have class clusters in the adversarial gradient direction. - :param train_start: index of first training set example - :param train_end: index of last training set example - :param test_start: index of first test set example - :param test_end: index of last test set example - :param nb_epochs: number of epochs to train model - :param batch_size: size of training batches - :param learning_rate: learning rate for training - :param SNNL_factor: multiplier for Soft Nearest Neighbor Loss - :return: an AccuracyReport object - """ - - # Object used to keep track of (and return) key accuracies - report = AccuracyReport() - - # Set TF random seed to improve reproducibility - tf.set_random_seed(1234) - - # Set logging level to see debug information - set_log_level(logging.DEBUG) - - # Create TF session - sess = tf.Session() - - # Get MNIST data - mnist = MNIST(train_start=train_start, train_end=train_end, - test_start=test_start, test_end=test_end) - x_train, y_train = mnist.get_set('train') - x_test, y_test = mnist.get_set('test') - - # Use Image Parameters - img_rows, img_cols, nchannels = x_train.shape[1:4] - nb_classes = y_train.shape[1] - - # Define input TF placeholder - x = tf.placeholder(tf.float32, shape=(None, img_rows, img_cols, - nchannels)) - y = tf.placeholder(tf.float32, shape=(None, nb_classes)) - - # Train an MNIST model - train_params = { - 'nb_epochs': nb_epochs, - 'batch_size': batch_size, - 'learning_rate': learning_rate - } - eval_params = {'batch_size': batch_size} - rng = np.random.RandomState([2017, 8, 30]) - - def do_eval(preds, x_set, y_set, report_key): - acc = model_eval(sess, x, y, preds, x_set, y_set, args=eval_params) - setattr(report, report_key, acc) - print('Test accuracy on legitimate examples: %0.4f' % (acc)) - - model = ModelBasicCNN('model', nb_classes, nb_filters) - preds = model.get_logits(x) - cross_entropy_loss = CrossEntropy(model) - if not SNNL_factor: - loss = cross_entropy_loss - else: - loss = SNNLCrossEntropy(model, factor=SNNL_factor, - optimize_temperature=False) - - def evaluate(): - do_eval(preds, x_test, y_test, 'clean_train_clean_eval') - - train(sess, loss, x_train, y_train, evaluate=evaluate, - args=train_params, rng=rng, var_list=model.get_params()) - - do_eval(preds, x_train, y_train, 'train_clean_train_clean_eval') - - def imscatter(points, images, ax=None, zoom=1, cmap="hot"): - if ax is None: - ax = plt.gca() - artists = [] - i = 0 - if not isinstance(cmap, list): - cmap = [cmap] * len(points) - for x0, y0 in points: - transformed = (images[i] - np.min(images[i])) / \ - (np.max(images[i]) - np.min(images[i])) - im = OffsetImage(transformed[:, :, 0], zoom=zoom, cmap=cmap[i]) - ab = AnnotationBbox(im, (x0, y0), xycoords='data', frameon=False) - artists.append(ax.add_artist(ab)) - i += 1 - ax.update_datalim(np.column_stack(np.transpose(points))) - ax.autoscale() - ax.get_xaxis().set_ticks([]) - ax.get_yaxis().set_ticks([]) - return artists - - adv_grads = tf.sign(tf.gradients(cross_entropy_loss.fprop(x, y), x)) - feed_dict = {x: x_test[:batch_size], y: y_test[:batch_size]} - adv_grads_val = sess.run(adv_grads, feed_dict=feed_dict) - adv_grads_val = np.reshape(adv_grads_val, (batch_size, img_rows * img_cols)) - - X_embedded = TSNE(n_components=2, verbose=0).fit_transform(adv_grads_val) - plt.figure(num=None, figsize=(50, 50), dpi=40, facecolor='w', edgecolor='k') - plt.title("TSNE of Sign of Adv Gradients, SNNLCrossEntropy Model, factor:" + - str(FLAGS.SNNL_factor), fontsize=42) - imscatter(X_embedded, x_test[:batch_size], zoom=2, cmap="Purples") - plt.savefig(output_dir + 'adversarial_gradients_SNNL_factor_' + - str(SNNL_factor) + '.png') - - -def main(argv=None): - SNNL_example(nb_epochs=FLAGS.nb_epochs, batch_size=FLAGS.batch_size, - learning_rate=FLAGS.learning_rate, - nb_filters=FLAGS.nb_filters, - SNNL_factor=FLAGS.SNNL_factor, - output_dir=FLAGS.output_dir) - - -if __name__ == '__main__': - flags.DEFINE_integer('nb_filters', NB_FILTERS, - 'Model size multiplier') - flags.DEFINE_integer('nb_epochs', NB_EPOCHS, - 'Number of epochs to train model') - flags.DEFINE_integer('batch_size', BATCH_SIZE, - 'Size of training batches') - flags.DEFINE_float('SNNL_factor', SNNL_FACTOR, - 'Multiplier for Soft Nearest Neighbor Loss') - flags.DEFINE_float('learning_rate', LEARNING_RATE, - 'Learning rate for training') - flags.DEFINE_string('output_dir', OUTPUT_DIR, - 'output directory for saving figures') - - tf.app.run() diff --git a/cleverhans/picklable_model.py b/cleverhans/picklable_model.py deleted file mode 100644 index 5f3dafcd4..000000000 --- a/cleverhans/picklable_model.py +++ /dev/null @@ -1,857 +0,0 @@ -"""Models that support pickling. - -NOTE: This module is much more free to change than many other modules -in CleverHans. CleverHans is very conservative about changes to any -code that affects the output of benchmark tests (attacks, evaluation -methods, etc.). This module provides *models* not *benchmarks* and -thus is free to change rapidly to provide better speed, accuracy, -etc. -""" -# pylint: disable=missing-docstring - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import tensorflow as tf -import numpy as np - -from cleverhans.compat import reduce_mean, reduce_prod -from cleverhans.model import Model -from cleverhans.serial import PicklableVariable as PV -from cleverhans.utils import ordered_union - - -class PicklableModel(Model): - """ - A Model that supports pickling. - - Subclasses of this model must use only PicklableVariable and must refer - to their variables only by referencing the Python objects, not using - TensorFlow names (so no variable scopes). Pickle cannot find variables - referenced only by name and thus may fail to save them. Pickle may not - be able to get the original name back when restoring the variable so the - names should not be relied on. - """ - - def __init__(self): - super(PicklableModel, self).__init__() - del self.scope # Must not use Variable scopes / names for anything - - def get_params(self): - raise NotImplementedError(str(type(self)) + " does not implement" - " get_params.") - - -class MLP(PicklableModel): - """ - A picklable multilayer perceptron - """ - - def __hash__(self): - return hash(id(self)) - - def __init__(self, layers, input_shape): - super(MLP, self).__init__() - - if not isinstance(layers, list): - raise ValueError("`layers` must be a list.") - - self.layer_names = [] - self.layers = layers - self.input_shape = input_shape - - if isinstance(layers[-1], Softmax): - if not hasattr(layers[-1], 'name'): - layers[-1].name = 'probs' - if not hasattr(layers[-2], 'name'): - layers[-2].name = 'logits' - else: - if not hasattr(layers[-1], 'name'): - layers[-1].name = 'logits' - for i, layer in enumerate(self.layers): - if layer.parent is None: - if i == 0: - layer.parent = "input" - else: - layer.parent = layers[i - 1].name - if hasattr(layer, 'name'): - name = layer.name - else: - name = layer.__class__.__name__ + str(i) - layer.name = name - self.layer_names.append(name) - - layer.set_input_shape(input_shape) - input_shape = layer.get_output_shape() - - def get_params(self): - out = [] - for layer in self.layers: - out = ordered_union(out, layer.get_params()) - return out - - def fprop(self, x=None, given=None, **kwargs): - - # Note: this currently isn't great. - # A layer can have any parent it wants, but the parent - # must come earlier in the list. - # There's no way to have > 1 parent. - # This means we can support branched structures that split, - # e.g. for multiple output heads, but not structures - # that converge. - # We can feed a value in the middle using "given" but - # only layers after the given one are run using the current - # implementation, so the feed must happen before any branch - # point. - - if x is None: - if given is None: - raise ValueError("One of `x` or `given` must be specified") - else: - assert given is None - given = ('input', x) - name, value = given - out = {name: value} - x = value - - if name == 'input': - layers = self.layers - else: - for i, layer in enumerate(self.layers[:-1]): - if layer.name == name: - layers = self.layers[i+1:] - break - - for layer in layers: - x = out[layer.parent] - try: - x = layer.fprop(x, **kwargs) - except TypeError as e: - msg = "TypeError in fprop for %s of type %s: %s" - msg = msg % (layer.name, str(type(layer)), str(e)) - raise TypeError(msg) - assert x is not None - out[layer.name] = x - return out - - def make_input_placeholder(self): - return tf.placeholder(tf.float32, tuple(self.input_shape)) - - def make_label_placeholder(self): - try: - return self.layers[-1].make_label_placeholder() - except NotImplementedError: - return tf.placeholder(tf.float32, - self.layers[-1].get_output_shape()) - - -class Layer(PicklableModel): - def __init__(self, name=None, parent=None): - super(Layer, self).__init__() - if name is not None: - self.name = name - self.parent = parent - - def get_output_shape(self): - return self.output_shape - - -class Linear(Layer): - """ - Linear, fully connected layer. - :param init_mode: string - "norm" : the weight vector for each output is initialized to have - the same norm, given by `init_scale` - "uniform_unit_scaling" : U(-sqrt(3/input_dim), sqrt(3/input_dim)) - from https://arxiv.org/abs/1412.6558 - """ - - def __init__(self, num_hid, init_scale=1., init_b=0., use_bias=True, - init_mode="norm", - **kwargs): - super(Linear, self).__init__(**kwargs) - self.num_hid = num_hid - self.init_scale = init_scale - self.init_b = init_b - self.use_bias = use_bias - self.init_mode = init_mode - - def set_input_shape(self, input_shape): - batch_size, dim = input_shape - self.input_shape = [batch_size, dim] - self.output_shape = [batch_size, self.num_hid] - if self.init_mode == "norm": - init = tf.random_normal([dim, self.num_hid], dtype=tf.float32) - init = init / tf.sqrt(1e-7 + tf.reduce_sum(tf.square(init), axis=0, - keep_dims=True)) - init = init * self.init_scale - elif self.init_mode == "uniform_unit_scaling": - scale = np.sqrt(3. / dim) - init = tf.random_uniform([dim, self.num_hid], dtype=tf.float32, - minval=-scale, maxval=scale) - else: - raise ValueError(self.init_mode) - self.W = PV(init) - if self.use_bias: - self.b = PV((np.zeros((self.num_hid,)) - + self.init_b).astype('float32')) - - def fprop(self, x, **kwargs): - out = tf.matmul(x, self.W.var) - if self.use_bias: - out = out + self.b.var - return out - - def get_params(self): - out = [self.W.var] - if self.use_bias: - out.append(self.b.var) - return out - - -class Conv2D(Layer): - """ - 2-D Convolution. Uses NHWC format for input and output. - :param output_channels: int - The number of channels to output - :param kernel_shape: tuple of two ints - (kernel rows, kernel columns) - Do not include input channels or output channels in kernel_shape. - :param strides: tuple of two ints - (row stride, column stride) - Do not include channel or batch strides. - :param use_bias: bool - If True (default is False) adds a per-channel bias term to the output - :param init_mode: string - "norm" : each kernel is initialized to have the same norm, - given by `init_scale` - "inv_sqrt" : Gaussian with standard devation given by sqrt(2/fan_out) - "glorot_uniform" : U(+/- sqrt(6/(fan_in+fan_out)) - """ - - def __init__(self, output_channels, kernel_shape, strides, padding, - use_bias=False, init_scale=1., - init_mode="norm", **kwargs): - self.__dict__.update(locals()) - del self.self - super(Conv2D, self).__init__(**kwargs) - - def set_input_shape(self, input_shape): - _batch_size, _rows, _cols, input_channels = input_shape - assert len(self.kernel_shape) == 2 - kernel_shape = tuple(self.kernel_shape) + (input_channels, - self.output_channels) - assert len(kernel_shape) == 4 - assert all(isinstance(e, int) for e in kernel_shape), kernel_shape - fan_in = self.kernel_shape[0] * \ - self.kernel_shape[1] * input_channels - fan_out = self.kernel_shape[0] * \ - self.kernel_shape[1] * self.output_channels - if self.init_mode == "norm": - init = tf.random_normal(kernel_shape, dtype=tf.float32) - squared_norms = tf.reduce_sum(tf.square(init), axis=(0, 1, 2)) - denom = tf.sqrt(1e-7 + squared_norms) - init = self.init_scale * init / denom - elif self.init_mode == "inv_sqrt": - init = tf.random_normal(kernel_shape, dtype=tf.float32, - stddev=np.sqrt(2.0 / fan_out)) - elif self.init_mode == "glorot_uniform": - scale = np.sqrt(6. / (fan_in + fan_out)) - init = tf.random_uniform(kernel_shape, dtype=tf.float32, - minval=-scale, maxval=scale) - else: - raise ValueError(self.init_mode) - self.kernels = PV(init, name=self.name + "_kernels") - if self.use_bias: - self.b = PV(np.zeros((self.output_channels,)).astype('float32')) - input_shape = list(input_shape) - orig_batch_size = input_shape[0] - input_shape[0] = 1 - dummy_batch = tf.zeros(input_shape) - dummy_output = self.fprop(dummy_batch) - output_shape = [int(e) for e in dummy_output.get_shape()] - output_shape[0] = orig_batch_size - self.output_shape = tuple(output_shape) - - def fprop(self, x, **kwargs): - assert len(self.strides) == 2 - out = tf.nn.conv2d(x, self.kernels.var, - (1,) + tuple(self.strides) + (1,), self.padding) - if self.use_bias: - out = out + self.b.var - return out - - def get_params(self): - out = [self.kernels.var] - if self.use_bias: - out.append(self.b.var) - return out - - -class ReLU(Layer): - - def __init__(self, leak=0., **kwargs): - super(ReLU, self).__init__(**kwargs) - self.leak = leak - - def set_input_shape(self, shape): - self.input_shape = shape - self.output_shape = shape - - def get_output_shape(self): - return self.output_shape - - def fprop(self, x, **kwargs): - out = tf.nn.relu(x) - if self.leak != 0.0: - # The code commented below resulted in the time per epoch of - # an 8-GPU wide resnet increasing by about 5% relative to the - # code now in use. - # The two different implementations have the same forward prop - # down to machine precision on all inputs I have tested, but - # sometimes have different derivatives. - # Both obtain about the same training accuracy but the faster - # version seems to also be slightly more accurate. - # The commented code and these performance notes are included to - # aid future revision efforts. - # - # out = out - self.leak * tf.nn.relu(-x) - # - - out = tf.where(tf.less(x, 0.0), self.leak * x, x) - return out - - def get_params(self): - return [] - - -class Sigmoid(Layer): - - def __init__(self, **kwargs): - super(Sigmoid, self).__init__(**kwargs) - - def set_input_shape(self, shape): - self.input_shape = shape - self.output_shape = shape - - def get_output_shape(self): - return self.output_shape - - def fprop(self, x, **kwargs): - return tf.nn.sigmoid(x) - - def get_params(self): - return [] - - -class Tanh(Layer): - - def __init__(self, **kwargs): - super(Tanh, self).__init__(**kwargs) - - def set_input_shape(self, shape): - self.input_shape = shape - self.output_shape = shape - - def get_output_shape(self): - return self.output_shape - - def fprop(self, x, **kwargs): - return tf.nn.tanh(x) - - def get_params(self): - return [] - - -class LeakyReLU(ReLU): - - def __init__(self, leak=.2, **kwargs): - super(LeakyReLU, self).__init__(leak=leak, **kwargs) - - -class ELU(Layer): - - def set_input_shape(self, shape): - self.input_shape = shape - self.output_shape = shape - - def get_output_shape(self): - return self.output_shape - - def fprop(self, x, **kwargs): - return tf.nn.elu(x) - - def get_params(self): - return [] - - -class SELU(Layer): - - def set_input_shape(self, shape): - self.input_shape = shape - self.output_shape = shape - - def get_output_shape(self): - return self.output_shape - - def fprop(self, x, **kwargs): - alpha = 1.6732632423543772848170429916717 - scale = 1.0507009873554804934193349852946 - mask = tf.to_float(x >= 0.) - out = mask * x + (1. - mask) * \ - (alpha * tf.exp((1. - mask) * x) - alpha) - return scale * out - - def get_params(self): - return [] - - -class TanH(Layer): - - def set_input_shape(self, shape): - self.input_shape = shape - self.output_shape = shape - - def get_output_shape(self): - return self.output_shape - - def fprop(self, x, **kwargs): - return tf.nn.tanh(x) - - -class Softmax(Layer): - - def set_input_shape(self, shape): - self.input_shape = shape - self.output_shape = shape - - def fprop(self, x, **kwargs): - out = tf.nn.softmax(x) - return out - - def get_params(self): - return [] - - def make_label_placeholder(self): - return tf.placeholder(tf.float32, self.output_shape) - - -class Flatten(Layer): - - def set_input_shape(self, shape): - self.input_shape = shape - output_width = 1 - for factor in shape[1:]: - output_width *= factor - self.output_width = output_width - self.output_shape = [None, output_width] - - def fprop(self, x, **kwargs): - return tf.reshape(x, [-1, self.output_width]) - - def get_params(self): - return [] - - -class Print(Layer): - - def set_input_shape(self, shape): - self.input_shape = shape - self.output_shape = shape - - def get_params(self): - return [] - - def fprop(self, x, **kwargs): - mean = tf.reduce_mean(x) - std = tf.sqrt(tf.reduce_mean(tf.square(x - mean))) - return tf.Print(x, - [tf.reduce_min(x), mean, tf.reduce_max(x), std], - "Print layer") - - -class Add(Layer): - """ - A Layer that adds a function to its input. - The function to add is specified in terms of multiple layers, just like - in the MLP class. - The Add layer is useful for implementing residual networks. - """ - - def __hash__(self): - return hash(id(self)) - - def set_input_shape(self, shape): - self.input_shape = shape - shapes = {"input": shape} - for layer in self.layers: - layer.set_input_shape(shapes[layer.parent]) - shapes[layer.name] = layer.get_output_shape() - self.output_shape = shapes[self.layers[-1].name] - - def __init__(self, layers): - super(Add, self).__init__() - - self.layer_names = [] - self.layers = layers - - for i, layer in enumerate(self.layers): - if layer.parent is None: - if i == 0: - layer.parent = "input" - else: - layer.parent = layers[i - 1].name - if hasattr(layer, 'name'): - name = layer.name - else: - name = layer.__class__.__name__ + str(i) - layer.name = name - self.layer_names.append(name) - - def get_params(self): - out = [] - for layer in self.layers: - out = ordered_union(out, layer.get_params()) - return out - - def fprop(self, x, **kwargs): - - orig_x = x - - # Note: this currently isn't great. - # A layer can have any parent it wants, but the parent - # must come earlier in the list. - # There's no way to have > 1 parent. - # This means we can support branched structures that split, - # e.g. for multiple output heads, but not structures - # that converge. - # We can feed a value in the middle using "given" but - # only layers after the given one are run using the current - # implementation, so the feed must happen before any branch - # point. - - out = {'input': x} - - for layer in self.layers: - x = out[layer.parent] - try: - x = layer.fprop(x) - except TypeError as e: - msg = "TypeError in fprop for layer %s of type %s: %s" - msg = msg % (layer.name, str(type(layer)), str(e)) - raise TypeError(msg) - assert x is not None - out[layer.name] = x - - return orig_x + out[self.layers[-1].name] - - -class PerImageStandardize(Layer): - - def set_input_shape(self, shape): - self.input_shape = shape - self.output_shape = shape - - def get_params(self): - return [] - - def fprop(self, x, **kwargs): - axis = [1, 2, 3] - mean = reduce_mean(x, axis=axis, keepdims=True) - variance = reduce_mean( - tf.square(x), axis=axis, keepdims=True) - tf.square(mean) - variance = tf.nn.relu(variance) - stddev = tf.sqrt(variance) - - num_pixels = reduce_prod(tf.shape(x)[1:]) - - min_stddev = tf.rsqrt(tf.to_float(num_pixels)) - pixel_value_scale = tf.maximum(stddev, min_stddev) - pixel_value_offset = mean - - x = tf.subtract(x, pixel_value_offset) - x = tf.div(x, pixel_value_scale) - return x - - -class Dropout(Layer): - """Dropout layer. - - By default, is a no-op. Activate it during training using the kwargs - - The default use case is that you never specify include_prob anywhere. - During evaluation, you don't do anything special regarding dropout, - and nothing gets dropped. During training, you pass "dropout=True" - to make units get randomly dropped. - If you've done nothing else, include_prob defaults to 0.5 in the - Dropout class constructor. - - A slightly more advanced use case is that you want to use different - include_probs for some layer. For example, people usually use - include_prob=0.8 for the input layer. To do this, you specify - include_prob in the constructor arguments for those layers. - Other than that, it's the same as the basic use case case. You do - nothing special at test time and nothing is dropped. - You pass dropout=True at train time and units in each layer are dropped - based on their include_prob specified in their layer's constructor. - - The most advanced use case is if you want to change dropout include - probabilities for a specific fprop call. In this case, you pass - dropout=True and dropout_dict for that call. Each layer uses the - include_prob specified in the dropout_dict for that call.of MLP.fprop. - """ - - def __init__(self, include_prob=0.5, **kwargs): - super(Dropout, self).__init__(**kwargs) - self.include_prob = include_prob - - def set_input_shape(self, shape): - self.input_shape = shape - self.output_shape = shape - - def get_params(self): - return [] - - def fprop(self, x, dropout=False, dropout_dict=None, **kwargs): - """ - Forward propagation as either no-op or dropping random units. - :param x: The input to the layer - :param dropout: bool specifying whether to drop units - :param dropout_dict: dict - This dictionary is usually not needed. - In rare cases, generally for research purposes, this dictionary - makes it possible to run forward propagation with a different - dropout include probability. - This dictionary should be passed as a named argument to the MLP - class, which will then pass it to *all* layers' fprop methods. - Other layers will just receive this as an ignored kwargs entry. - Each dropout layer looks up its own name in this dictionary - to read out its include probability. - """ - include_prob = self.include_prob - if dropout_dict is not None: - assert dropout - if self.name in dropout_dict: - include_prob = dropout_dict[self.name] - if dropout: - return tf.nn.dropout(x, include_prob) - return x - - -class ResidualWithGroupNorm(Layer): - """A residual network layer that uses group normalization. - - :param out_filter: Number of output filters - :param stride: int - Stride for convolutional layers. Replicated to both row and column. - """ - - def __init__(self, out_filter, stride, activate_before_residual=False, - leak=0.1, **kwargs): - assert isinstance(stride, int) - self.__dict__.update(locals()) - del self.self - self.lrelu = LeakyReLU(leak) - super(ResidualWithGroupNorm, self).__init__(**kwargs) - - def set_input_shape(self, shape): - self.input_shape = tuple(shape) - self.in_filter = shape[-1] - self.gn1 = GroupNorm(name=self.name + "_gn1") - self.gn1.set_input_shape(shape) - strides = (self.stride, self.stride) - self.conv1 = Conv2D(self.out_filter, (3, 3), strides, "SAME", - name=self.name + "_conv1", init_mode="inv_sqrt") - self.conv1.set_input_shape(shape) - self.gn2 = GroupNorm(name=self.name + "_gn2") - self.gn2.set_input_shape(self.conv1.get_output_shape()) - self.conv2 = Conv2D(self.out_filter, (3, 3), (1, 1), "SAME", - name=self.name + "_conv2", init_mode="inv_sqrt") - self.conv2.set_input_shape(self.conv1.get_output_shape()) - self.output_shape = self.conv2.get_output_shape() - - def get_params(self): - sublayers = [self.conv1, self.conv2, self.gn1, self.gn2] - params = [] - for sublayer in sublayers: - params = params + sublayer.get_params() - assert self.conv1.kernels.var in params - return params - - def fprop(self, x, **kwargs): - if self.activate_before_residual: - x = self.gn1.fprop(x) - x = self.lrelu.fprop(x) - orig_x = x - else: - orig_x = x - x = self.gn1.fprop(x) - x = self.lrelu.fprop(x) - x = self.conv1.fprop(x) - x = self.gn2.fprop(x) - x = self.lrelu.fprop(x) - x = self.conv2.fprop(x) - if self.stride != 1: - stride = [1, self.stride, self.stride, 1] - orig_x = tf.nn.avg_pool(orig_x, stride, stride, 'VALID') - out_filter = self.out_filter - in_filter = self.in_filter - if in_filter != out_filter: - orig_x = tf.pad(orig_x, [[0, 0], [0, 0], [0, 0], - [(out_filter - in_filter) // 2, - (out_filter - in_filter) // 2]]) - x = x + orig_x - return x - - -class GlobalAveragePool(Layer): - - def set_input_shape(self, shape): - self.input_shape = shape - self.output_shape = [shape[0], shape[-1]] - - def get_params(self): - return [] - - def fprop(self, x, **kwargs): - assert len(list(x.get_shape())) == 4 - return tf.reduce_mean(x, [1, 2]) - - -class GroupNorm(Layer): - """ - Group normalization - - https://arxiv.org/abs/1803.08494 - """ - - def __init__(self, num_groups=32, eps=1e-3, init_gamma=1., - **kwargs): - self.num_groups = num_groups - self.eps = eps - self.init_gamma = init_gamma - super(GroupNorm, self).__init__(**kwargs) - - def set_input_shape(self, shape): - self.input_shape = shape - self.output_shape = shape - channels = shape[-1] - self.channels = channels - self.actual_num_groups = min(self.channels, self.num_groups) - extra_dims = (self.channels // self.actual_num_groups, - self.actual_num_groups) - self.expanded_shape = tuple(shape[1:3]) + tuple(extra_dims) - init_value = np.ones((channels,), dtype='float32') * self.init_gamma - self.gamma = PV(init_value, name=self.name + "_gamma") - self.beta = PV(np.zeros((self.channels,), dtype='float32'), - name=self.name + "_beta") - - def fprop(self, x, **kwargs): - shape = tf.shape(x) - batch_size = shape[0] - x = tf.reshape(x, (batch_size,) + self.expanded_shape) - mean, var = tf.nn.moments(x, [1, 2, 3], keep_dims=True) - x = (x - mean) * tf.rsqrt(var + self.eps) - x = tf.reshape(x, shape) - x = x * self.gamma.var + self.beta.var - return x - - def get_params(self): - return [self.gamma.var, self.beta.var] - - -class BatchNorm(Layer): - """ - This BatchNorm is always run in train mode for now - """ - - def __init__(self, eps=1e-3, init_gamma=1., - **kwargs): - self.eps = eps - self.init_gamma = init_gamma - super(BatchNorm, self).__init__(**kwargs) - - def set_input_shape(self, shape): - self.input_shape = shape - self.output_shape = shape - channels = shape[-1] - init_value = np.ones((channels,), dtype='float32') * self.init_gamma - self.gamma = PV(init_value, name=self.name + "_gamma") - self.beta = PV(np.zeros((channels,), dtype='float32'), - name=self.name + "_beta") - - def fprop(self, x, **kwargs): - mean, var = tf.nn.moments(x, [0, 1, 2], keep_dims=True) - x = (x - mean) * tf.rsqrt(var + self.eps) - x = x * self.gamma.var + self.beta.var - return x - - def get_params(self): - return [self.gamma.var, self.beta.var] - - -class ResidualWithBatchNorm(Layer): - """A residual network layer that uses batch normalization. - - :param out_filter: Number of output filters - :param stride: int - Stride for convolutional layers. Replicated to both row and column. - """ - - def __init__(self, out_filter, stride, activate_before_residual=False, - leak=0.1, **kwargs): - assert isinstance(stride, int) - self.__dict__.update(locals()) - del self.self - self.lrelu = LeakyReLU(leak) - super(ResidualWithBatchNorm, self).__init__(**kwargs) - - def set_input_shape(self, shape): - self.input_shape = tuple(shape) - self.in_filter = shape[-1] - self.bn1 = BatchNorm(name=self.name + "_bn1") - self.bn1.set_input_shape(shape) - strides = (self.stride, self.stride) - self.conv1 = Conv2D(self.out_filter, (3, 3), strides, "SAME", - name=self.name + "_conv1", init_mode="inv_sqrt") - self.conv1.set_input_shape(shape) - self.bn2 = BatchNorm(name=self.name + "_bn2") - self.bn2.set_input_shape(self.conv1.get_output_shape()) - self.conv2 = Conv2D(self.out_filter, (3, 3), (1, 1), "SAME", - name=self.name + "_conv2", init_mode="inv_sqrt") - self.conv2.set_input_shape(self.conv1.get_output_shape()) - self.output_shape = self.conv2.get_output_shape() - - def get_params(self): - sublayers = [self.conv1, self.conv2, self.bn1, self.bn2] - params = [] - for sublayer in sublayers: - params = params + sublayer.get_params() - assert self.conv1.kernels.var in params - return params - - def fprop(self, x, **kwargs): - if self.activate_before_residual: - x = self.bn1.fprop(x) - x = self.lrelu.fprop(x) - orig_x = x - else: - orig_x = x - x = self.bn1.fprop(x) - x = self.lrelu.fprop(x) - x = self.conv1.fprop(x) - x = self.bn2.fprop(x) - x = self.lrelu.fprop(x) - x = self.conv2.fprop(x) - if self.stride != 1: - stride = [1, self.stride, self.stride, 1] - orig_x = tf.nn.avg_pool(orig_x, stride, stride, 'VALID') - out_filter = self.out_filter - in_filter = self.in_filter - if in_filter != out_filter: - orig_x = tf.pad(orig_x, [[0, 0], [0, 0], [0, 0], - [(out_filter - in_filter) // 2, - (out_filter - in_filter) // 2]]) - x = x + orig_x - return x diff --git a/cleverhans/plot/image.py b/cleverhans/plot/image.py index 7edac254c..76d340d7f 100644 --- a/cleverhans/plot/image.py +++ b/cleverhans/plot/image.py @@ -10,137 +10,139 @@ from cleverhans.utils import shell_call -def show(ndarray, min_val=None, max_val=None): - """ - Display an image. - :param ndarray: The image as an ndarray - :param min_val: The minimum pixel value in the image format - :param max_val: The maximum pixel valie in the image format - If min_val and max_val are not specified, attempts to - infer whether the image is in any of the common ranges: - [0, 1], [-1, 1], [0, 255] - This can be ambiguous, so it is better to specify if known. - """ - - # Create a temporary file with the suffix '.png'. - fd, path = mkstemp(suffix='.png') - os.close(fd) - save(path, ndarray, min_val, max_val) - shell_call(VIEWER_COMMAND + [path]) +def show(ndarray, min_val=None, max_val=None): + """ + Display an image. + :param ndarray: The image as an ndarray + :param min_val: The minimum pixel value in the image format + :param max_val: The maximum pixel valie in the image format + If min_val and max_val are not specified, attempts to + infer whether the image is in any of the common ranges: + [0, 1], [-1, 1], [0, 255] + This can be ambiguous, so it is better to specify if known. + """ + + # Create a temporary file with the suffix '.png'. + fd, path = mkstemp(suffix=".png") + os.close(fd) + save(path, ndarray, min_val, max_val) + shell_call(VIEWER_COMMAND + [path]) def save(path, ndarray, min_val=None, max_val=None): - """ - Save an image, represented as an ndarray, to the filesystem - :param path: string, filepath - :param ndarray: The image as an ndarray - :param min_val: The minimum pixel value in the image format - :param max_val: The maximum pixel valie in the image format - If min_val and max_val are not specified, attempts to - infer whether the image is in any of the common ranges: - [0, 1], [-1, 1], [0, 255] - This can be ambiguous, so it is better to specify if known. - """ - as_pil(ndarray, min_val, max_val).save(path) + """ + Save an image, represented as an ndarray, to the filesystem + :param path: string, filepath + :param ndarray: The image as an ndarray + :param min_val: The minimum pixel value in the image format + :param max_val: The maximum pixel valie in the image format + If min_val and max_val are not specified, attempts to + infer whether the image is in any of the common ranges: + [0, 1], [-1, 1], [0, 255] + This can be ambiguous, so it is better to specify if known. + """ + as_pil(ndarray, min_val, max_val).save(path) + def as_pil(ndarray, min_val=None, max_val=None): - """ - Converts an ndarray to a PIL image. - :param ndarray: The numpy ndarray to convert - :param min_val: The minimum pixel value in the image format - :param max_val: The maximum pixel valie in the image format - If min_val and max_val are not specified, attempts to - infer whether the image is in any of the common ranges: - [0, 1], [-1, 1], [0, 255] - This can be ambiguous, so it is better to specify if known. - """ - - assert isinstance(ndarray, np.ndarray) - - # rows x cols for grayscale image - # rows x cols x channels for color - assert ndarray.ndim in [2, 3] - if ndarray.ndim == 3: - channels = ndarray.shape[2] - # grayscale or RGB - assert channels in [1, 3] - - actual_min = ndarray.min() - actual_max = ndarray.max() - - if min_val is not None: - assert actual_min >= min_val - assert actual_max <= max_val - - if np.issubdtype(ndarray.dtype, np.floating): - if min_val is None: - if actual_min < -1.: - raise ValueError("Unrecognized range") - if actual_min < 0: - min_val = -1. - else: - min_val = 0. - if max_val is None: - if actual_max > 255.: - raise ValueError("Unrecognized range") - if actual_max > 1.: - max_val = 255. - else: - max_val = 1. - ndarray = (ndarray - min_val) - value_range = max_val - min_val - ndarray *= (255. / value_range) - ndarray = np.cast['uint8'](ndarray) - elif 'int' in str(ndarray.dtype): + """ + Converts an ndarray to a PIL image. + :param ndarray: The numpy ndarray to convert + :param min_val: The minimum pixel value in the image format + :param max_val: The maximum pixel valie in the image format + If min_val and max_val are not specified, attempts to + infer whether the image is in any of the common ranges: + [0, 1], [-1, 1], [0, 255] + This can be ambiguous, so it is better to specify if known. + """ + + assert isinstance(ndarray, np.ndarray) + + # rows x cols for grayscale image + # rows x cols x channels for color + assert ndarray.ndim in [2, 3] + if ndarray.ndim == 3: + channels = ndarray.shape[2] + # grayscale or RGB + assert channels in [1, 3] + + actual_min = ndarray.min() + actual_max = ndarray.max() + if min_val is not None: - assert min_val == 0 - else: - assert actual_min >= 0. - if max_val is not None: - assert max_val == 255 + assert actual_min >= min_val + assert actual_max <= max_val + + if np.issubdtype(ndarray.dtype, np.floating): + if min_val is None: + if actual_min < -1.0: + raise ValueError("Unrecognized range") + if actual_min < 0: + min_val = -1.0 + else: + min_val = 0.0 + if max_val is None: + if actual_max > 255.0: + raise ValueError("Unrecognized range") + if actual_max > 1.0: + max_val = 255.0 + else: + max_val = 1.0 + ndarray = ndarray - min_val + value_range = max_val - min_val + ndarray *= 255.0 / value_range + ndarray = np.cast["uint8"](ndarray) + elif "int" in str(ndarray.dtype): + if min_val is not None: + assert min_val == 0 + else: + assert actual_min >= 0.0 + if max_val is not None: + assert max_val == 255 + else: + assert actual_max <= 255.0 else: - assert actual_max <= 255. - else: - raise ValueError("Unrecognized dtype") + raise ValueError("Unrecognized dtype") + + out = Image.fromarray(ndarray) - out = Image.fromarray(ndarray) + return out - return out def make_grid(image_batch): - """ - Turns a batch of images into one big image. - :param image_batch: ndarray, shape (batch_size, rows, cols, channels) - :returns : a big image containing all `batch_size` images in a grid - """ - m, ir, ic, ch = image_batch.shape + """ + Turns a batch of images into one big image. + :param image_batch: ndarray, shape (batch_size, rows, cols, channels) + :returns : a big image containing all `batch_size` images in a grid + """ + m, ir, ic, ch = image_batch.shape - pad = 3 + pad = 3 - padded = np.zeros((m, ir + pad * 2, ic + pad * 2, ch)) - padded[:, pad:-pad, pad:-pad, :] = image_batch + padded = np.zeros((m, ir + pad * 2, ic + pad * 2, ch)) + padded[:, pad:-pad, pad:-pad, :] = image_batch - m, ir, ic, ch = padded.shape + m, ir, ic, ch = padded.shape - pr = int(np.sqrt(m)) - pc = int(np.ceil(float(m) / pr)) - extra_m = pr * pc - assert extra_m > m + pr = int(np.sqrt(m)) + pc = int(np.ceil(float(m) / pr)) + extra_m = pr * pc + assert extra_m > m - padded = np.concatenate((padded, np.zeros((extra_m - m, ir, ic, ch))), axis=0) + padded = np.concatenate((padded, np.zeros((extra_m - m, ir, ic, ch))), axis=0) - row_content = np.split(padded, pr) - row_content = [np.split(content, pc) for content in row_content] - rows = [np.concatenate(content, axis=2) for content in row_content] - grid = np.concatenate(rows, axis=1) - assert grid.shape[0] == 1, grid.shape - grid = grid[0] + row_content = np.split(padded, pr) + row_content = [np.split(content, pc) for content in row_content] + rows = [np.concatenate(content, axis=2) for content in row_content] + grid = np.concatenate(rows, axis=1) + assert grid.shape[0] == 1, grid.shape + grid = grid[0] - return grid + return grid -if platform.system() == 'Darwin': - VIEWER_COMMAND = ['open', '-a', 'Preview'] +if platform.system() == "Darwin": + VIEWER_COMMAND = ["open", "-a", "Preview"] else: - VIEWER_COMMAND = ['eog', '--new-instance'] + VIEWER_COMMAND = ["eog", "--new-instance"] diff --git a/cleverhans/plot/pyplot_defaults.py b/cleverhans/plot/pyplot_defaults.py index ddf77df4c..a8f9d02cb 100644 --- a/cleverhans/plot/pyplot_defaults.py +++ b/cleverhans/plot/pyplot_defaults.py @@ -16,10 +16,11 @@ import matplotlib from matplotlib import pyplot -matplotlib.rcParams['text.latex.preamble'] = r'\usepackage{bm}' -matplotlib.rcParams['text.usetex'] = True -pyplot.rcParams['pdf.fonttype'] = 42 -pyplot.rcParams['font.family'] = 'serif' -pyplot.rcParams['font.serif'] = 'CMU Serif' -pyplot.rcParams['font.size'] = 8 + +matplotlib.rcParams["text.latex.preamble"] = r"\usepackage{bm}" +matplotlib.rcParams["text.usetex"] = True +pyplot.rcParams["pdf.fonttype"] = 42 +pyplot.rcParams["font.family"] = "serif" +pyplot.rcParams["font.serif"] = "CMU Serif" +pyplot.rcParams["font.size"] = 8 # Note: if you get an error, delete fontList.cache diff --git a/cleverhans/plot/pyplot_image.py b/cleverhans/plot/pyplot_image.py index b2db31b04..59d0ec2ec 100644 --- a/cleverhans/plot/pyplot_image.py +++ b/cleverhans/plot/pyplot_image.py @@ -6,177 +6,177 @@ import numpy as np from six.moves import range -def pair_visual(original, adversarial, figure=None): - """ - This function displays two images: the original and the adversarial sample - :param original: the original input - :param adversarial: the input after perturbations have been applied - :param figure: if we've already displayed images, use the same plot - :return: the matplot figure to reuse for future samples - """ - import matplotlib.pyplot as plt - - # Squeeze the image to remove single-dimensional entries from array shape - original = np.squeeze(original) - adversarial = np.squeeze(adversarial) - - # Ensure our inputs are of proper shape - assert(len(original.shape) == 2 or len(original.shape) == 3) - - # To avoid creating figures per input sample, reuse the sample plot - if figure is None: - plt.ion() - figure = plt.figure() - figure.canvas.set_window_title('Cleverhans: Pair Visualization') - - # Add the images to the plot - perturbations = adversarial - original - for index, image in enumerate((original, perturbations, adversarial)): - figure.add_subplot(1, 3, index + 1) - plt.axis('off') - - # If the image is 2D, then we have 1 color channel - if len(image.shape) == 2: - plt.imshow(image, cmap='gray') - else: - plt.imshow(image) - # Give the plot some time to update - plt.pause(0.01) +def pair_visual(original, adversarial, figure=None): + """ + This function displays two images: the original and the adversarial sample + :param original: the original input + :param adversarial: the input after perturbations have been applied + :param figure: if we've already displayed images, use the same plot + :return: the matplot figure to reuse for future samples + """ + import matplotlib.pyplot as plt + + # Squeeze the image to remove single-dimensional entries from array shape + original = np.squeeze(original) + adversarial = np.squeeze(adversarial) + + # Ensure our inputs are of proper shape + assert len(original.shape) == 2 or len(original.shape) == 3 + + # To avoid creating figures per input sample, reuse the sample plot + if figure is None: + plt.ion() + figure = plt.figure() + figure.canvas.set_window_title("Cleverhans: Pair Visualization") + + # Add the images to the plot + perturbations = adversarial - original + for index, image in enumerate((original, perturbations, adversarial)): + figure.add_subplot(1, 3, index + 1) + plt.axis("off") + + # If the image is 2D, then we have 1 color channel + if len(image.shape) == 2: + plt.imshow(image, cmap="gray") + else: + plt.imshow(image) + + # Give the plot some time to update + plt.pause(0.01) + + # Draw the plot and return + plt.show() + return figure - # Draw the plot and return - plt.show() - return figure def grid_visual(data): - """ - This function displays a grid of images to show full misclassification - :param data: grid data of the form; - [nb_classes : nb_classes : img_rows : img_cols : nb_channels] - :return: if necessary, the matplot figure to reuse - """ - import matplotlib.pyplot as plt - - # Ensure interactive mode is disabled and initialize our graph - plt.ioff() - figure = plt.figure() - figure.canvas.set_window_title('Cleverhans: Grid Visualization') - - # Add the images to the plot - num_cols = data.shape[0] - num_rows = data.shape[1] - num_channels = data.shape[4] - for y in range(num_rows): - for x in range(num_cols): - figure.add_subplot(num_rows, num_cols, (x + 1) + (y * num_cols)) - plt.axis('off') - - if num_channels == 1: - plt.imshow(data[x, y, :, :, 0], cmap='gray') - else: - plt.imshow(data[x, y, :, :, :]) - - # Draw the plot and return - plt.show() - return figure - - -def get_logits_over_interval(sess, model, x_data, fgsm_params, - min_epsilon=-10., max_epsilon=10., - num_points=21): - """Get logits when the input is perturbed in an interval in adv direction. - - Args: - sess: Tf session - model: Model for which we wish to get logits. - x_data: Numpy array corresponding to single data. - point of shape [height, width, channels]. - fgsm_params: Parameters for generating adversarial examples. - min_epsilon: Minimum value of epsilon over the interval. - max_epsilon: Maximum value of epsilon over the interval. - num_points: Number of points used to interpolate. - - Returns: - Numpy array containing logits. - - Raises: - ValueError if min_epsilon is larger than max_epsilon. - """ - # Get the height, width and number of channels - height = x_data.shape[0] - width = x_data.shape[1] - channels = x_data.shape[2] - - x_data = np.expand_dims(x_data, axis=0) - import tensorflow as tf - from cleverhans.attacks import FastGradientMethod - - # Define the data placeholder - x = tf.placeholder(dtype=tf.float32, - shape=[1, height, - width, - channels], - name='x') - # Define adv_x - fgsm = FastGradientMethod(model, sess=sess) - adv_x = fgsm.generate(x, **fgsm_params) - - if min_epsilon > max_epsilon: - raise ValueError('Minimum epsilon is less than maximum epsilon') - - eta = tf.nn.l2_normalize(adv_x - x, dim=0) - epsilon = tf.reshape(tf.lin_space(float(min_epsilon), - float(max_epsilon), - num_points), - (num_points, 1, 1, 1)) - lin_batch = x + epsilon * eta - logits = model.get_logits(lin_batch) - with sess.as_default(): - log_prob_adv_array = sess.run(logits, - feed_dict={x: x_data}) - return log_prob_adv_array - -def linear_extrapolation_plot(log_prob_adv_array, y, file_name, - min_epsilon=-10, max_epsilon=10, - num_points=21): - """Generate linear extrapolation plot. - - Args: - log_prob_adv_array: Numpy array containing log probabilities - y: Tf placeholder for the labels - file_name: Plot filename - min_epsilon: Minimum value of epsilon over the interval - max_epsilon: Maximum value of epsilon over the interval - num_points: Number of points used to interpolate - """ - import matplotlib - matplotlib.use('Agg') - import matplotlib.pyplot as plt - - figure = plt.figure() - figure.canvas.set_window_title('Cleverhans: Linear Extrapolation Plot') - - correct_idx = np.argmax(y, axis=0) - fig = plt.figure() - plt.xlabel('Epsilon') - plt.ylabel('Logits') - x_axis = np.linspace(min_epsilon, max_epsilon, num_points) - plt.xlim(min_epsilon - 1, max_epsilon + 1) - for i in range(y.shape[0]): - if i == correct_idx: - ls = '-' - linewidth = 5 - else: - ls = '--' - linewidth = 2 - plt.plot( - x_axis, - log_prob_adv_array[:, i], - ls=ls, - linewidth=linewidth, - label='{}'.format(i)) - plt.legend(loc='best', fontsize=14) - plt.show() - fig.savefig(file_name) - plt.clf() - return figure + """ + This function displays a grid of images to show full misclassification + :param data: grid data of the form; + [nb_classes : nb_classes : img_rows : img_cols : nb_channels] + :return: if necessary, the matplot figure to reuse + """ + import matplotlib.pyplot as plt + + # Ensure interactive mode is disabled and initialize our graph + plt.ioff() + figure = plt.figure() + figure.canvas.set_window_title("Cleverhans: Grid Visualization") + + # Add the images to the plot + num_cols = data.shape[0] + num_rows = data.shape[1] + num_channels = data.shape[4] + for y in range(num_rows): + for x in range(num_cols): + figure.add_subplot(num_rows, num_cols, (x + 1) + (y * num_cols)) + plt.axis("off") + + if num_channels == 1: + plt.imshow(data[x, y, :, :, 0], cmap="gray") + else: + plt.imshow(data[x, y, :, :, :]) + + # Draw the plot and return + plt.show() + return figure + + +def get_logits_over_interval( + sess, model, x_data, fgsm_params, min_epsilon=-10.0, max_epsilon=10.0, num_points=21 +): + """Get logits when the input is perturbed in an interval in adv direction. + + Args: + sess: Tf session + model: Model for which we wish to get logits. + x_data: Numpy array corresponding to single data. + point of shape [height, width, channels]. + fgsm_params: Parameters for generating adversarial examples. + min_epsilon: Minimum value of epsilon over the interval. + max_epsilon: Maximum value of epsilon over the interval. + num_points: Number of points used to interpolate. + + Returns: + Numpy array containing logits. + + Raises: + ValueError if min_epsilon is larger than max_epsilon. + """ + # Get the height, width and number of channels + height = x_data.shape[0] + width = x_data.shape[1] + channels = x_data.shape[2] + + x_data = np.expand_dims(x_data, axis=0) + import tensorflow as tf + from cleverhans.attacks import FastGradientMethod + + # Define the data placeholder + x = tf.placeholder(dtype=tf.float32, shape=[1, height, width, channels], name="x") + # Define adv_x + fgsm = FastGradientMethod(model, sess=sess) + adv_x = fgsm.generate(x, **fgsm_params) + + if min_epsilon > max_epsilon: + raise ValueError("Minimum epsilon is less than maximum epsilon") + + eta = tf.nn.l2_normalize(adv_x - x, dim=0) + epsilon = tf.reshape( + tf.lin_space(float(min_epsilon), float(max_epsilon), num_points), + (num_points, 1, 1, 1), + ) + lin_batch = x + epsilon * eta + logits = model.get_logits(lin_batch) + with sess.as_default(): + log_prob_adv_array = sess.run(logits, feed_dict={x: x_data}) + return log_prob_adv_array + + +def linear_extrapolation_plot( + log_prob_adv_array, y, file_name, min_epsilon=-10, max_epsilon=10, num_points=21 +): + """Generate linear extrapolation plot. + + Args: + log_prob_adv_array: Numpy array containing log probabilities + y: Tf placeholder for the labels + file_name: Plot filename + min_epsilon: Minimum value of epsilon over the interval + max_epsilon: Maximum value of epsilon over the interval + num_points: Number of points used to interpolate + """ + import matplotlib + + matplotlib.use("Agg") + import matplotlib.pyplot as plt + + figure = plt.figure() + figure.canvas.set_window_title("Cleverhans: Linear Extrapolation Plot") + + correct_idx = np.argmax(y, axis=0) + fig = plt.figure() + plt.xlabel("Epsilon") + plt.ylabel("Logits") + x_axis = np.linspace(min_epsilon, max_epsilon, num_points) + plt.xlim(min_epsilon - 1, max_epsilon + 1) + for i in range(y.shape[0]): + if i == correct_idx: + ls = "-" + linewidth = 5 + else: + ls = "--" + linewidth = 2 + plt.plot( + x_axis, + log_prob_adv_array[:, i], + ls=ls, + linewidth=linewidth, + label="{}".format(i), + ) + plt.legend(loc="best", fontsize=14) + plt.show() + fig.savefig(file_name) + plt.clf() + return figure diff --git a/cleverhans/plot/save_pdf.py b/cleverhans/plot/save_pdf.py index 17b066df2..2b8be691c 100644 --- a/cleverhans/plot/save_pdf.py +++ b/cleverhans/plot/save_pdf.py @@ -6,12 +6,12 @@ def save_pdf(path): - """ - Saves a pdf of the current matplotlib figure. + """ + Saves a pdf of the current matplotlib figure. - :param path: str, filepath to save to - """ + :param path: str, filepath to save to + """ - pp = PdfPages(path) - pp.savefig(pyplot.gcf()) - pp.close() + pp = PdfPages(path) + pp.savefig(pyplot.gcf()) + pp.close() diff --git a/cleverhans/plot/success_fail.py b/cleverhans/plot/success_fail.py index d225f8c3a..6c36b3bd8 100644 --- a/cleverhans/plot/success_fail.py +++ b/cleverhans/plot/success_fail.py @@ -11,264 +11,307 @@ from cleverhans.utils import safe_zip LINEWIDTH = 2 -DEFAULT_SUCCESS_NAME = 'clean' +DEFAULT_SUCCESS_NAME = "clean" # This must be a tuple or it is not safe to use as a param default -DEFAULT_FAIL_NAMES = ('mc', 'bundled') +DEFAULT_FAIL_NAMES = ("mc", "bundled") -def plot_report_from_path(path, success_name=DEFAULT_SUCCESS_NAME, - fail_names=DEFAULT_FAIL_NAMES, label=None, - is_max_confidence=True, - linewidth=LINEWIDTH, - plot_upper_bound=True): - """ - Plots a success-fail curve from a confidence report stored on disk, - :param path: string filepath for the stored report. - (Should be the output of make_confidence_report*.py) - :param success_name: The name (confidence report key) of the data that - should be used to measure success rate - :param fail_names: A list of names (confidence report keys) of the data - that should be used to measure failure rate. - *Only one of these keys will be plotted*. Each key will be tried in - order until one is found in the report. This is to support both the - output of `make_confidence_report` and `make_confidence_report_bundled`. - :param label: Optional string. Name to use for this curve in the legend. - :param is_max_confidence: bool. - If True, when measuring the failure rate, treat the data as the output - of a maximum confidence attack procedure. - This means that the attack is optimal (assuming the underlying optimizer - is good enough, *which is probably false*, so interpret the plot - accordingly) for thresholds >= .5 but for lower thresholds the observed - failure rate is a lower bound on the true worst failure rate and the - observed coverage is an upper bound (assuming good enough optimization) - on the true failure rate. - The plot thus draws the threshold >= .5 portion of the curve with a solid - line and the upper and lower bounds with a dashed line. - See https://openreview.net/forum?id=H1g0piA9tQ for details. - If False, the attack procedure is regarded as an ad hoc way of obtaining - a loose lower bound, and thus the whole curve is drawn with dashed lines. - :param linewidth: thickness of the line to draw - :param plot_upper_bound: include upper bound on error rate in plot - """ - report = load(path) - plot_report(report, success_name, fail_names, label, is_max_confidence, - linewidth, plot_upper_bound) +def plot_report_from_path( + path, + success_name=DEFAULT_SUCCESS_NAME, + fail_names=DEFAULT_FAIL_NAMES, + label=None, + is_max_confidence=True, + linewidth=LINEWIDTH, + plot_upper_bound=True, +): + """ + Plots a success-fail curve from a confidence report stored on disk, + :param path: string filepath for the stored report. + (Should be the output of make_confidence_report*.py) + :param success_name: The name (confidence report key) of the data that + should be used to measure success rate + :param fail_names: A list of names (confidence report keys) of the data + that should be used to measure failure rate. + *Only one of these keys will be plotted*. Each key will be tried in + order until one is found in the report. This is to support both the + output of `make_confidence_report` and `make_confidence_report_bundled`. + :param label: Optional string. Name to use for this curve in the legend. + :param is_max_confidence: bool. + If True, when measuring the failure rate, treat the data as the output + of a maximum confidence attack procedure. + This means that the attack is optimal (assuming the underlying optimizer + is good enough, *which is probably false*, so interpret the plot + accordingly) for thresholds >= .5 but for lower thresholds the observed + failure rate is a lower bound on the true worst failure rate and the + observed coverage is an upper bound (assuming good enough optimization) + on the true failure rate. + The plot thus draws the threshold >= .5 portion of the curve with a solid + line and the upper and lower bounds with a dashed line. + See https://openreview.net/forum?id=H1g0piA9tQ for details. + If False, the attack procedure is regarded as an ad hoc way of obtaining + a loose lower bound, and thus the whole curve is drawn with dashed lines. + :param linewidth: thickness of the line to draw + :param plot_upper_bound: include upper bound on error rate in plot + """ + report = load(path) + plot_report( + report, + success_name, + fail_names, + label, + is_max_confidence, + linewidth, + plot_upper_bound, + ) -def plot_report(report, success_name, fail_names, label=None, - is_max_confidence=True, - linewidth=LINEWIDTH, - plot_upper_bound=True): - """ - Plot a success fail curve from a confidence report - :param report: A confidence report - (the type of object saved by make_confidence_report.py) - :param success_name: see plot_report_from_path - :param fail_names: see plot_report_from_path - :param label: see plot_report_from_path - :param is_max_confidence: see plot_report_from_path - :param linewidth: see plot_report_from_path - """ - (fail_optimal, success_optimal, fail_lower_bound, fail_upper_bound, - success_bounded) = make_curve(report, success_name, fail_names) - assert len(fail_lower_bound) == len(fail_upper_bound) - fail_optimal = np.array(fail_optimal) - fail_lower_bound = np.array(fail_lower_bound) - fail_upper_bound = np.array(fail_upper_bound) - if is_max_confidence: - p, = pyplot.plot(fail_optimal, success_optimal, label=label, - linewidth=linewidth) - color = p.get_color() - pyplot.plot(fail_lower_bound, success_bounded, '--', color=color) - if plot_upper_bound: - pyplot.plot(fail_upper_bound, success_bounded, '--', color=color) - else: - # If the attack was not MaxConfidence, then this whole curve is just - # a loose lower bound - all_fail = np.concatenate((fail_optimal, fail_lower_bound), axis=0) - pyplot.plot(all_fail, success_optimal + success_bounded, - '--', label=label, linewidth=linewidth) +def plot_report( + report, + success_name, + fail_names, + label=None, + is_max_confidence=True, + linewidth=LINEWIDTH, + plot_upper_bound=True, +): + """ + Plot a success fail curve from a confidence report + :param report: A confidence report + (the type of object saved by make_confidence_report.py) + :param success_name: see plot_report_from_path + :param fail_names: see plot_report_from_path + :param label: see plot_report_from_path + :param is_max_confidence: see plot_report_from_path + :param linewidth: see plot_report_from_path + """ + ( + fail_optimal, + success_optimal, + fail_lower_bound, + fail_upper_bound, + success_bounded, + ) = make_curve(report, success_name, fail_names) + assert len(fail_lower_bound) == len(fail_upper_bound) + fail_optimal = np.array(fail_optimal) + fail_lower_bound = np.array(fail_lower_bound) + fail_upper_bound = np.array(fail_upper_bound) - pyplot.xlabel("Failure rate on adversarial examples") - pyplot.ylabel("Success rate on clean examples") - gap = fail_upper_bound - fail_lower_bound - if gap.size > 0: - assert gap.min() >= 0. - print("Max gap: ", gap.max()) + if is_max_confidence: + (p,) = pyplot.plot( + fail_optimal, success_optimal, label=label, linewidth=linewidth + ) + color = p.get_color() + pyplot.plot(fail_lower_bound, success_bounded, "--", color=color) + if plot_upper_bound: + pyplot.plot(fail_upper_bound, success_bounded, "--", color=color) + else: + # If the attack was not MaxConfidence, then this whole curve is just + # a loose lower bound + all_fail = np.concatenate((fail_optimal, fail_lower_bound), axis=0) + pyplot.plot( + all_fail, + success_optimal + success_bounded, + "--", + label=label, + linewidth=linewidth, + ) + pyplot.xlabel("Failure rate on adversarial examples") + pyplot.ylabel("Success rate on clean examples") + gap = fail_upper_bound - fail_lower_bound + if gap.size > 0: + assert gap.min() >= 0.0 + print("Max gap: ", gap.max()) -def make_curve(report, success_name, fail_names): - """ - Make a success-failure curve. - :param report: A confidence report - (the type of object saved by make_confidence_report.py) - :param success_name: see plot_report_from_path - :param fail_names: see plot_report_from_path - :returns: - fail_optimal: list of failure rates on adversarial data for the optimal - (t >= .5) part of the curve. Each entry corresponds to a different - threshold. Thresholds are chosen to make the smoothest possible curve - from the available data, e.g. one threshold between each unique - confidence value observed in the data. To make sure that linear - interpolation between points in the curve never overestimates the - failure rate for a specific success rate, the curve also includes - extra points that increment the failure rate prior to any point - that increments the success rate, so the curve moves up and to the - right in a series of backwards "L" shapes rather than moving up - and to the right along diagonal lines. For large datasets these - maximally pessimistic points will usually not be visible and the - curve will appear smooth. - success_optimal: list of success rates on clean data on the optimal - part of the curve. Matches up with `fail_optimal`. - fail_lower_bound: list of observed failure rates on the t < .5 portion - of the curve where MaxConfidence is not optimal. - fail_upper_bound: list of upper bounds (assuming good enough optimization, - so not a true upper bound) on the failure rates on the t < .5 portion - of the curve where MaxConfidence is not optimal. Matches up with - `fail_lower_bound`. - success_bounded: success rates on the non-optimal part of the curve. - Matches up with `fail_lower_bound` and `fail_upper_bound`. - """ - success_results = report[success_name] - fail_name = None # pacify pylint - found = False - for fail_name in fail_names: - if fail_name in report: - found = True - break - if not found: - raise ValueError(fail_name + " not in report." - "Available keys: " + str(report.keys())) - fail_results = report[fail_name] - # "good" means drawn from the distribution where we measure success rate. - # "bad" means drawn from the distribution where we measure failure rate. - # From here on out we use those terms, to avoid confusion between examples - # that actually failed and examples that were drawn from the distribution - # where we measured failure rate. +def make_curve(report, success_name, fail_names): + """ + Make a success-failure curve. + :param report: A confidence report + (the type of object saved by make_confidence_report.py) + :param success_name: see plot_report_from_path + :param fail_names: see plot_report_from_path + :returns: + fail_optimal: list of failure rates on adversarial data for the optimal + (t >= .5) part of the curve. Each entry corresponds to a different + threshold. Thresholds are chosen to make the smoothest possible curve + from the available data, e.g. one threshold between each unique + confidence value observed in the data. To make sure that linear + interpolation between points in the curve never overestimates the + failure rate for a specific success rate, the curve also includes + extra points that increment the failure rate prior to any point + that increments the success rate, so the curve moves up and to the + right in a series of backwards "L" shapes rather than moving up + and to the right along diagonal lines. For large datasets these + maximally pessimistic points will usually not be visible and the + curve will appear smooth. + success_optimal: list of success rates on clean data on the optimal + part of the curve. Matches up with `fail_optimal`. + fail_lower_bound: list of observed failure rates on the t < .5 portion + of the curve where MaxConfidence is not optimal. + fail_upper_bound: list of upper bounds (assuming good enough optimization, + so not a true upper bound) on the failure rates on the t < .5 portion + of the curve where MaxConfidence is not optimal. Matches up with + `fail_lower_bound`. + success_bounded: success rates on the non-optimal part of the curve. + Matches up with `fail_lower_bound` and `fail_upper_bound`. + """ + success_results = report[success_name] + fail_name = None # pacify pylint + found = False + for fail_name in fail_names: + if fail_name in report: + found = True + break + if not found: + raise ValueError( + fail_name + " not in report." "Available keys: " + str(report.keys()) + ) + fail_results = report[fail_name] - old_all_probs_version = False - if isinstance(success_results, dict): - # This dictionary key lookup will trigger a deprecation warning if `success_results` is not the old dictionary - # style of report, so we don't want to do a dictionary lookup unless we really are using the old version. - old_all_probs_version = 'all_probs' in success_results + # "good" means drawn from the distribution where we measure success rate. + # "bad" means drawn from the distribution where we measure failure rate. + # From here on out we use those terms, to avoid confusion between examples + # that actually failed and examples that were drawn from the distribution + # where we measured failure rate. - if old_all_probs_version: - warnings.warn("The 'all_probs' key is included only to support " - " old files from a private development codebase. " - "Support for this key can be dropped at any time " - " without warning.") - good_probs = success_results['all_probs'] - bad_probs = fail_results['all_probs'] - bad_corrects = fail_results['correctness_mask'] - good_corrects = success_results['correctness_mask'] - else: + old_all_probs_version = False if isinstance(success_results, dict): - # Still using dict, but using newer key names - warnings.warn("Support for dictionary confidence reports is deprecated. Switch to using the classes in " - "cleverhans.confidence_report. Support for old dictionary-style reports may be removed " - "on or after 2019-07-19.") - good_probs = success_results['confidence'] - bad_probs = fail_results['confidence'] - good_corrects = success_results['correctness'] - bad_corrects = fail_results['correctness'] + # This dictionary key lookup will trigger a deprecation warning if `success_results` is not the old dictionary + # style of report, so we don't want to do a dictionary lookup unless we really are using the old version. + old_all_probs_version = "all_probs" in success_results + + if old_all_probs_version: + warnings.warn( + "The 'all_probs' key is included only to support " + " old files from a private development codebase. " + "Support for this key can be dropped at any time " + " without warning." + ) + good_probs = success_results["all_probs"] + bad_probs = fail_results["all_probs"] + bad_corrects = fail_results["correctness_mask"] + good_corrects = success_results["correctness_mask"] else: - # current version - good_probs = success_results.confidence - bad_probs = fail_results.confidence - good_corrects = success_results.correctness - bad_corrects = fail_results.correctness - good_triplets = [(prob, correct, True) for prob, correct - in safe_zip(good_probs, good_corrects)] - bad_triplets = [(prob, correct, False) for prob, correct - in safe_zip(bad_probs, bad_corrects)] - total_good = len(good_triplets) - total_bad = len(bad_triplets) - if total_good != 10000: - warnings.warn("Not using full test set? Found " + str(total_good) + - " examples for measuring success rate") - if total_bad != 10000: - warnings.warn("Not using full test set for adversarial examples?") - all_triplets = good_triplets + bad_triplets - all_triplets = sorted(all_triplets, key=lambda x: -x[0]) + if isinstance(success_results, dict): + # Still using dict, but using newer key names + warnings.warn( + "Support for dictionary confidence reports is deprecated. Switch to using the classes in " + "cleverhans.confidence_report. Support for old dictionary-style reports may be removed " + "on or after 2019-07-19." + ) + good_probs = success_results["confidence"] + bad_probs = fail_results["confidence"] + good_corrects = success_results["correctness"] + bad_corrects = fail_results["correctness"] + else: + # current version + good_probs = success_results.confidence + bad_probs = fail_results.confidence + good_corrects = success_results.correctness + bad_corrects = fail_results.correctness + good_triplets = [ + (prob, correct, True) for prob, correct in safe_zip(good_probs, good_corrects) + ] + bad_triplets = [ + (prob, correct, False) for prob, correct in safe_zip(bad_probs, bad_corrects) + ] + total_good = len(good_triplets) + total_bad = len(bad_triplets) + if total_good != 10000: + warnings.warn( + "Not using full test set? Found " + + str(total_good) + + " examples for measuring success rate" + ) + if total_bad != 10000: + warnings.warn("Not using full test set for adversarial examples?") + all_triplets = good_triplets + bad_triplets + all_triplets = sorted(all_triplets, key=lambda x: -x[0]) - # Start with the case for threshold t = 1. - # Examples are covered only if prob > t (strict inequality) - # So initially nothing is covered - good_covered_and_correct = 0 - bad_covered_and_incorrect = 0 + # Start with the case for threshold t = 1. + # Examples are covered only if prob > t (strict inequality) + # So initially nothing is covered + good_covered_and_correct = 0 + bad_covered_and_incorrect = 0 - # Number of examples that are bad, incorrect, and covered by - # a t >= 0.5, or that were merely covered by a t < 0.5 - failure_opportunities = 0 + # Number of examples that are bad, incorrect, and covered by + # a t >= 0.5, or that were merely covered by a t < 0.5 + failure_opportunities = 0 - next_idx = 0 + next_idx = 0 - fail_optimal = [] - success_optimal = [] - fail_upper_bound = [] - fail_lower_bound = [] - success_bounded = [] + fail_optimal = [] + success_optimal = [] + fail_upper_bound = [] + fail_lower_bound = [] + success_bounded = [] - bounded = False + bounded = False - # NOTE: the loop always exits via an internal break statement. - # Copied the termination condition to the while statement for ease - # of reading. - while next_idx < len(all_triplets): - gs = float(good_covered_and_correct) / total_good - bf = float(bad_covered_and_incorrect) / total_bad - # Add results for current threshold to the list - if not bounded: + # NOTE: the loop always exits via an internal break statement. + # Copied the termination condition to the while statement for ease + # of reading. + while next_idx < len(all_triplets): + gs = float(good_covered_and_correct) / total_good + bf = float(bad_covered_and_incorrect) / total_bad + # Add results for current threshold to the list + if not bounded: - # Sometimes when there are big jumps the failure rate it makes - # artifacts in the plot, where there's a long linear track. - # This implies the real success-fail curve is linear when - # actually it just isn't sampled by the data. - # To avoid implying that the model reaches a higher success - # rate than it actually does, we avoid these plotting artifacts - # by introducing extra points that make the graph move horizontally - # to the right first, then vertically. - if len(fail_optimal) > 0: - prev_bf = fail_optimal[-1] - prev_gs = success_optimal[-1] + # Sometimes when there are big jumps the failure rate it makes + # artifacts in the plot, where there's a long linear track. + # This implies the real success-fail curve is linear when + # actually it just isn't sampled by the data. + # To avoid implying that the model reaches a higher success + # rate than it actually does, we avoid these plotting artifacts + # by introducing extra points that make the graph move horizontally + # to the right first, then vertically. + if len(fail_optimal) > 0: + prev_bf = fail_optimal[-1] + prev_gs = success_optimal[-1] - if gs > prev_gs and bf > prev_bf: - fail_optimal.append(bf) - success_optimal.append(prev_gs) + if gs > prev_gs and bf > prev_bf: + fail_optimal.append(bf) + success_optimal.append(prev_gs) - success_optimal.append(gs) - fail_optimal.append(bf) - else: - success_bounded.append(gs) - fail_lower_bound.append(bf) - fail_upper_bound.append(float(failure_opportunities) / total_bad) + success_optimal.append(gs) + fail_optimal.append(bf) + else: + success_bounded.append(gs) + fail_lower_bound.append(bf) + fail_upper_bound.append(float(failure_opportunities) / total_bad) - if next_idx == len(all_triplets): - break + if next_idx == len(all_triplets): + break - # next_prob_to_include is not quite the same thing as the threshold. - # The threshold is infinitesimally smaller than this value. - next_prob_to_include = all_triplets[next_idx][0] + # next_prob_to_include is not quite the same thing as the threshold. + # The threshold is infinitesimally smaller than this value. + next_prob_to_include = all_triplets[next_idx][0] - # Process all ties - while next_prob_to_include == all_triplets[next_idx][0]: - _prob, correct, is_good = all_triplets[next_idx] - if is_good: - good_covered_and_correct += correct - else: - if next_prob_to_include <= .5: - failure_opportunities += 1 - else: - failure_opportunities += 1 - correct - bad_covered_and_incorrect += 1 - correct - next_idx += 1 - if next_idx == len(all_triplets): - break + # Process all ties + while next_prob_to_include == all_triplets[next_idx][0]: + _prob, correct, is_good = all_triplets[next_idx] + if is_good: + good_covered_and_correct += correct + else: + if next_prob_to_include <= 0.5: + failure_opportunities += 1 + else: + failure_opportunities += 1 - correct + bad_covered_and_incorrect += 1 - correct + next_idx += 1 + if next_idx == len(all_triplets): + break - if next_prob_to_include <= .5: - bounded = True + if next_prob_to_include <= 0.5: + bounded = True - out = (fail_optimal, success_optimal, fail_lower_bound, fail_upper_bound, - success_bounded) - return out + out = ( + fail_optimal, + success_optimal, + fail_lower_bound, + fail_upper_bound, + success_bounded, + ) + return out diff --git a/cleverhans/serial.py b/cleverhans/serial.py deleted file mode 100644 index 3bdc1ef8b..000000000 --- a/cleverhans/serial.py +++ /dev/null @@ -1,219 +0,0 @@ -"""Serialization functionality. - -""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import warnings - -import joblib -import tensorflow as tf - -from cleverhans.model import Model -from cleverhans.utils import ordered_union -from cleverhans.utils import safe_zip - - -class PicklableVariable(object): - """ - A wrapper around a Variable that makes it picklable. - - The name of the Variable will not be reliable, only the value. Models - intended to be picklable should identify variables by referencing - Python objects rather than by using TensorFlow's names. - - TensorFlow Variables have different values associated with each Session. - For this class, the value associated with the default Session will be used - for both saving and loading, so both operations require that a default - Session has been selected. - - Pickle is not secure. Unpickle only files you made yourself. - - See cleverhans_tutorials/mnist_tutorial_picklable.py for examples of a - complete model training, pickling, and unpickling process using - PicklableVariable. - - See cleverhans.picklable_model for models built using PicklableVariable. - """ - - def __init__(self, *args, **kwargs): - self.var = tf.Variable(*args, **kwargs) - - def __getstate__(self): - sess = tf.get_default_session() - if sess is None: - raise RuntimeError("PicklableVariable requires a default " - "TensorFlow session") - return {'var': sess.run(self.var)} - - def __setstate__(self, d): - self.var = tf.Variable(d['var']) - sess = tf.get_default_session() - if sess is None: - raise RuntimeError("PicklableVariable requires a default " - "TensorFlow session") - sess.run(self.var.initializer) - - -class NoRefModel(Model): - """ - A Model that can be pickled because it contains no references to any - Variables (e.g. it identifies Variables only by name). - The Model must be able to find all of its Variables via get_vars - for them to be pickled. - Note that NoRefModel may have different Variable names after it is - restored, e.g. if the unpickling is run with a different enclosing - scope. NoRefModel will still work in these circumstances as long - as get_params returns the same order of Variables after unpickling - as it did before pickling. - See also cleverhans.picklable_model for a different, complementary - pickling strategy: models that can be pickled because they use *only* - references to Variables and work regardless of Variable names. - """ - - def __getstate__(self): - # Serialize everything except the Variables - out = self.__dict__.copy() - - # The base Model class adds this tf reference to self - # We mustn't pickle anything tf, this will need to be - # regenerated after the model is reloaded. - if "_dummy_input" in out: - del out["_dummy_input"] - - # Add the Variables - sess = tf.get_default_session() - if sess is None: - raise RuntimeError("NoRefModel requires a default " - "TensorFlow session") - tf_variables = self.get_vars() - out[VARS] = sess.run(tf_variables) - out[VAR_NAMES] = [var.name for var in tf_variables] - return out - - def __setstate__(self, d): - tf_variables = d[VARS] - del d[VARS] - tf_variable_names = None - # older joblib files may not have "_tf_variable_names" - if VAR_NAMES in d: - tf_variable_names = d[VAR_NAMES] - del d[VAR_NAMES] - else: - warnings.warn("This joblib file has no " + VAR_NAMES + " field. " - "The field may become required on or after 2019-04-11." - "You can make your file compatible with the new format by" - " loading the file and re-saving it.") - # Deserialize everything except the Variables - self.__dict__ = d - # Deserialize the Variables - sess = tf.get_default_session() - if sess is None: - raise RuntimeError("NoRefModel requires a default " - "TensorFlow session") - cur_vars = self.get_vars() - if len(cur_vars) != len(tf_variables): - print("Model format mismatch") - print("Current model has " + str(len(cur_vars)) + " variables") - print("Saved model has " + str(len(tf_variables)) + " variables") - print("Names of current vars:") - for var in cur_vars: - print("\t" + var.name) - if tf_variable_names is not None: - print("Names of saved vars:") - for name in tf_variable_names: - print("\t" + name) - else: - print("Saved vars use old format, no names available for them") - assert False - - found = [False] * len(cur_vars) - if tf_variable_names is not None: - # New version using the names to handle changes in ordering - for value, name in safe_zip(tf_variables, tf_variable_names): - value_found = False - for idx, cur_var in enumerate(cur_vars): - if cur_var.name == name: - assert not found[idx] - value_found = True - found[idx] = True - cur_var.load(value, sess) - break - assert value_found - assert all(found) - else: - # Old version that works if and only if the order doesn't change - for var, value in safe_zip(cur_vars, tf_variables): - var.load(value, sess) - - def get_vars(self): - """ - Provides access to the model's Variables. - This may include Variables that are not parameters, such as batch - norm running moments. - :return: A list of all Variables defining the model. - """ - - # Catch eager execution and assert function overload. - try: - if tf.executing_eagerly(): - raise NotImplementedError("For Eager execution - get_vars " - "must be overridden.") - except AttributeError: - pass - - done = False - tried_to_make_params = False - while not done: - # Most models in cleverhans use only trainable variables and do not - # make sure the other collections are updated correctly. - trainable_vars = tf.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES, - self.scope + "/") - # When wrapping other code, such as the CIFAR 10 challenge models, - # we need to make sure we get the batch norm running averages as well - # as the trainable variables. - model_vars = tf.get_collection(tf.GraphKeys.MODEL_VARIABLES, - self.scope + "/") - scope_vars = ordered_union(trainable_vars, model_vars) - - if len(scope_vars) > 0: - done = True - else: - assert not tried_to_make_params - tried_to_make_params = True - self.make_params() - - # Make sure no variables have been added or removed - if hasattr(self, "num_vars"): - assert self.num_vars == len(scope_vars) - else: - self.num_vars = len(scope_vars) - - return scope_vars - - -def save(filepath, obj): - """Saves an object to the specified filepath using joblib. - - joblib is like pickle but will save NumPy arrays as separate files for - greater efficiency. - - :param filepath: str, path to save to - :obj filepath: object to save - """ - - joblib.dump(obj, filepath) - - -def load(filepath): - """Returns an object stored via `save` - """ - - obj = joblib.load(filepath) - - return obj - -VARS = "_tf_variables" -VAR_NAMES = "_tf_variable_names" diff --git a/cleverhans/future/jax/__init__.py b/cleverhans/tf2/__init__.py similarity index 100% rename from cleverhans/future/jax/__init__.py rename to cleverhans/tf2/__init__.py diff --git a/tutorials/future/jax/__init__.py b/cleverhans/tf2/attacks/__init__.py similarity index 100% rename from tutorials/future/jax/__init__.py rename to cleverhans/tf2/attacks/__init__.py diff --git a/cleverhans/tf2/attacks/basic_iterative_method.py b/cleverhans/tf2/attacks/basic_iterative_method.py new file mode 100644 index 000000000..5357f4de1 --- /dev/null +++ b/cleverhans/tf2/attacks/basic_iterative_method.py @@ -0,0 +1,40 @@ +""" +The BasicIterativeMethod attack. +""" + +from cleverhans.tf2.attacks.projected_gradient_descent import projected_gradient_descent + + +def basic_iterative_method( + model_fn, + x, + eps, + eps_iter, + nb_iter, + norm, + clip_min=None, + clip_max=None, + y=None, + targeted=False, + rand_init=None, + rand_minmax=0.3, + sanity_checks=True, +): + """ + The BasicIterativeMethod attack. + """ + return projected_gradient_descent( + model_fn, + x, + eps, + eps_iter, + nb_iter, + norm, + clip_min=clip_min, + clip_max=clip_max, + y=y, + targeted=targeted, + rand_init=False, + rand_minmax=rand_minmax, + sanity_checks=sanity_checks, + ) diff --git a/cleverhans/tf2/attacks/carlini_wagner_l2.py b/cleverhans/tf2/attacks/carlini_wagner_l2.py new file mode 100644 index 000000000..e0346577d --- /dev/null +++ b/cleverhans/tf2/attacks/carlini_wagner_l2.py @@ -0,0 +1,339 @@ +"""The CarliniWagnerL2 attack. +""" +import numpy as np +import tensorflow as tf +from cleverhans.tf2.utils import get_or_guess_labels, set_with_mask + + +def carlini_wagner_l2(model_fn, x, **kwargs): + """ + This is the function interface for the Carlini-Wagner-L2 attack. + For more details on the attack and the parameters see the corresponding class. + """ + return CarliniWagnerL2(model_fn, **kwargs).attack(x) + + +class CarliniWagnerL2Exception(Exception): + pass + + +class CarliniWagnerL2(object): + def __init__( + self, + model_fn, + y=None, + targeted=False, + batch_size=128, + clip_min=0.0, + clip_max=1.0, + binary_search_steps=5, + max_iterations=1_000, + abort_early=True, + confidence=0.0, + initial_const=1e-2, + learning_rate=5e-3, + ): + """ + This attack was originally proposed by Carlini and Wagner. It is an + iterative attack that finds adversarial examples on many defenses that + are robust to other attacks. + Paper link: https://arxiv.org/abs/1608.04644 + At a high level, this attack is an iterative attack using Adam and + a specially-chosen loss function to find adversarial examples with + lower distortion than other attacks. This comes at the cost of speed, + as this attack is often much slower than others. + + :param model_fn: a callable that takes an input tensor and returns the model logits. + :param y: (optional) Tensor with target labels. + :param targeted: (optional) Targeted attack? + :param batch_size (optional): Number of attacks to run simultaneously. + :param clip_min: (optional) float. Minimum float values for adversarial example components. + :param clip_max: (optional) float. Maximum float value for adversarial example components. + :param binary_search_steps (optional): The number of times we perform binary + search to find the optimal tradeoff- + constant between norm of the purturbation + and confidence of the classification. + :param max_iterations (optional): The maximum number of iterations. Setting this + to a larger value will produce lower distortion + results. Using only a few iterations requires + a larger learning rate, and will produce larger + distortion results. + :param abort_early (optional): If true, allows early aborts if gradient descent + is unable to make progress (i.e., gets stuck in + a local minimum). + :param confidence (optional): Confidence of adversarial examples: higher produces + examples with larger l2 distortion, but more + strongly classified as adversarial. + :param initial_const (optional): The initial tradeoff-constant used to tune the + relative importance of the size of the perturbation + and confidence of classification. + If binary_search_steps is large, the initial + constant is not important. A smaller value of + this constant gives lower distortion results. + :param learning_rate (optional): The learning rate for the attack algorithm. + Smaller values produce better results but are + slower to converge. + """ + self.model_fn = model_fn + + self.batch_size = batch_size + + self.y = y + self.targeted = y is not None + + self.clip_min = clip_min + self.clip_max = clip_max + + self.binary_search_steps = binary_search_steps + self.max_iterations = max_iterations + self.abort_early = abort_early + self.learning_rate = learning_rate + + self.confidence = confidence + self.initial_const = initial_const + + # the optimizer + self.optimizer = tf.keras.optimizers.Adam(self.learning_rate) + + super(CarliniWagnerL2, self).__init__() + + def attack(self, x): + """ + Returns adversarial examples for the tensor. + :param x: input tensor. + :return: a numpy tensor with the adversarial example. + """ + adv_ex = np.zeros_like(x) + for i in range(0, len(x), self.batch_size): + adv_ex[i : i + self.batch_size] = self._attack( + x[i : i + self.batch_size] + ).numpy() + + return adv_ex + + def _attack(self, x): + if self.clip_min is not None: + if not np.all(tf.math.greater_equal(x, self.clip_min)): + raise CarliniWagnerL2Exception( + f"The input is smaller than the minimum value of {self.clip_min}r" + ) + + if self.clip_max is not None: + if not np.all(tf.math.less_equal(x, self.clip_max)): + raise CarliniWagnerL2Exception( + f"The input is greater than the maximum value of {self.clip_max}!" + ) + + y, _ = get_or_guess_labels(self.model_fn, x, y=self.y, targeted=self.targeted) + + # cast to tensor if provided as numpy array + original_x = tf.cast(x, tf.float32) + shape = original_x.shape + + if not y.shape.as_list()[0] == original_x.shape.as_list()[0]: + raise CarliniWagnerL2Exception("x and y do not have the same shape!") + + # re-scale x to [0, 1] + x = original_x + x = (x - self.clip_min) / (self.clip_max - self.clip_min) + x = tf.clip_by_value(x, 0.0, 1.0) + + # scale to [-1, 1] + x = (x * 2.0) - 1.0 + + # convert tonh-space + x = tf.atanh(x * 0.999999) + + # parameters for the binary search + lower_bound = tf.zeros(shape[:1]) + upper_bound = tf.ones(shape[:1]) * 1e10 + + const = tf.ones(shape) * self.initial_const + + # placeholder variables for best values + best_l2 = tf.fill(shape[:1], 1e10) + best_score = tf.fill(shape[:1], -1) + best_score = tf.cast(best_score, tf.int32) + best_attack = original_x + + # convience function for comparing + compare_fn = tf.equal if self.targeted else tf.not_equal + + # the perturbation + modifier = tf.Variable(tf.zeros(shape, dtype=x.dtype), trainable=True) + + for outer_step in range(self.binary_search_steps): + # at each iteration reset variable state + modifier.assign(tf.zeros(shape, dtype=x.dtype)) + for var in self.optimizer.variables(): + var.assign(tf.zeros(var.shape, dtype=var.dtype)) + + # variables to keep track in the inner loop + current_best_l2 = tf.fill(shape[:1], 1e10) + current_best_score = tf.fill(shape[:1], -1) + current_best_score = tf.cast(current_best_score, tf.int32) + + # The last iteration (if we run many steps) repeat the search once. + if ( + self.binary_search_steps >= 10 + and outer_step == self.binary_search_steps - 1 + ): + const = upper_bound + + # early stopping criteria + prev = None + + for iteration in range(self.max_iterations): + x_new, loss, preds, l2_dist = self.attack_step(x, y, modifier, const) + + # check if we made progress, abort otherwise + if ( + self.abort_early + and iteration % ((self.max_iterations // 10) or 1) == 0 + ): + if prev is not None and loss > prev * 0.9999: + break + + prev = loss + + lab = tf.argmax(y, axis=1) + + pred_with_conf = ( + preds - self.confidence + if self.targeted + else preds + self.confidence + ) + pred_with_conf = tf.argmax(pred_with_conf, axis=1) + + pred = tf.argmax(preds, axis=1) + pred = tf.cast(pred, tf.int32) + + # compute a binary mask of the tensors we want to assign + mask = tf.math.logical_and( + tf.less(l2_dist, current_best_l2), compare_fn(pred_with_conf, lab) + ) + + # all entries which evaluate to True get reassigned + current_best_l2 = set_with_mask(current_best_l2, l2_dist, mask) + current_best_score = set_with_mask(current_best_score, pred, mask) + + # if the l2 distance is better than the one found before + # and if the example is a correct example (with regards to the labels) + mask = tf.math.logical_and( + tf.less(l2_dist, best_l2), compare_fn(pred_with_conf, lab) + ) + + best_l2 = set_with_mask(best_l2, l2_dist, mask) + best_score = set_with_mask(best_score, pred, mask) + + # mask is of shape [batch_size]; best_attack is [batch_size, image_size] + # need to expand + mask = tf.reshape(mask, [-1, 1, 1, 1]) + mask = tf.tile(mask, [1, *best_attack.shape[1:]]) + + best_attack = set_with_mask(best_attack, x_new, mask) + + # adjust binary search parameters + lab = tf.argmax(y, axis=1) + lab = tf.cast(lab, tf.int32) + + # we first compute the mask for the upper bound + upper_mask = tf.math.logical_and( + compare_fn(best_score, lab), + tf.not_equal(best_score, -1), + ) + upper_bound = set_with_mask( + upper_bound, tf.math.minimum(upper_bound, const), upper_mask + ) + + # based on this mask compute const mask + const_mask = tf.math.logical_and( + upper_mask, + tf.less(upper_bound, 1e9), + ) + const = set_with_mask(const, (lower_bound + upper_bound) / 2.0, const_mask) + + # else case is the negation of the inital mask + lower_mask = tf.math.logical_not(upper_mask) + lower_bound = set_with_mask( + lower_bound, tf.math.maximum(lower_bound, const), lower_mask + ) + + const_mask = tf.math.logical_and( + lower_mask, + tf.less(upper_bound, 1e9), + ) + const = set_with_mask(const, (lower_bound + upper_bound) / 2, const_mask) + + const_mask = tf.math.logical_not(const_mask) + const = set_with_mask(const, const * 10, const_mask) + + return best_attack + + def attack_step(self, x, y, modifier, const): + x_new, grads, loss, preds, l2_dist = self.gradient(x, y, modifier, const) + + self.optimizer.apply_gradients([(grads, modifier)]) + return x_new, loss, preds, l2_dist + + @tf.function + def gradient(self, x, y, modifier, const): + # compute the actual attack + with tf.GradientTape() as tape: + adv_image = modifier + x + x_new = clip_tanh(adv_image, clip_min=self.clip_min, clip_max=self.clip_max) + preds = self.model_fn(x_new) + loss, l2_dist = loss_fn( + x=x, + x_new=x_new, + y_true=y, + y_pred=preds, + confidence=self.confidence, + const=const, + targeted=self.targeted, + clip_min=self.clip_min, + clip_max=self.clip_max, + ) + + grads = tape.gradient(loss, adv_image) + return x_new, grads, loss, preds, l2_dist + + +def l2(x, y): + # technically squarred l2 + return tf.reduce_sum(tf.square(x - y), list(range(1, len(x.shape)))) + + +def loss_fn( + x, + x_new, + y_true, + y_pred, + confidence, + const=0, + targeted=False, + clip_min=0, + clip_max=1, +): + other = clip_tanh(x, clip_min=clip_min, clip_max=clip_max) + l2_dist = l2(x_new, other) + + real = tf.reduce_sum(y_true * y_pred, 1) + other = tf.reduce_max((1.0 - y_true) * y_pred - y_true * 10_000, 1) + + if targeted: + # if targeted, optimize for making the other class most likely + loss_1 = tf.maximum(0.0, other - real + confidence) + else: + # if untargeted, optimize for making this class least likely. + loss_1 = tf.maximum(0.0, real - other + confidence) + + # sum up losses + loss_2 = tf.reduce_sum(l2_dist) + loss_1 = tf.reduce_sum(const * loss_1) + loss = loss_1 + loss_2 + return loss, l2_dist + + +def clip_tanh(x, clip_min, clip_max): + return ((tf.tanh(x) + 1) / 2) * (clip_max - clip_min) + clip_min diff --git a/cleverhans/tf2/attacks/fast_gradient_method.py b/cleverhans/tf2/attacks/fast_gradient_method.py new file mode 100644 index 000000000..ae3210f20 --- /dev/null +++ b/cleverhans/tf2/attacks/fast_gradient_method.py @@ -0,0 +1,76 @@ +"""The Fast Gradient Method attack.""" + +import numpy as np +import tensorflow as tf + +from cleverhans.tf2.utils import optimize_linear, compute_gradient + + +def fast_gradient_method( + model_fn, + x, + eps, + norm, + loss_fn=None, + clip_min=None, + clip_max=None, + y=None, + targeted=False, + sanity_checks=False, +): + """ + Tensorflow 2.0 implementation of the Fast Gradient Method. + :param model_fn: a callable that takes an input tensor and returns the model logits. + :param x: input tensor. + :param eps: epsilon (input variation parameter); see https://arxiv.org/abs/1412.6572. + :param norm: Order of the norm (mimics NumPy). Possible values: np.inf, 1 or 2. + :param loss_fn: (optional) callable. Loss function that takes (labels, logits) as arguments and returns loss. + default function is 'tf.nn.sparse_softmax_cross_entropy_with_logits' + :param clip_min: (optional) float. Minimum float value for adversarial example components. + :param clip_max: (optional) float. Maximum float value for adversarial example components. + :param y: (optional) Tensor with true labels. If targeted is true, then provide the + target label. Otherwise, only provide this parameter if you'd like to use true + labels when crafting adversarial samples. Otherwise, model predictions are used + as labels to avoid the "label leaking" effect (explained in this paper: + https://arxiv.org/abs/1611.01236). Default is None. + :param targeted: (optional) bool. Is the attack targeted or untargeted? + Untargeted, the default, will try to make the label incorrect. + Targeted will instead try to move in the direction of being more like y. + :param sanity_checks: bool, if True, include asserts (Turn them off to use less runtime / + memory or for unit tests that intentionally pass strange input) + :return: a tensor for the adversarial example + """ + if norm not in [np.inf, 1, 2]: + raise ValueError("Norm order must be either np.inf, 1, or 2.") + + if loss_fn is None: + loss_fn = tf.nn.sparse_softmax_cross_entropy_with_logits + + asserts = [] + + # If a data range was specified, check that the input was in that range + if clip_min is not None: + asserts.append(tf.math.greater_equal(x, clip_min)) + + if clip_max is not None: + asserts.append(tf.math.less_equal(x, clip_max)) + + if y is None: + # Using model predictions as ground truth to avoid label leaking + y = tf.argmax(model_fn(x), 1) + + grad = compute_gradient(model_fn, loss_fn, x, y, targeted) + + optimal_perturbation = optimize_linear(grad, eps, norm) + # Add perturbation to original example to obtain adversarial example + adv_x = x + optimal_perturbation + + # If clipping is needed, reset all values outside of [clip_min, clip_max] + if (clip_min is not None) or (clip_max is not None): + # We don't currently support one-sided clipping + assert clip_min is not None and clip_max is not None + adv_x = tf.clip_by_value(adv_x, clip_min, clip_max) + + if sanity_checks: + assert np.all(asserts) + return adv_x diff --git a/cleverhans/tf2/attacks/madry_et_al.py b/cleverhans/tf2/attacks/madry_et_al.py new file mode 100644 index 000000000..4682bd3ec --- /dev/null +++ b/cleverhans/tf2/attacks/madry_et_al.py @@ -0,0 +1,39 @@ +""" +The MadryEtAl attack +""" + +from cleverhans.tf2.attacks.projected_gradient_descent import projected_gradient_descent + + +def madry_et_al( + model_fn, + x, + eps, + eps_iter, + nb_iter, + norm, + clip_min=None, + clip_max=None, + y=None, + targeted=False, + rand_minmax=0.3, + sanity_checks=True, +): + """ + The attack from Madry et al 2017 + """ + return projected_gradient_descent( + model_fn, + x, + eps, + eps_iter, + nb_iter, + norm, + clip_min=clip_min, + clip_max=clip_max, + y=y, + targeted=targeted, + rand_init=True, + rand_minmax=rand_minmax, + sanity_checks=sanity_checks, + ) diff --git a/cleverhans/tf2/attacks/momentum_iterative_method.py b/cleverhans/tf2/attacks/momentum_iterative_method.py new file mode 100644 index 000000000..16186d413 --- /dev/null +++ b/cleverhans/tf2/attacks/momentum_iterative_method.py @@ -0,0 +1,117 @@ +"""The MomentumIterativeMethod attack.""" + +import numpy as np +import tensorflow as tf + +from cleverhans.tf2.utils import optimize_linear, compute_gradient +from cleverhans.tf2.utils import clip_eta + + +def momentum_iterative_method( + model_fn, + x, + eps=0.3, + eps_iter=0.06, + nb_iter=10, + norm=np.inf, + clip_min=None, + clip_max=None, + y=None, + targeted=False, + decay_factor=1.0, + sanity_checks=True, +): + """ + Tensorflow 2.0 implementation of Momentum Iterative Method (Dong et al. 2017). + This method won the first places in NIPS 2017 Non-targeted Adversarial Attacks + and Targeted Adversarial Attacks. The original paper used hard labels + for this attack; no label smoothing. + Paper link: https://arxiv.org/pdf/1710.06081.pdf + :param model_fn: a callable that takes an input tensor and returns the model logits. + :param x: input tensor. + :param eps: (optional float) maximum distortion of adversarial example + compared to original input + :param eps_iter: (optional float) step size for each attack iteration + :param nb_iter: (optional int) Number of attack iterations. + :param norm: (optional) Order of the norm (mimics Numpy). + Possible values: np.inf, 1 or 2. + :param clip_min: (optional float) Minimum input component value + :param clip_max: (optional float) Maximum input component value + :param y: (optional) Tensor with true labels. If targeted is true, then provide the + target label. Otherwise, only provide this parameter if you'd like to use true + labels when crafting adversarial samples. Otherwise, model predictions are used + as labels to avoid the "label leaking" effect (explained in this paper: + https://arxiv.org/abs/1611.01236). Default is None. + :param targeted: (optional) bool. Is the attack targeted or untargeted? + Untargeted, the default, will try to make the label incorrect. + Targeted will instead try to move in the direction of being more like y. + :param decay_factor: (optional) Decay factor for the momentum term. + :param sanity_checks: bool, if True, include asserts (Turn them off to use less runtime / + memory or for unit tests that intentionally pass strange input) + :return: a tensor for the adversarial example + """ + + if norm == 1: + raise NotImplementedError( + "This attack hasn't been tested for norm=1." + "It's not clear that FGM makes a good inner " + "loop step for iterative optimization since " + "it updates just one coordinate at a time." + ) + + # Check if order of the norm is acceptable given current implementation + if norm not in [np.inf, 1, 2]: + raise ValueError("Norm order must be either np.inf, 1, or 2.") + + asserts = [] + + # If a data range was specified, check that the input was in that range + if clip_min is not None: + asserts.append(tf.math.greater_equal(x, clip_min)) + + if clip_max is not None: + asserts.append(tf.math.less_equal(x, clip_max)) + + if y is None: + # Using model predictions as ground truth to avoid label leaking + y = tf.argmax(model_fn(x), 1) + + # Initialize loop variables + momentum = tf.zeros_like(x) + adv_x = x + + i = 0 + while i < nb_iter: + # Define gradient of loss wrt input + grad = compute_gradient(model_fn, loss_fn, adv_x, y, targeted) + + # Normalize current gradient and add it to the accumulated gradient + red_ind = list(range(1, len(grad.shape))) + avoid_zero_div = tf.cast(1e-12, grad.dtype) + grad = grad / tf.math.maximum( + avoid_zero_div, + tf.math.reduce_mean(tf.math.abs(grad), red_ind, keepdims=True), + ) + momentum = decay_factor * momentum + grad + + optimal_perturbation = optimize_linear(momentum, eps_iter, norm) + # Update and clip adversarial example in current iteration + adv_x = adv_x + optimal_perturbation + adv_x = x + clip_eta(adv_x - x, norm, eps) + + if clip_min is not None and clip_max is not None: + adv_x = tf.clip_by_value(adv_x, clip_min, clip_max) + i += 1 + + if sanity_checks: + assert np.all(asserts) + + return adv_x + + +def loss_fn(labels, logits): + """ + Added softmax cross entropy loss for MIM as in the original MI-FGSM paper. + """ + + return tf.nn.sparse_softmax_cross_entropy_with_logits(labels, logits, name=None) diff --git a/cleverhans/tf2/attacks/projected_gradient_descent.py b/cleverhans/tf2/attacks/projected_gradient_descent.py new file mode 100644 index 000000000..b3686f000 --- /dev/null +++ b/cleverhans/tf2/attacks/projected_gradient_descent.py @@ -0,0 +1,138 @@ +"""The Projected Gradient Descent attack.""" + +import numpy as np +import tensorflow as tf + +from cleverhans.tf2.attacks.fast_gradient_method import fast_gradient_method +from cleverhans.tf2.utils import clip_eta, random_lp_vector + + +def projected_gradient_descent( + model_fn, + x, + eps, + eps_iter, + nb_iter, + norm, + loss_fn=None, + clip_min=None, + clip_max=None, + y=None, + targeted=False, + rand_init=None, + rand_minmax=None, + sanity_checks=False, +): + """ + This class implements either the Basic Iterative Method + (Kurakin et al. 2016) when rand_init is set to 0. or the + Madry et al. (2017) method when rand_minmax is larger than 0. + Paper link (Kurakin et al. 2016): https://arxiv.org/pdf/1607.02533.pdf + Paper link (Madry et al. 2017): https://arxiv.org/pdf/1706.06083.pdf + :param model_fn: a callable that takes an input tensor and returns the model logits. + :param x: input tensor. + :param eps: epsilon (input variation parameter); see https://arxiv.org/abs/1412.6572. + :param eps_iter: step size for each attack iteration + :param nb_iter: Number of attack iterations. + :param norm: Order of the norm (mimics NumPy). Possible values: np.inf, 1 or 2. + :param loss_fn: (optional) callable. loss function that takes (labels, logits) as arguments and returns loss. + default function is 'tf.nn.sparse_softmax_cross_entropy_with_logits' + :param clip_min: (optional) float. Minimum float value for adversarial example components. + :param clip_max: (optional) float. Maximum float value for adversarial example components. + :param y: (optional) Tensor with true labels. If targeted is true, then provide the + target label. Otherwise, only provide this parameter if you'd like to use true + labels when crafting adversarial samples. Otherwise, model predictions are used + as labels to avoid the "label leaking" effect (explained in this paper: + https://arxiv.org/abs/1611.01236). Default is None. + :param targeted: (optional) bool. Is the attack targeted or untargeted? + Untargeted, the default, will try to make the label incorrect. + Targeted will instead try to move in the direction of being more like y. + :param rand_init: (optional) float. Start the gradient descent from a point chosen + uniformly at random in the norm ball of radius + rand_init_eps + :param rand_minmax: (optional) float. Size of the norm ball from which + the initial starting point is chosen. Defaults to eps + :param sanity_checks: bool, if True, include asserts (Turn them off to use less runtime / + memory or for unit tests that intentionally pass strange input) + :return: a tensor for the adversarial example + """ + + assert eps_iter <= eps, (eps_iter, eps) + if norm == 1: + raise NotImplementedError( + "It's not clear that FGM is a good inner loop" + " step for PGD when norm=1, because norm=1 FGM " + " changes only one pixel at a time. We need " + " to rigorously test a strong norm=1 PGD " + "before enabling this feature." + ) + if norm not in [np.inf, 2]: + raise ValueError("Norm order must be either np.inf or 2.") + + if loss_fn is None: + loss_fn = tf.nn.sparse_softmax_cross_entropy_with_logits + + asserts = [] + + # If a data range was specified, check that the input was in that range + if clip_min is not None: + asserts.append(tf.math.greater_equal(x, clip_min)) + + if clip_max is not None: + asserts.append(tf.math.less_equal(x, clip_max)) + + # Initialize loop variables + if rand_minmax is None: + rand_minmax = eps + + if rand_init: + eta = random_lp_vector( + tf.shape(x), norm, tf.cast(rand_minmax, x.dtype), dtype=x.dtype + ) + else: + eta = tf.zeros_like(x) + + # Clip eta + eta = clip_eta(eta, norm, eps) + adv_x = x + eta + if clip_min is not None or clip_max is not None: + adv_x = tf.clip_by_value(adv_x, clip_min, clip_max) + + if y is None: + # Using model predictions as ground truth to avoid label leaking + y = tf.argmax(model_fn(x), 1) + + i = 0 + while i < nb_iter: + adv_x = fast_gradient_method( + model_fn, + adv_x, + eps_iter, + norm, + loss_fn, + clip_min=clip_min, + clip_max=clip_max, + y=y, + targeted=targeted, + ) + + # Clipping perturbation eta to norm norm ball + eta = adv_x - x + eta = clip_eta(eta, norm, eps) + adv_x = x + eta + + # Redo the clipping. + # FGM already did it, but subtracting and re-adding eta can add some + # small numerical error. + if clip_min is not None or clip_max is not None: + adv_x = tf.clip_by_value(adv_x, clip_min, clip_max) + i += 1 + + asserts.append(eps_iter <= eps) + if norm == np.inf and clip_min is not None: + # TODO necessary to cast to x.dtype? + asserts.append(eps + clip_min <= clip_max) + + if sanity_checks: + assert np.all(asserts) + return adv_x diff --git a/cleverhans/tf2/attacks/spsa.py b/cleverhans/tf2/attacks/spsa.py new file mode 100644 index 000000000..c900d5f6e --- /dev/null +++ b/cleverhans/tf2/attacks/spsa.py @@ -0,0 +1,434 @@ +# pylint: disable=missing-docstring + +import tensorflow as tf + +tf_dtype = tf.as_dtype("float32") + + +def spsa( + model_fn, + x, + y, + eps, + nb_iter, + clip_min=None, + clip_max=None, + targeted=False, + early_stop_loss_threshold=None, + learning_rate=0.01, + delta=0.01, + spsa_samples=128, + spsa_iters=1, + is_debug=False, +): + """Tensorflow 2.0 implementation of SPSA. + + This implements the SPSA adversary, as in https://arxiv.org/abs/1802.05666 (Uesato et al. 2018). + SPSA is a gradient-free optimization method, which is useful when the model is non-differentiable, + or more generally, the gradients do not point in useful directions. + :param model_fn: A callable that takes an input tensor and returns the model logits. + :param x: Input tensor. + :param y: Tensor with true labels. If targeted is true, then provide the target label. + :param eps: The size of the maximum perturbation, measured in the L-infinity norm. + :param nb_iter: The number of optimization steps. + :param clip_min: If specified, the minimum input value. + :param clip_max: If specified, the maximum input value. + :param targeted: (optional) bool. Is the attack targeted or untargeted? Untargeted, the default, + will try to make the label incorrect. Targeted will instead try to move in the direction + of being more like y. + :param early_stop_loss_threshold: A float or None. If specified, the attack will end as soon as + the loss is below `early_stop_loss_threshold`. + :param learning_rate: Learning rate of ADAM optimizer. + :param delta: Perturbation size used for SPSA approximation. + :param spsa_samples: Number of inputs to evaluate at a single time. The true batch size + (the number of evaluated inputs for each update) is `spsa_samples * + spsa_iters` + :param spsa_iters: Number of model evaluations before performing an update, where each evaluation + is on `spsa_samples` different inputs. + :param is_debug: If True, print the adversarial loss after each update. + """ + if x.get_shape().as_list()[0] != 1: + raise ValueError("For SPSA, input tensor x must have batch_size of 1.") + + optimizer = SPSAAdam( + lr=learning_rate, delta=delta, num_samples=spsa_samples, num_iters=spsa_iters + ) + + def loss_fn(x, label): + """ + Margin logit loss, with correct sign for targeted vs untargeted loss. + """ + logits = model_fn(x) + loss_multiplier = 1 if targeted else -1 + return loss_multiplier * margin_logit_loss( + logits, label, nb_classes=logits.get_shape()[-1] + ) + + adv_x = projected_optimization( + loss_fn, + x, + y, + eps, + nb_iter, + optimizer, + clip_min, + clip_max, + early_stop_loss_threshold, + is_debug=is_debug, + ) + + return adv_x + + +class SPSAAdam(tf.optimizers.Adam): + """Optimizer for gradient-free attacks in https://arxiv.org/abs/1802.05666. + + Gradients estimates are computed using Simultaneous Perturbation Stochastic Approximation (SPSA), + combined with the ADAM update rule (https://arxiv.org/abs/1412.6980). + """ + + def __init__( + self, + lr=0.01, + delta=0.01, + num_samples=128, + num_iters=1, + compare_to_analytic_grad=False, + ): + super(SPSAAdam, self).__init__(lr=lr) + assert num_samples % 2 == 0, "number of samples must be even" + self._delta = delta + self._num_samples = num_samples // 2 # Since we mirror +/- delta later + self._num_iters = num_iters + self._compare_to_analytic_grad = compare_to_analytic_grad + + def _get_delta(self, x, delta): + x_shape = x.get_shape().as_list() + delta_x = delta * tf.sign( + tf.random.uniform( + [self._num_samples] + x_shape[1:], + minval=-1.0, + maxval=1.0, + dtype=tf_dtype, + ) + ) + return delta_x + + def _compute_gradients(self, loss_fn, x): + """Compute a new value of `x` to minimize `loss_fn` using SPSA. + + Args: + loss_fn: a callable that takes `x`, a batch of images, and returns a batch of loss values. + `x` will be optimized to minimize `loss_fn(x)`. + x: A list of Tensors, the values to be updated. This is analogous to the `var_list` argument + in standard TF Optimizer. + + Returns: + new_x: A list of Tensors, the same length as `x`, which are updated + new_optim_state: A dict, with the same structure as `optim_state`, which have been updated. + """ + + # Assumes `x` is a list, containing a [1, H, W, C] image.If static batch dimension is None, + # tf.reshape to batch size 1 so that static shape can be inferred. + assert len(x) == 1 + static_x_shape = x[0].get_shape().as_list() + if static_x_shape[0] is None: + x[0] = tf.reshape(x[0], [1] + static_x_shape[1:]) + assert x[0].get_shape().as_list()[0] == 1 + x = x[0] + x_shape = x.get_shape().as_list() + + def body(i, grad_array): + delta = self._delta + delta_x = self._get_delta(x, delta) + delta_x = tf.concat([delta_x, -delta_x], axis=0) + loss_vals = tf.reshape( + loss_fn(x + delta_x), [2 * self._num_samples] + [1] * (len(x_shape) - 1) + ) + avg_grad = tf.reduce_mean(loss_vals * delta_x, axis=0) / delta + avg_grad = tf.expand_dims(avg_grad, axis=0) + new_grad_array = grad_array.write(i, avg_grad) + return i + 1, new_grad_array + + def cond(i, _): + return i < self._num_iters + + _, all_grads = tf.while_loop( + cond, + body, + loop_vars=[0, tf.TensorArray(size=self._num_iters, dtype=tf_dtype)], + back_prop=False, + parallel_iterations=1, + ) + avg_grad = tf.reduce_sum(all_grads.stack(), axis=0) + return [avg_grad] + + def _apply_gradients(self, grads, x, optim_state): + """Given a gradient, make one optimization step. + + :param grads: list of tensors, same length as `x`, containing the corresponding gradients + :param x: list of tensors to update + :param optim_state: dict + + Returns: + new_x: list of tensors, updated version of `x` + new_optim_state: dict, updated version of `optim_state` + """ + + new_x = [None] * len(x) + new_optim_state = { + "t": optim_state["t"] + 1.0, + "m": [None] * len(x), + "u": [None] * len(x), + } + t = new_optim_state["t"] + for i in range(len(x)): + g = grads[i] + m_old = optim_state["m"][i] + u_old = optim_state["u"][i] + new_optim_state["m"][i] = self.beta_1 * m_old + (1.0 - self.beta_1) * g + new_optim_state["u"][i] = self.beta_2 * u_old + (1.0 - self.beta_2) * g * g + m_hat = new_optim_state["m"][i] / (1.0 - tf.pow(self.beta_1, t)) + u_hat = new_optim_state["u"][i] / (1.0 - tf.pow(self.beta_2, t)) + new_x[i] = x[i] - self.lr * m_hat / (tf.sqrt(u_hat) + self.epsilon) + return new_x, new_optim_state + + def init_state(self, x): + """Initialize t, m, and u""" + optim_state = { + "t": 0.0, + "m": [tf.zeros_like(v) for v in x], + "u": [tf.zeros_like(v) for v in x], + } + return optim_state + + def minimize(self, loss_fn, x, optim_state): + """Analogous to tf.Optimizer.minimize + + :param loss_fn: tf Tensor, representing the loss to minimize + :param x: list of Tensor, analogous to tf.Optimizer's var_list + :param optim_state: A possibly nested dict, containing any optimizer state. + + Returns: + new_x: list of Tensor, updated version of `x` + new_optim_state: dict, updated version of `optim_state` + """ + grads = self._compute_gradients(loss_fn, x) + return self._apply_gradients(grads, x, optim_state) + + +def margin_logit_loss(model_logits, label, nb_classes=10): + """Computes difference between logit for `label` and next highest logit. + + The loss is high when `label` is unlikely (targeted by default). This follows the same interface + as `loss_fn` for projected_optimization, i.e. it returns a batch of loss values. + """ + + if "int" in str(label.dtype): + logit_mask = tf.one_hot(label, depth=nb_classes, axis=-1) + else: + logit_mask = label + if "int" in str(logit_mask.dtype): + logit_mask = tf.cast(logit_mask, dtype=tf.float32) + try: + label_logits = tf.reduce_sum(logit_mask * model_logits, axis=-1) + except TypeError: + raise TypeError( + "Could not take row-wise dot product between logit mask, of dtype " + + str(logit_mask.dtype) + + " and model_logits, of dtype " + + str(model_logits.dtype) + ) + logits_with_target_label_neg_inf = model_logits - logit_mask * 99999 + highest_nonlabel_logits = tf.reduce_max(logits_with_target_label_neg_inf, axis=-1) + loss = highest_nonlabel_logits - label_logits + return loss + + +def _project_perturbation( + perturbation, epsilon, input_image, clip_min=None, clip_max=None +): + """ + Project `perturbation` onto L-infinity ball of radius `epsilon`. Also project into hypercube such + that the resulting adversarial example is between clip_min and clip_max, if applicable. + """ + + if clip_min is None or clip_max is None: + raise NotImplementedError( + "_project_perturbation currently has clipping hard-coded in." + ) + + # Ensure inputs are in the correct range + with tf.control_dependencies( + [ + tf.debugging.assert_less_equal( + input_image, tf.cast(clip_max, input_image.dtype) + ), + tf.debugging.assert_greater_equal( + input_image, tf.cast(clip_min, input_image.dtype) + ), + ] + ): + clipped_perturbation = tf.clip_by_value(perturbation, -epsilon, epsilon) + new_image = tf.clip_by_value( + input_image + clipped_perturbation, clip_min, clip_max + ) + return new_image - input_image + + +def projected_optimization( + loss_fn, + input_image, + label, + epsilon, + num_steps, + optimizer, + clip_min=None, + clip_max=None, + early_stop_loss_threshold=None, + project_perturbation=_project_perturbation, + is_debug=False, +): + """ + Generic projected optimization, generalized to work with approximate gradients. Used for e.g. + the SPSA attack. + + Args: + :param loss_fn: A callable which takes `input_image` and `label` as + arguments, and returns a batch of loss values. + :param input_image: Tensor, a batch of images + :param label: Tensor, a batch of labels + :param epsilon: float, the L-infinity norm of the maximum allowable + perturbation + :param num_steps: int, the number of steps of gradient descent + :param optimizer: A `SPSAAdam` object + :param clip_min: float, minimum pixel value + :param clip_max: float, maximum pixel value + :param project_perturbation: A function, which will be used to enforce + some constraint. It should have the same + signature as `_project_perturbation`. + :param early_stop_loss_threshold: A float or None. If specified, the attack will end if the loss is below + `early_stop_loss_threshold`. + Enabling this option can have several different effects: + - Setting the threshold to 0. guarantees that if a successful attack is found, it is returned. + This increases the attack success rate, because without early stopping the optimizer can accidentally + bounce back to a point where the attack fails. + - Early stopping can make the attack run faster because it may run for fewer steps. + - Early stopping can make the attack run slower because the loss must be calculated at each step. + The loss is not calculated as part of the normal SPSA optimization procedure. + For most reasonable choices of hyperparameters, early stopping makes the attack much faster because + it decreases the number of steps dramatically. + :param is_debug: A bool. If True, print debug info for attack progress. + + Returns: + adversarial version of `input_image`, with L-infinity difference less than epsilon, which tries + to minimize loss_fn. + + Note that this function is not intended as an Attack by itself. Rather, it is designed as a helper + function which you can use to write your own attack methods. The method uses a tf.while_loop to + optimize a loss function in a single sess.run() call. + """ + assert num_steps is not None + if is_debug: + with tf.device("/cpu:0"): + tf.print("Starting PGD attack with epsilon: %s" % epsilon) + + init_perturbation = tf.random.uniform( + tf.shape(input_image), + minval=tf.cast(-epsilon, input_image.dtype), + maxval=tf.cast(epsilon, input_image.dtype), + dtype=input_image.dtype, + ) + init_perturbation = project_perturbation( + init_perturbation, epsilon, input_image, clip_min=clip_min, clip_max=clip_max + ) + init_optim_state = optimizer.init_state([init_perturbation]) + + def loop_body(i, perturbation, flat_optim_state): + """Update perturbation to input image.""" + optim_state = tf.nest.pack_sequence_as( + structure=init_optim_state, flat_sequence=flat_optim_state + ) + + def wrapped_loss_fn(x): + return loss_fn(input_image + x, label) + + new_perturbation_list, new_optim_state = optimizer.minimize( + wrapped_loss_fn, [perturbation], optim_state + ) + projected_perturbation = project_perturbation( + new_perturbation_list[0], + epsilon, + input_image, + clip_min=clip_min, + clip_max=clip_max, + ) + + # Be careful with this bool. A value of 0. is a valid threshold but evaluates to False, so we + # must explicitly check whether the value is None. + early_stop = early_stop_loss_threshold is not None + compute_loss = is_debug or early_stop + # Don't waste time building the loss graph if we're not going to use it + if compute_loss: + # NOTE: this step is not actually redundant with the optimizer step. + # SPSA calculates the loss at randomly perturbed points but doesn't calculate the loss at the current point. + loss = tf.reduce_mean(wrapped_loss_fn(projected_perturbation), axis=0) + + if is_debug: + with tf.device("/cpu:0"): + tf.print(loss, "Total batch loss") + + if early_stop: + i = tf.cond( + tf.less(loss, early_stop_loss_threshold), + lambda: float(num_steps), + lambda: i, + ) + + return i + 1, projected_perturbation, tf.nest.flatten(new_optim_state) + + def cond(i, *_): + return tf.less(i, num_steps) + + flat_init_optim_state = tf.nest.flatten(init_optim_state) + _, final_perturbation, _ = tf.while_loop( + cond, + loop_body, + loop_vars=(tf.constant(0.0), init_perturbation, flat_init_optim_state), + parallel_iterations=1, + back_prop=False, + maximum_iterations=num_steps, + ) + + if project_perturbation is _project_perturbation: + # TODO: this assert looks totally wrong. + # Not bothering to fix it now because it's only an assert. + # 1) Multiplying by 1.1 gives a huge margin of error. This should probably take the difference + # and allow a tolerance of 1e-6 or something like that. + # 2) I think it should probably check the *absolute value* of final_perturbation + perturbation_max = epsilon * 1.1 + check_diff = tf.debugging.assert_less_equal( + final_perturbation, + tf.cast(perturbation_max, final_perturbation.dtype), + message="final_perturbation must change no pixel by more than %s" + % perturbation_max, + ) + else: + # TODO: let caller pass in a check_diff function as well as + # project_perturbation + check_diff = tf.no_op() + + if clip_min is None or clip_max is None: + raise NotImplementedError("This function only supports clipping for now") + check_range = [ + tf.debugging.assert_less_equal( + input_image, tf.cast(clip_max, input_image.dtype) + ), + tf.debugging.assert_greater_equal( + input_image, tf.cast(clip_min, input_image.dtype) + ), + ] + + with tf.control_dependencies([check_diff] + check_range): + adversarial_image = input_image + final_perturbation + return tf.stop_gradient(adversarial_image) diff --git a/cleverhans/tf2/utils.py b/cleverhans/tf2/utils.py new file mode 100644 index 000000000..cb4a69cb1 --- /dev/null +++ b/cleverhans/tf2/utils.py @@ -0,0 +1,242 @@ +import numpy as np +import tensorflow as tf + + +def clip_eta(eta, norm, eps): + """ + Helper function to clip the perturbation to epsilon norm ball. + :param eta: A tensor with the current perturbation. + :param norm: Order of the norm (mimics Numpy). + Possible values: np.inf, 1 or 2. + :param eps: Epsilon, bound of the perturbation. + """ + + # Clipping perturbation eta to self.norm norm ball + if norm not in [np.inf, 1, 2]: + raise ValueError("norm must be np.inf, 1, or 2.") + axis = list(range(1, len(eta.get_shape()))) + avoid_zero_div = 1e-12 + if norm == np.inf: + eta = tf.clip_by_value(eta, -eps, eps) + else: + if norm == 1: + raise NotImplementedError("") + # This is not the correct way to project on the L1 norm ball: + # norm = tf.maximum(avoid_zero_div, reduce_sum(tf.abs(eta), reduc_ind, keepdims=True)) + elif norm == 2: + # avoid_zero_div must go inside sqrt to avoid a divide by zero in the gradient through this operation + norm = tf.sqrt( + tf.maximum( + avoid_zero_div, tf.reduce_sum(tf.square(eta), axis, keepdims=True) + ) + ) + # We must *clip* to within the norm ball, not *normalize* onto the surface of the ball + factor = tf.minimum(1.0, tf.math.divide(eps, norm)) + eta = eta * factor + return eta + + +def random_exponential(shape, rate=1.0, dtype=tf.float32, seed=None): + """ + Helper function to sample from the exponential distribution, which is not + included in core TensorFlow. + + shape: shape of the sampled tensor. + :rate: (optional) rate parameter of the exponential distribution, defaults to 1.0. + :dtype: (optional) data type of the sempled tensor, defaults to tf.float32. + :seed: (optional) custom seed to be used for sampling. + """ + return tf.random.gamma(shape, alpha=1, beta=1.0 / rate, dtype=dtype, seed=seed) + + +def random_laplace(shape, loc=0.0, scale=1.0, dtype=tf.float32, seed=None): + """ + Helper function to sample from the Laplace distribution, which is not + included in core TensorFlow. + + :shape: shape of the sampled tensor. + :loc: (optional) mean of the laplace distribution, defaults to 0.0. + :scale: (optional) scale parameter of the laplace diustribution, defaults to 1.0. + :dtype: (optional) data type of the sempled tensor, defaults to tf.float32. + :seed: (optional) custom seed to be used for sampling. + """ + z1 = random_exponential(shape, 1.0 / scale, dtype=dtype, seed=seed) + z2 = random_exponential(shape, 1.0 / scale, dtype=dtype, seed=seed) + return z1 - z2 + loc + + +def random_lp_vector(shape, ord, eps, dtype=tf.float32, seed=None): + """ + Helper function to generate uniformly random vectors from a norm ball of + radius epsilon. + :param shape: Output shape of the random sample. The shape is expected to be + of the form `(n, d1, d2, ..., dn)` where `n` is the number of + i.i.d. samples that will be drawn from a norm ball of dimension + `d1*d1*...*dn`. + :param ord: Order of the norm (mimics Numpy). + Possible values: np.inf, 1 or 2. + :param eps: Epsilon, radius of the norm ball. + :param dtype: (optional) type of the tensor. + :param seed: (optional) integer. + """ + if ord not in [np.inf, 1, 2]: + raise ValueError("ord must be np.inf, 1, or 2.") + + if ord == np.inf: + r = tf.random.uniform(shape, -eps, eps, dtype=dtype, seed=seed) + else: + + # For ord=1 and ord=2, we use the generic technique from + # (Calafiore et al. 1998) to sample uniformly from a norm ball. + # Paper link (Calafiore et al. 1998): + # https://ieeexplore.ieee.org/stamp/stamp.jsp?arnumber=758215&tag=1 + # We first sample from the surface of the norm ball, and then scale by + # a factor `w^(1/d)` where `w~U[0,1]` is a standard uniform random variable + # and `d` is the dimension of the ball. In high dimensions, this is roughly + # equivalent to sampling from the surface of the ball. + + dim = tf.reduce_prod(shape[1:]) + + if ord == 1: + x = random_laplace( + (shape[0], dim), loc=1.0, scale=1.0, dtype=dtype, seed=seed + ) + norm = tf.reduce_sum(tf.abs(x), axis=-1, keepdims=True) + elif ord == 2: + x = tf.random.normal((shape[0], dim), dtype=dtype, seed=seed) + norm = tf.sqrt(tf.reduce_sum(tf.square(x), axis=-1, keepdims=True)) + else: + raise ValueError("ord must be np.inf, 1, or 2.") + + w = tf.pow( + tf.random.uniform((shape[0], 1), dtype=dtype, seed=seed), + 1.0 / tf.cast(dim, dtype), + ) + r = eps * tf.reshape(w * x / norm, shape) + + return r + + +def get_or_guess_labels(model_fn, x, y=None, targeted=False): + """ + Helper function to get the label to use in generating an + adversarial example for x. + If 'y' is not None, then use these labels. + If 'targeted' is True, then assume it's a targeted attack + and y must be set. + Otherwise, use the model's prediction as the label and perform an + untargeted attack + :param model_fn: a callable that takes an input tensor and returns the model logits. + :param x: input tensor. + """ + if targeted is True and y is None: + raise ValueError("Must provide y for a targeted attack!") + + preds = model_fn(x) + nb_classes = preds.shape[-1] + + # labels set by the user + if y is not None: + # inefficient when y is a tensor, but this function only get called once + y = np.asarray(y) + + if len(y.shape) == 1: + # the user provided categorical encoding + y = tf.one_hot(y, nb_classes) + + y = tf.cast(y, x.dtype) + return y, nb_classes + + # must be an untargeted attack + labels = tf.cast( + tf.equal(tf.reduce_max(preds, axis=1, keepdims=True), preds), x.dtype + ) + + return labels, nb_classes + + +def set_with_mask(x, x_other, mask): + """Helper function which returns a tensor similar to x with all the values + of x replaced by x_other where the mask evaluates to true. + """ + mask = tf.cast(mask, x.dtype) + ones = tf.ones_like(mask, dtype=x.dtype) + return x_other * mask + x * (ones - mask) + + +# Due to performance reasons, this function is wrapped inside of tf.function decorator. +# Not using the decorator here, or letting the user wrap the attack in tf.function is way +# slower on Tensorflow 2.0.0-alpha0. +@tf.function +def compute_gradient(model_fn, loss_fn, x, y, targeted): + """ + Computes the gradient of the loss with respect to the input tensor. + :param model_fn: a callable that takes an input tensor and returns the model logits. + :param loss_fn: loss function that takes (labels, logits) as arguments and returns loss. + :param x: input tensor + :param y: Tensor with true labels. If targeted is true, then provide the target label. + :param targeted: bool. Is the attack targeted or untargeted? Untargeted, the default, will + try to make the label incorrect. Targeted will instead try to move in the + direction of being more like y. + :return: A tensor containing the gradient of the loss with respect to the input tensor. + """ + + with tf.GradientTape() as g: + g.watch(x) + # Compute loss + loss = loss_fn(labels=y, logits=model_fn(x)) + if ( + targeted + ): # attack is targeted, minimize loss of target label rather than maximize loss of correct label + loss = -loss + + # Define gradient of loss wrt input + grad = g.gradient(loss, x) + return grad + + +def optimize_linear(grad, eps, norm=np.inf): + """ + Solves for the optimal input to a linear function under a norm constraint. + + Optimal_perturbation = argmax_{eta, ||eta||_{norm} < eps} dot(eta, grad) + + :param grad: tf tensor containing a batch of gradients + :param eps: float scalar specifying size of constraint region + :param norm: int specifying order of norm + :returns: + tf tensor containing optimal perturbation + """ + + # Convert the iterator returned by `range` into a list. + axis = list(range(1, len(grad.get_shape()))) + avoid_zero_div = 1e-12 + if norm == np.inf: + # Take sign of gradient + optimal_perturbation = tf.sign(grad) + # The following line should not change the numerical results. It applies only because + # `optimal_perturbation` is the output of a `sign` op, which has zero derivative anyway. + # It should not be applied for the other norms, where the perturbation has a non-zero derivative. + optimal_perturbation = tf.stop_gradient(optimal_perturbation) + elif norm == 1: + abs_grad = tf.abs(grad) + sign = tf.sign(grad) + max_abs_grad = tf.reduce_max(abs_grad, axis, keepdims=True) + tied_for_max = tf.dtypes.cast( + tf.equal(abs_grad, max_abs_grad), dtype=tf.float32 + ) + num_ties = tf.reduce_sum(tied_for_max, axis, keepdims=True) + optimal_perturbation = sign * tied_for_max / num_ties + elif norm == 2: + square = tf.maximum( + avoid_zero_div, tf.reduce_sum(tf.square(grad), axis, keepdims=True) + ) + optimal_perturbation = grad / tf.sqrt(square) + else: + raise NotImplementedError( + "Only L-inf, L1 and L2 norms are currently implemented." + ) + + # Scale perturbation to be the solution for the norm=eps rather than norm=1 problem + scaled_perturbation = tf.multiply(eps, optimal_perturbation) + return scaled_perturbation diff --git a/cleverhans/future/tf2/__init__.py b/cleverhans/torch/__init__.py similarity index 100% rename from cleverhans/future/tf2/__init__.py rename to cleverhans/torch/__init__.py diff --git a/cleverhans/torch/attacks/__init__.py b/cleverhans/torch/attacks/__init__.py new file mode 100644 index 000000000..96e495e19 --- /dev/null +++ b/cleverhans/torch/attacks/__init__.py @@ -0,0 +1 @@ +# pylint: disable=missing-docstring diff --git a/cleverhans/torch/attacks/carlini_wagner_l2.py b/cleverhans/torch/attacks/carlini_wagner_l2.py new file mode 100644 index 000000000..62a186c01 --- /dev/null +++ b/cleverhans/torch/attacks/carlini_wagner_l2.py @@ -0,0 +1,214 @@ +"""The CarliniWagnerL2 attack.""" +import torch + + +INF = float("inf") + + +def carlini_wagner_l2( + model_fn, + x, + n_classes, + y=None, + targeted=False, + lr=5e-3, + confidence=0, + clip_min=0, + clip_max=1, + initial_const=1e-2, + binary_search_steps=5, + max_iterations=1000, +): + """ + This attack was originally proposed by Carlini and Wagner. It is an + iterative attack that finds adversarial examples on many defenses that + are robust to other attacks. + Paper link: https://arxiv.org/abs/1608.04644 + + At a high level, this attack is an iterative attack using Adam and + a specially-chosen loss function to find adversarial examples with + lower distortion than other attacks. This comes at the cost of speed, + as this attack is often much slower than others. + + :param model_fn: a callable that takes an input tensor and returns + the model logits. The logits should be a tensor of shape + (n_examples, n_classes). + :param x: input tensor of shape (n_examples, ...), where ... can + be any arbitrary dimension that is compatible with + model_fn. + :param n_classes: the number of classes. + :param y: (optional) Tensor with true labels. If targeted is true, + then provide the target label. Otherwise, only provide + this parameter if you'd like to use true labels when + crafting adversarial samples. Otherwise, model predictions + are used as labels to avoid the "label leaking" effect + (explained in this paper: + https://arxiv.org/abs/1611.01236). If provide y, it + should be a 1D tensor of shape (n_examples, ). + Default is None. + :param targeted: (optional) bool. Is the attack targeted or + untargeted? Untargeted, the default, will try to make the + label incorrect. Targeted will instead try to move in the + direction of being more like y. + :param lr: (optional) float. The learning rate for the attack + algorithm. Default is 5e-3. + :param confidence: (optional) float. Confidence of adversarial + examples: higher produces examples with larger l2 + distortion, but more strongly classified as adversarial. + Default is 0. + :param clip_min: (optional) float. Minimum float value for + adversarial example components. Default is 0. + :param clip_max: (optional) float. Maximum float value for + adversarial example components. Default is 1. + :param initial_const: The initial tradeoff-constant to use to tune the + relative importance of size of the perturbation and + confidence of classification. If binary_search_steps is + large, the initial constant is not important. A smaller + value of this constant gives lower distortion results. + Default is 1e-2. + :param binary_search_steps: (optional) int. The number of times we + perform binary search to find the optimal tradeoff-constant + between norm of the perturbation and confidence of the + classification. Default is 5. + :param max_iterations: (optional) int. The maximum number of + iterations. Setting this to a larger value will produce + lower distortion results. Using only a few iterations + requires a larger learning rate, and will produce larger + distortion results. Default is 1000. + """ + + def compare(pred, label, is_logits=False): + """ + A helper function to compare prediction against a label. + Returns true if the attack is considered successful. + + :param pred: can be either a 1D tensor of logits or a predicted + class (int). + :param label: int. A label to compare against. + :param is_logits: (optional) bool. If True, treat pred as an + array of logits. Default is False. + """ + + # Convert logits to predicted class if necessary + if is_logits: + pred_copy = pred.clone().detach() + pred_copy[label] += -confidence if targeted else confidence + pred = torch.argmax(pred_copy) + + return pred == label if targeted else pred != label + + if y is None: + # Using model predictions as ground truth to avoid label leaking + pred = model_fn(x) + y = torch.argmax(pred, 1) + + # Initialize some values needed for binary search on const + lower_bound = [0.0] * len(x) + upper_bound = [1e10] * len(x) + const = x.new_ones(len(x), 1) * initial_const + + o_bestl2 = [INF] * len(x) + o_bestscore = [-1.0] * len(x) + x = torch.clamp(x, clip_min, clip_max) + ox = x.clone().detach() # save the original x + o_bestattack = x.clone().detach() + + # Map images into the tanh-space + # TODO as of 01/06/2020, PyTorch does not natively support + # arctanh (see, e.g., + # https://github.com/pytorch/pytorch/issues/10324). + # This particular implementation here is not numerically + # stable and should be substituted w/ PyTorch's native + # implementation when it comes out in the future + arctanh = lambda x: 0.5 * torch.log((1 + x) / (1 - x)) + x = (x - clip_min) / (clip_max - clip_min) + x = torch.clamp(x, 0, 1) + x = x * 2 - 1 + x = arctanh(x * 0.999999) + + # Prepare some variables + modifier = torch.zeros_like(x, requires_grad=True) + y_onehot = torch.nn.functional.one_hot(y, n_classes).to(torch.float) + + # Define loss functions and optimizer + f_fn = lambda real, other, targeted: torch.max( + ((other - real) if targeted else (real - other)) + confidence, + torch.tensor(0.0).to(real.device), + ) + l2dist_fn = lambda x, y: torch.pow(x - y, 2).sum(list(range(len(x.size())))[1:]) + optimizer = torch.optim.Adam([modifier], lr=lr) + + # Outer loop performing binary search on const + for outer_step in range(binary_search_steps): + # Initialize some values needed for the inner loop + bestl2 = [INF] * len(x) + bestscore = [-1.0] * len(x) + + # Inner loop performing attack iterations + for i in range(max_iterations): + # One attack step + new_x = (torch.tanh(modifier + x) + 1) / 2 + new_x = new_x * (clip_max - clip_min) + clip_min + logits = model_fn(new_x) + + real = torch.sum(y_onehot * logits, 1) + other, _ = torch.max((1 - y_onehot) * logits - y_onehot * 1e4, 1) + + optimizer.zero_grad() + f = f_fn(real, other, targeted) + l2 = l2dist_fn(new_x, ox) + loss = (const * f + l2).sum() + loss.backward() + optimizer.step() + + # Update best results + for n, (l2_n, logits_n, new_x_n) in enumerate(zip(l2, logits, new_x)): + y_n = y[n] + succeeded = compare(logits_n, y_n, is_logits=True) + if l2_n < o_bestl2[n] and succeeded: + pred_n = torch.argmax(logits_n) + o_bestl2[n] = l2_n + o_bestscore[n] = pred_n + o_bestattack[n] = new_x_n + # l2_n < o_bestl2[n] implies l2_n < bestl2[n] so we modify inner loop variables too + bestl2[n] = l2_n + bestscore[n] = pred_n + elif l2_n < bestl2[n] and succeeded: + bestl2[n] = l2_n + bestscore[n] = torch.argmax(logits_n) + + # Binary search step + for n in range(len(x)): + y_n = y[n] + + if compare(bestscore[n], y_n) and bestscore[n] != -1: + # Success, divide const by two + upper_bound[n] = min(upper_bound[n], const[n]) + if upper_bound[n] < 1e9: + const[n] = (lower_bound[n] + upper_bound[n]) / 2 + else: + # Failure, either multiply by 10 if no solution found yet + # or do binary search with the known upper bound + lower_bound[n] = max(lower_bound[n], const[n]) + if upper_bound[n] < 1e9: + const[n] = (lower_bound[n] + upper_bound[n]) / 2 + else: + const[n] *= 10 + + return o_bestattack.detach() + + +if __name__ == "__main__": + x = torch.clamp(torch.randn(5, 10), 0, 1) + y = torch.randint(0, 9, (5,)) + model_fn = lambda x: x + + # targeted + new_x = carlini_wagner_l2(model_fn, x, 10, targeted=True, y=y) + new_pred = model_fn(new_x) + new_pred = torch.argmax(new_pred, 1) + + # untargeted + new_x_untargeted = carlini_wagner_l2(model_fn, x, 10, targeted=False, y=y) + new_pred_untargeted = model_fn(new_x_untargeted) + new_pred_untargeted = torch.argmax(new_pred_untargeted, 1) diff --git a/cleverhans/torch/attacks/fast_gradient_method.py b/cleverhans/torch/attacks/fast_gradient_method.py new file mode 100644 index 000000000..042a62690 --- /dev/null +++ b/cleverhans/torch/attacks/fast_gradient_method.py @@ -0,0 +1,103 @@ +"""The Fast Gradient Method attack.""" +import numpy as np +import torch + +from cleverhans.torch.utils import optimize_linear + + +def fast_gradient_method( + model_fn, + x, + eps, + norm, + clip_min=None, + clip_max=None, + y=None, + targeted=False, + sanity_checks=False, +): + """ + PyTorch implementation of the Fast Gradient Method. + :param model_fn: a callable that takes an input tensor and returns the model logits. + :param x: input tensor. + :param eps: epsilon (input variation parameter); see https://arxiv.org/abs/1412.6572. + :param norm: Order of the norm (mimics NumPy). Possible values: np.inf, 1 or 2. + :param clip_min: (optional) float. Minimum float value for adversarial example components. + :param clip_max: (optional) float. Maximum float value for adversarial example components. + :param y: (optional) Tensor with true labels. If targeted is true, then provide the + target label. Otherwise, only provide this parameter if you'd like to use true + labels when crafting adversarial samples. Otherwise, model predictions are used + as labels to avoid the "label leaking" effect (explained in this paper: + https://arxiv.org/abs/1611.01236). Default is None. + :param targeted: (optional) bool. Is the attack targeted or untargeted? + Untargeted, the default, will try to make the label incorrect. + Targeted will instead try to move in the direction of being more like y. + :param sanity_checks: bool, if True, include asserts (Turn them off to use less runtime / + memory or for unit tests that intentionally pass strange input) + :return: a tensor for the adversarial example + """ + if norm not in [np.inf, 1, 2]: + raise ValueError( + "Norm order must be either np.inf, 1, or 2, got {} instead.".format(norm) + ) + if eps < 0: + raise ValueError( + "eps must be greater than or equal to 0, got {} instead".format(eps) + ) + if eps == 0: + return x + if clip_min is not None and clip_max is not None: + if clip_min > clip_max: + raise ValueError( + "clip_min must be less than or equal to clip_max, got clip_min={} and clip_max={}".format( + clip_min, clip_max + ) + ) + + asserts = [] + + # If a data range was specified, check that the input was in that range + if clip_min is not None: + assert_ge = torch.all( + torch.ge(x, torch.tensor(clip_min, device=x.device, dtype=x.dtype)) + ) + asserts.append(assert_ge) + + if clip_max is not None: + assert_le = torch.all( + torch.le(x, torch.tensor(clip_max, device=x.device, dtype=x.dtype)) + ) + asserts.append(assert_le) + + # x needs to be a leaf variable, of floating point type and have requires_grad being True for + # its grad to be computed and stored properly in a backward call + x = x.clone().detach().to(torch.float).requires_grad_(True) + if y is None: + # Using model predictions as ground truth to avoid label leaking + _, y = torch.max(model_fn(x), 1) + + # Compute loss + loss_fn = torch.nn.CrossEntropyLoss() + loss = loss_fn(model_fn(x), y) + # If attack is targeted, minimize loss of target label rather than maximize loss of correct label + if targeted: + loss = -loss + + # Define gradient of loss wrt input + loss.backward() + optimal_perturbation = optimize_linear(x.grad, eps, norm) + + # Add perturbation to original example to obtain adversarial example + adv_x = x + optimal_perturbation + + # If clipping is needed, reset all values outside of [clip_min, clip_max] + if (clip_min is not None) or (clip_max is not None): + if clip_min is None or clip_max is None: + raise ValueError( + "One of clip_min and clip_max is None but we don't currently support one-sided clipping" + ) + adv_x = torch.clamp(adv_x, clip_min, clip_max) + + if sanity_checks: + assert np.all(asserts) + return adv_x diff --git a/cleverhans/torch/attacks/hop_skip_jump_attack.py b/cleverhans/torch/attacks/hop_skip_jump_attack.py new file mode 100644 index 000000000..fbe3acbea --- /dev/null +++ b/cleverhans/torch/attacks/hop_skip_jump_attack.py @@ -0,0 +1,396 @@ +""" +Boundary Attack++ +""" +import numpy as np +import torch + + +def hop_skip_jump_attack( + model_fn, + x, + norm, + y_target=None, + image_target=None, + initial_num_evals=100, + max_num_evals=10000, + stepsize_search="geometric_progression", + num_iterations=64, + gamma=1.0, + constraint=2, + batch_size=128, + verbose=True, + clip_min=0, + clip_max=1, +): + """ + PyTorch implementation of HopSkipJumpAttack. + HopSkipJumpAttack was originally proposed by Chen, Jordan and Wainwright. + It is a decision-based attack that requires access to output + labels of a model alone. + Paper link: https://arxiv.org/abs/1904.02144 + At a high level, this attack is an iterative attack composed of three + steps: Binary search to approach the boundary; gradient estimation; + stepsize search. HopSkipJumpAttack requires fewer model queries than + Boundary Attack which was based on rejective sampling. + + :param model_fn: a callable that takes an input tensor and returns the model logits. + :param x: input tensor with n samples. + :param norm: The distance to optimize. Possible values: 2 or np.inf. + :param y_target: A tensor of shape (n, nb_classes) for target labels. + Required for targeted attack. + :param image_target: A tensor of shape (n, **image shape) for initial + target images. Required for targeted attack. + :param initial_num_evals: initial number of evaluations for + gradient estimation. + :param max_num_evals: maximum number of evaluations for gradient estimation. + :param stepsize_search: How to search for stepsize; choices are + 'geometric_progression', 'grid_search'. + 'geometric progression' initializes the stepsize + by ||x_t - x||_p / sqrt(iteration), and keep + decreasing by half until reaching the target + side of the boundary. 'grid_search' chooses the + optimal epsilon over a grid, in the scale of + ||x_t - x||_p. + :param num_iterations: The number of iterations. + :param gamma: The binary search threshold theta is gamma / d^{3/2} for + l2 attack and gamma / d^2 for linf attack. + :param batch_size: batch_size for model prediction. + :param verbose: (boolean) Whether distance at each step is printed. + :param clip_min: (optional float) Minimum input component value + :param clip_max: (optional float) Maximum input component value + """ + shape = (1,) + x.shape[1:] + if y_target is not None: + assert image_target is not None, "Require a target image for targeted attack." + if clip_min is not None and clip_max is not None: + if clip_min > clip_max: + raise ValueError( + "clip_min must be less than or equal to clip_max, got clip_min={} and clip_max={}".format( + clip_min, clip_max + ) + ) + + d = int(np.prod(shape)) + + if constraint == 2: + theta = gamma / (np.sqrt(d) * d) + else: + theta = gamma / (d * d) + + def hsja(sample, target_label, target_image): + if target_label is None: + _, original_label = torch.max(model_fn(sample), 1) + + def decision_function(images): + """ + Decision function output 1 on the desired side of the boundary, + 0 otherwise. + """ + images = torch.clamp(images, clip_min, clip_max) + prob = [] + for i in range(0, len(images), batch_size): + batch = images[i : i + batch_size] + prob_i = model_fn(batch) + prob.append(prob_i) + prob = torch.cat(prob, dim=0) + if target_label is None: + return torch.max(prob, dim=1)[1] != original_label + else: + return torch.max(prob, dim=1)[1] == target_label + + # Initialize. + if target_image is None: + perturbed = initialize(decision_function, sample, shape, clip_min, clip_max) + else: + perturbed = target_image.to(sample.device) + + # Project the initialization to the boundary. + perturbed, dist_post_update = binary_search_batch( + sample, perturbed, decision_function, shape, constraint, theta + ) + dist = compute_distance(perturbed, sample, constraint) + + for j in np.arange(num_iterations): + current_iteration = j + 1 + + # Choose delta. + delta = select_delta( + dist_post_update, + current_iteration, + clip_max, + clip_min, + d, + theta, + constraint, + ) + + # Choose number of evaluations. + num_evals = int(min([initial_num_evals * np.sqrt(j + 1), max_num_evals])) + + # approximate gradient. + gradf = approximate_gradient( + decision_function, + perturbed, + num_evals, + delta, + constraint, + shape[1:], + clip_min, + clip_max, + ) + if constraint == np.inf: + update = torch.sign(gradf) + else: + update = gradf + + # search step size. + if stepsize_search == "geometric_progression": + # find step size. + epsilon = geometric_progression_for_stepsize( + perturbed, update, dist, decision_function, current_iteration + ) + + # Update the sample. + perturbed = torch.clamp( + perturbed + epsilon * update, clip_min, clip_max + ) + + # Binary search to return to the boundary. + perturbed, dist_post_update = binary_search_batch( + sample, perturbed, decision_function, shape, constraint, theta + ) + + elif stepsize_search == "grid_search": + # Grid search for stepsize. + epsilons = ( + torch.from_numpy(np.logspace(-4, 0, num=20, endpoint=True)) + .to(perturbed.device) + .float() + * dist + ) + perturbeds = ( + perturbed + epsilons.view((20,) + (1,) * (len(shape) - 1)) * update + ) + perturbeds = torch.clamp(perturbeds, clip_min, clip_max) + idx_perturbed = decision_function(perturbeds) + + if torch.sum(idx_perturbed) > 0: + # Select the perturbation that yields the minimum distance # after binary search. + perturbed, dist_post_update = binary_search_batch( + sample, + perturbeds[idx_perturbed], + decision_function, + shape, + constraint, + theta, + ) + + # compute new distance. + dist = compute_distance(perturbed, sample, constraint) + if verbose: + print( + "iteration: {:d}, {:s} distance {:.4E}".format( + j + 1, str(constraint), dist + ) + ) + + return perturbed + + # Perform attack on one instance at a time + adv_x = [] + for i, x_ in enumerate(x): + x_ = x_.unsqueeze(0) + if y_target is not None: + # targeted attack that requires target label and image. + pert = hsja(x_, y_target[i], image_target[i]) + else: + if image_target is not None: + pert = hsja(x_, None, image_target[i]) + else: + # untargeted attack without an initialized image. + pert = hsja(x_, None, None) + adv_x.append(pert) + return torch.cat(adv_x, 0) + + +def compute_distance(x_ori, x_pert, constraint=2): + """ Compute the distance between two images. """ + if constraint == 2: + dist = torch.norm(x_ori - x_pert, p=2) + elif constraint == np.inf: + dist = torch.max(torch.abs(x_ori - x_pert)) + return dist + + +def approximate_gradient( + decision_function, sample, num_evals, delta, constraint, shape, clip_min, clip_max +): + """ Gradient direction estimation """ + # Generate random vectors. + noise_shape = [num_evals] + list(shape) + if constraint == 2: + rv = torch.randn(noise_shape) + elif constraint == np.inf: + rv = -1 + torch.rand(noise_shape) * 2 + + axis = tuple(range(1, 1 + len(shape))) + rv = rv / torch.sqrt(torch.sum(rv ** 2, dim=axis, keepdim=True)) + perturbed = sample + delta * rv.to(sample.device) + perturbed = torch.clamp(perturbed, clip_min, clip_max) + rv = (perturbed - sample) / delta + + # query the model. + decisions = decision_function(perturbed).float() + fval = 2.0 * decisions.view((decisions.shape[0],) + (1,) * len(shape)) - 1.0 + + # Baseline subtraction (when fval differs) + fval_mean = torch.mean(fval) + if fval_mean == 1.0: # label changes. + gradf = torch.mean(rv, dim=0) + elif fval_mean == -1.0: # label not change. + gradf = -torch.mean(rv, dim=0) + else: + fval = fval - fval_mean + gradf = torch.mean(fval * rv, dim=0) + + # Get the gradient direction. + gradf = gradf / torch.norm(gradf, p=2) + + return gradf + + +def project(original_image, perturbed_images, alphas, shape, constraint): + """ Projection onto given l2 / linf balls in a batch. """ + alphas = alphas.view((alphas.shape[0],) + (1,) * (len(shape) - 1)) + if constraint == 2: + projected = (1 - alphas) * original_image + alphas * perturbed_images + elif constraint == np.inf: + projected = torch.clamp( + perturbed_images, original_image - alphas, original_image + alphas + ) + return projected + + +def binary_search_batch( + original_image, perturbed_images, decision_function, shape, constraint, theta +): + """ Binary search to approach the boundary. """ + + # Compute distance between each of perturbed image and original image. + dists_post_update = torch.stack( + [ + compute_distance(original_image, perturbed_image, constraint) + for perturbed_image in perturbed_images + ] + ) + + # Choose upper thresholds in binary searchs based on constraint. + if constraint == np.inf: + highs = dists_post_update + # Stopping criteria. + thresholds = torch.min(dists_post_update * theta, theta) + else: + highs = torch.ones(len(perturbed_images)).to(original_image.device) + thresholds = theta + + lows = torch.zeros(len(perturbed_images)).to(original_image.device) + + while torch.max((highs - lows) / thresholds) > 1: + # projection to mids. + mids = (highs + lows) / 2.0 + mid_images = project(original_image, perturbed_images, mids, shape, constraint) + + # Update highs and lows based on model decisions. + decisions = decision_function(mid_images) + lows = torch.where(decisions == 0, mids, lows) + highs = torch.where(decisions == 1, mids, highs) + + out_images = project(original_image, perturbed_images, highs, shape, constraint) + + # Compute distance of the output image to select the best choice. + # (only used when stepsize_search is grid_search.) + dists = torch.stack( + [ + compute_distance(original_image, out_image, constraint) + for out_image in out_images + ] + ) + _, idx = torch.min(dists, 0) + + dist = dists_post_update[idx] + out_image = out_images[idx].unsqueeze(0) + return out_image, dist + + +def initialize(decision_function, sample, shape, clip_min, clip_max): + """ + Efficient Implementation of BlendedUniformNoiseAttack in Foolbox. + """ + success = 0 + num_evals = 0 + + # Find a misclassified random noise. + while True: + random_noise = clip_min + torch.rand(shape).to(sample.device) * ( + clip_max - clip_min + ) + success = decision_function(random_noise)[0] + if success: + break + num_evals += 1 + message = ( + "Initialization failed! Try to use a misclassified image as `target_image`" + ) + assert num_evals < 1e4, message + + # Binary search to minimize l2 distance to original image. + low = 0.0 + high = 1.0 + while high - low > 0.001: + mid = (high + low) / 2.0 + blended = (1 - mid) * sample + mid * random_noise + success = decision_function(blended)[0] + if success: + high = mid + else: + low = mid + + initialization = (1 - high) * sample + high * random_noise + return initialization + + +def geometric_progression_for_stepsize( + x, update, dist, decision_function, current_iteration +): + """Geometric progression to search for stepsize. + Keep decreasing stepsize by half until reaching + the desired side of the boundary. + """ + epsilon = dist / np.sqrt(current_iteration) + while True: + updated = x + epsilon * update + success = decision_function(updated)[0] + if success: + break + else: + epsilon = epsilon / 2.0 + + return epsilon + + +def select_delta( + dist_post_update, current_iteration, clip_max, clip_min, d, theta, constraint +): + """ + Choose the delta at the scale of distance + between x and perturbed sample. + """ + if current_iteration == 1: + delta = 0.1 * (clip_max - clip_min) + else: + if constraint == 2: + delta = np.sqrt(d) * theta * dist_post_update + elif constraint == np.inf: + delta = d * theta * dist_post_update + + return delta diff --git a/cleverhans/future/torch/attacks/noise.py b/cleverhans/torch/attacks/noise.py similarity index 94% rename from cleverhans/future/torch/attacks/noise.py rename to cleverhans/torch/attacks/noise.py index af805e65b..4a7e38493 100644 --- a/cleverhans/future/torch/attacks/noise.py +++ b/cleverhans/torch/attacks/noise.py @@ -22,15 +22,16 @@ def noise(x, eps=0.3, order=np.inf, clip_min=None, clip_max=None): Args: :param x: the input tensor - :param eps: (optional float) maximum distortion of adversarial example + :param eps: (optional float) maximum distortion of adversarial example compared to original input. :param norm: (optional) Order of the norm. :param clip_min: (optional float) Minimum input component value :param clip_max: (optional float) Maximum input component value """ - if order != np.inf: raise NotImplementedError(norm) - + if order != np.inf: + raise NotImplementedError(norm) + eta = torch.FloatTensor(*x.shape).to(x.device).uniform_(-eps, eps) adv_x = x + eta diff --git a/cleverhans/torch/attacks/projected_gradient_descent.py b/cleverhans/torch/attacks/projected_gradient_descent.py new file mode 100644 index 000000000..4ac4ac662 --- /dev/null +++ b/cleverhans/torch/attacks/projected_gradient_descent.py @@ -0,0 +1,153 @@ +"""The Projected Gradient Descent attack.""" +import numpy as np +import torch + +from cleverhans.torch.attacks.fast_gradient_method import fast_gradient_method +from cleverhans.torch.utils import clip_eta + + +def projected_gradient_descent( + model_fn, + x, + eps, + eps_iter, + nb_iter, + norm, + clip_min=None, + clip_max=None, + y=None, + targeted=False, + rand_init=True, + rand_minmax=None, + sanity_checks=True, +): + """ + This class implements either the Basic Iterative Method + (Kurakin et al. 2016) when rand_init is set to False. or the + Madry et al. (2017) method if rand_init is set to True. + Paper link (Kurakin et al. 2016): https://arxiv.org/pdf/1607.02533.pdf + Paper link (Madry et al. 2017): https://arxiv.org/pdf/1706.06083.pdf + :param model_fn: a callable that takes an input tensor and returns the model logits. + :param x: input tensor. + :param eps: epsilon (input variation parameter); see https://arxiv.org/abs/1412.6572. + :param eps_iter: step size for each attack iteration + :param nb_iter: Number of attack iterations. + :param norm: Order of the norm (mimics NumPy). Possible values: np.inf, 1 or 2. + :param clip_min: (optional) float. Minimum float value for adversarial example components. + :param clip_max: (optional) float. Maximum float value for adversarial example components. + :param y: (optional) Tensor with true labels. If targeted is true, then provide the + target label. Otherwise, only provide this parameter if you'd like to use true + labels when crafting adversarial samples. Otherwise, model predictions are used + as labels to avoid the "label leaking" effect (explained in this paper: + https://arxiv.org/abs/1611.01236). Default is None. + :param targeted: (optional) bool. Is the attack targeted or untargeted? + Untargeted, the default, will try to make the label incorrect. + Targeted will instead try to move in the direction of being more like y. + :param rand_init: (optional) bool. Whether to start the attack from a randomly perturbed x. + :param rand_minmax: (optional) bool. Support of the continuous uniform distribution from + which the random perturbation on x was drawn. Effective only when rand_init is + True. Default equals to eps. + :param sanity_checks: bool, if True, include asserts (Turn them off to use less runtime / + memory or for unit tests that intentionally pass strange input) + :return: a tensor for the adversarial example + """ + if norm == 1: + raise NotImplementedError( + "It's not clear that FGM is a good inner loop" + " step for PGD when norm=1, because norm=1 FGM " + " changes only one pixel at a time. We need " + " to rigorously test a strong norm=1 PGD " + "before enabling this feature." + ) + if norm not in [np.inf, 2]: + raise ValueError("Norm order must be either np.inf or 2.") + if eps < 0: + raise ValueError( + "eps must be greater than or equal to 0, got {} instead".format(eps) + ) + if eps == 0: + return x + if eps_iter < 0: + raise ValueError( + "eps_iter must be greater than or equal to 0, got {} instead".format( + eps_iter + ) + ) + if eps_iter == 0: + return x + + assert eps_iter <= eps, (eps_iter, eps) + if clip_min is not None and clip_max is not None: + if clip_min > clip_max: + raise ValueError( + "clip_min must be less than or equal to clip_max, got clip_min={} and clip_max={}".format( + clip_min, clip_max + ) + ) + + asserts = [] + + # If a data range was specified, check that the input was in that range + if clip_min is not None: + assert_ge = torch.all( + torch.ge(x, torch.tensor(clip_min, device=x.device, dtype=x.dtype)) + ) + asserts.append(assert_ge) + + if clip_max is not None: + assert_le = torch.all( + torch.le(x, torch.tensor(clip_max, device=x.device, dtype=x.dtype)) + ) + asserts.append(assert_le) + + # Initialize loop variables + if rand_init: + if rand_minmax is None: + rand_minmax = eps + eta = torch.zeros_like(x).uniform_(-rand_minmax, rand_minmax) + else: + eta = torch.zeros_like(x) + + # Clip eta + eta = clip_eta(eta, norm, eps) + adv_x = x + eta + if clip_min is not None or clip_max is not None: + adv_x = torch.clamp(adv_x, clip_min, clip_max) + + if y is None: + # Using model predictions as ground truth to avoid label leaking + _, y = torch.max(model_fn(x), 1) + + i = 0 + while i < nb_iter: + adv_x = fast_gradient_method( + model_fn, + adv_x, + eps_iter, + norm, + clip_min=clip_min, + clip_max=clip_max, + y=y, + targeted=targeted, + ) + + # Clipping perturbation eta to norm norm ball + eta = adv_x - x + eta = clip_eta(eta, norm, eps) + adv_x = x + eta + + # Redo the clipping. + # FGM already did it, but subtracting and re-adding eta can add some + # small numerical error. + if clip_min is not None or clip_max is not None: + adv_x = torch.clamp(adv_x, clip_min, clip_max) + i += 1 + + asserts.append(eps_iter <= eps) + if norm == np.inf and clip_min is not None: + # TODO necessary to cast clip_min and clip_max to x.dtype? + asserts.append(eps + clip_min <= clip_max) + + if sanity_checks: + assert np.all(asserts) + return adv_x diff --git a/cleverhans/torch/attacks/semantic.py b/cleverhans/torch/attacks/semantic.py new file mode 100644 index 000000000..12f60df69 --- /dev/null +++ b/cleverhans/torch/attacks/semantic.py @@ -0,0 +1,26 @@ +""" +Semantic adversarial Examples +""" + + +def semantic(x, center=True, max_val=1.0): + """ + Semantic adversarial examples. + + https://arxiv.org/abs/1703.06857 + + Note: data must either be centered (so that the negative image can be + made by simple negation) or must be in the interval of [-1, 1] + + Arguments + --------- + center : bool + If true, assumes data has 0 mean so the negative image is just negation. + If false, assumes data is in interval [0, max_val] + max_val : float + Maximum value allowed in the input data. + """ + + if self.center: + return x * -1 + return self.max_val - x diff --git a/cleverhans/future/torch/attacks/sparse_l1_descent.py b/cleverhans/torch/attacks/sparse_l1_descent.py similarity index 100% rename from cleverhans/future/torch/attacks/sparse_l1_descent.py rename to cleverhans/torch/attacks/sparse_l1_descent.py diff --git a/cleverhans/torch/attacks/spsa.py b/cleverhans/torch/attacks/spsa.py new file mode 100644 index 000000000..86dc21df8 --- /dev/null +++ b/cleverhans/torch/attacks/spsa.py @@ -0,0 +1,235 @@ +"""The SPSA attack.""" +import numpy as np +import torch +from torch import optim +from cleverhans.torch.utils import clip_eta + + +def spsa( + model_fn, + x, + eps, + nb_iter, + norm=np.inf, + clip_min=-np.inf, + clip_max=np.inf, + y=None, + targeted=False, + early_stop_loss_threshold=None, + learning_rate=0.01, + delta=0.01, + spsa_samples=128, + spsa_iters=1, + is_debug=False, + sanity_checks=True, +): + """ + This implements the SPSA adversary, as in https://arxiv.org/abs/1802.05666 + (Uesato et al. 2018). SPSA is a gradient-free optimization method, which is useful when + the model is non-differentiable, or more generally, the gradients do not point in useful + directions. + + :param model_fn: A callable that takes an input tensor and returns the model logits. + :param x: Input tensor. + :param eps: The size of the maximum perturbation, measured in the L-infinity norm. + :param nb_iter: The number of optimization steps. + :param norm: Order of the norm (mimics NumPy). Possible values: np.inf, 1 or 2. + :param clip_min: If specified, the minimum input value. + :param clip_max: If specified, the maximum input value. + :param y: (optional) Tensor with true labels. If targeted is true, then provide the + target label. Otherwise, only provide this parameter if you'd like to use true + labels when crafting adversarial samples. Otherwise, model predictions are used + as labels to avoid the "label leaking" effect (explained in this paper: + https://arxiv.org/abs/1611.01236). Default is None. + :param targeted: (optional) bool. Is the attack targeted or untargeted? Untargeted, the + default, will try to make the label incorrect. Targeted will instead try to + move in the direction of being more like y. + :param early_stop_loss_threshold: A float or None. If specified, the attack will end as + soon as the loss is below `early_stop_loss_threshold`. + :param learning_rate: Learning rate of ADAM optimizer. + :param delta: Perturbation size used for SPSA approximation. + :param spsa_samples: Number of inputs to evaluate at a single time. The true batch size + (the number of evaluated inputs for each update) is `spsa_samples * + spsa_iters` + :param spsa_iters: Number of model evaluations before performing an update, where each + evaluation is on `spsa_samples` different inputs. + :param is_debug: If True, print the adversarial loss after each update. + :param sanity_checks: bool, if True, include asserts (Turn them off to use less runtime / + memory or for unit tests that intentionally pass strange input) + :return: a tensor for the adversarial example + """ + + if y is not None and len(x) != len(y): + raise ValueError( + "number of inputs {} is different from number of labels {}".format( + len(x), len(y) + ) + ) + if y is None: + y = torch.argmax(model_fn(x), dim=1) + + # The rest of the function doesn't support batches of size greater than 1, + # so if the batch is bigger we split it up. + if len(x) != 1: + adv_x = [] + for x_single, y_single in zip(x, y): + adv_x_single = spsa( + model_fn=model_fn, + x=x_single.unsqueeze(0), + eps=eps, + nb_iter=nb_iter, + norm=norm, + clip_min=clip_min, + clip_max=clip_max, + y=y_single.unsqueeze(0), + targeted=targeted, + early_stop_loss_threshold=early_stop_loss_threshold, + learning_rate=learning_rate, + delta=delta, + spsa_samples=spsa_samples, + spsa_iters=spsa_iters, + is_debug=is_debug, + sanity_checks=sanity_checks, + ) + adv_x.append(adv_x_single) + return torch.cat(adv_x) + + if eps < 0: + raise ValueError( + "eps must be greater than or equal to 0, got {} instead".format(eps) + ) + if eps == 0: + return x + + if clip_min is not None and clip_max is not None: + if clip_min > clip_max: + raise ValueError( + "clip_min must be less than or equal to clip_max, got clip_min={} and clip_max={}".format( + clip_min, clip_max + ) + ) + + asserts = [] + + # If a data range was specified, check that the input was in that range + asserts.append(torch.all(x >= clip_min)) + asserts.append(torch.all(x <= clip_max)) + + if is_debug: + print("Starting SPSA attack with eps = {}".format(eps)) + + perturbation = (torch.rand_like(x) * 2 - 1) * eps + _project_perturbation(perturbation, norm, eps, x, clip_min, clip_max) + optimizer = optim.Adam([perturbation], lr=learning_rate) + + for i in range(nb_iter): + + def loss_fn(pert): + """ + Margin logit loss, with correct sign for targeted vs untargeted loss. + """ + logits = model_fn(x + pert) + loss_multiplier = 1 if targeted else -1 + return loss_multiplier * _margin_logit_loss(logits, y.expand(len(pert))) + + spsa_grad = _compute_spsa_gradient( + loss_fn, x, delta=delta, samples=spsa_samples, iters=spsa_iters + ) + perturbation.grad = spsa_grad + optimizer.step() + + _project_perturbation(perturbation, norm, eps, x, clip_min, clip_max) + + loss = loss_fn(perturbation).item() + if is_debug: + print("Iteration {}: loss = {}".format(i, loss)) + if early_stop_loss_threshold is not None and loss < early_stop_loss_threshold: + break + + adv_x = torch.clamp((x + perturbation).detach(), clip_min, clip_max) + + if norm == np.inf: + asserts.append(torch.all(torch.abs(adv_x - x) <= eps + 1e-6)) + else: + asserts.append( + torch.all( + torch.abs( + torch.renorm(adv_x - x, p=norm, dim=0, maxnorm=eps) - (adv_x - x) + ) + < 1e-6 + ) + ) + asserts.append(torch.all(adv_x >= clip_min)) + asserts.append(torch.all(adv_x <= clip_max)) + + if sanity_checks: + assert np.all(asserts) + + return adv_x + + +def _project_perturbation( + perturbation, norm, epsilon, input_image, clip_min=-np.inf, clip_max=np.inf +): + """ + Project `perturbation` onto L-infinity ball of radius `epsilon`. Also project into + hypercube such that the resulting adversarial example is between clip_min and clip_max, + if applicable. This is an in-place operation. + """ + + clipped_perturbation = clip_eta(perturbation, norm, epsilon) + new_image = torch.clamp(input_image + clipped_perturbation, clip_min, clip_max) + + perturbation.add_((new_image - input_image) - perturbation) + + +def _compute_spsa_gradient(loss_fn, x, delta, samples, iters): + """ + Approximately compute the gradient of `loss_fn` at `x` using SPSA with the + given parameters. The gradient is approximated by evaluating `iters` batches + of `samples` size each. + """ + + assert len(x) == 1 + num_dims = len(x.size()) + + x_batch = x.expand(samples, *([-1] * (num_dims - 1))) + + grad_list = [] + for i in range(iters): + delta_x = delta * torch.sign(torch.rand_like(x_batch) - 0.5) + delta_x = torch.cat([delta_x, -delta_x]) + with torch.no_grad(): + loss_vals = loss_fn(x + delta_x) + while len(loss_vals.size()) < num_dims: + loss_vals = loss_vals.unsqueeze(-1) + avg_grad = ( + torch.mean(loss_vals * torch.sign(delta_x), dim=0, keepdim=True) / delta + ) + grad_list.append(avg_grad) + + return torch.mean(torch.cat(grad_list), dim=0, keepdim=True) + + +def _margin_logit_loss(logits, labels): + """ + Computes difference between logits for `labels` and next highest logits. + + The loss is high when `label` is unlikely (targeted by default). + """ + + correct_logits = logits.gather(1, labels[:, None]).squeeze(1) + + logit_indices = torch.arange( + logits.size()[1], + dtype=labels.dtype, + device=labels.device, + )[None, :].expand(labels.size()[0], -1) + incorrect_logits = torch.where( + logit_indices == labels[:, None], + torch.full_like(logits, float("-inf")), + logits, + ) + max_incorrect_logits, _ = torch.max(incorrect_logits, 1) + + return max_incorrect_logits - correct_logits diff --git a/cleverhans/future/torch/__init__.py b/cleverhans/torch/tests/__init__.py similarity index 100% rename from cleverhans/future/torch/__init__.py rename to cleverhans/torch/tests/__init__.py diff --git a/cleverhans/torch/tests/test_attacks.py b/cleverhans/torch/tests/test_attacks.py new file mode 100644 index 000000000..3715de197 --- /dev/null +++ b/cleverhans/torch/tests/test_attacks.py @@ -0,0 +1,1047 @@ +# pylint: disable=missing-docstring +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import numpy as np +from nose.plugins.skip import SkipTest +import torch + +from cleverhans.devtools.checks import CleverHansTest +from cleverhans.torch.attacks.fast_gradient_method import fast_gradient_method +from cleverhans.torch.attacks.projected_gradient_descent import ( + projected_gradient_descent, +) +from cleverhans.torch.attacks.carlini_wagner_l2 import carlini_wagner_l2 +from cleverhans.torch.attacks.spsa import spsa +from cleverhans.torch.attacks.hop_skip_jump_attack import hop_skip_jump_attack +from cleverhans.torch.attacks.sparse_l1_descent import sparse_l1_descent + + +class TrivialModel(torch.nn.Module): + def __init__(self): + super(TrivialModel, self).__init__() + self.w1 = torch.tensor([[1.0, -1]]) + + def forward(self, x, **kwargs): + return torch.matmul(x, self.w1) + + +class SimpleModel(torch.nn.Module): + def __init__(self): + super(SimpleModel, self).__init__() + self.w1 = torch.tensor([[1.5, 0.3], [-2, 0.3]]) + self.w2 = torch.tensor([[-2.4, 1.2], [0.5, -2.3]]) + + def forward(self, x): + x = torch.matmul(x, self.w1) + x = torch.sigmoid(x) + x = torch.matmul(x, self.w2) + return x + + +class DummyModel(torch.nn.Module): + def __init__(self, n_features): + super(DummyModel, self).__init__() + self.model = torch.nn.Sequential( + torch.nn.Linear(n_features, 60), + torch.nn.ReLU(), + torch.nn.Linear(60, 10), + ) + + def forward(self, x): + x = x.view(x.shape[0], -1) + return self.model(x) + + +class CommonAttackProperties(CleverHansTest): + def setUp(self): + super(CommonAttackProperties, self).setUp() + self.model = SimpleModel() + self.trivial_model = TrivialModel() + self.x = torch.randn(100, 2) + self.normalized_x = torch.rand(100, 2) # truncated between [0, 1) + self.trivial_x = torch.randn(100, 1) + self.trivial_normalized_x = torch.rand(100, 1) # truncated between [0, 1) + self.y_target = torch.randint(low=0, high=2, size=(100,)) + self.ord_list = [1, 2, np.inf] + + def help_adv_examples_success_rate(self, model, x, rate=0.5, **kwargs): + x_adv = self.attack(model_fn=model, x=x, **kwargs) + _, ori_label = model(x).max(1) + _, adv_label = model(x_adv).max(1) + adv_acc = adv_label.eq(ori_label).sum().to(torch.float) / x.size(0) + self.assertLess(adv_acc, rate) + + def help_targeted_adv_examples_success_rate(self, model, x, rate=0.7, **kwargs): + x_adv = self.attack( + model_fn=model, x=x, y=self.y_target, targeted=True, **kwargs + ) + + _, adv_label = model(x_adv).max(1) + adv_success = adv_label.eq(self.y_target).sum().to(torch.float) / x.size(0) + self.assertGreater(adv_success, rate) + + +class TestFastGradientMethod(CommonAttackProperties): + def setUp(self): + super(TestFastGradientMethod, self).setUp() + self.attack = fast_gradient_method + self.eps_list = [0, 0.1, 0.3, 1.0, 3] + self.attack_param = {"eps": 0.5, "clip_min": -5, "clip_max": 5} + + def test_invalid_input(self): + x = torch.tensor([[-2.0, 3.0]]) + for norm in self.ord_list: + self.assertRaises( + AssertionError, + self.attack, + model_fn=self.model, + x=x, + eps=0.1, + norm=norm, + clip_min=-1.0, + clip_max=1.0, + sanity_checks=True, + ) + + def test_invalid_eps(self): + for norm in self.ord_list: + self.assertRaises( + ValueError, + self.attack, + model_fn=self.model, + x=self.x, + eps=-0.1, + norm=norm, + ) + + def test_eps_equals_zero(self): + for norm in self.ord_list: + self.assertClose( + self.attack(model_fn=self.model, x=self.x, eps=0, norm=norm), self.x + ) + + def test_eps(self): + # test if the attack respects the norm constraint + # NOTE this has been tested with the optimize_linear function in + # test_utils, so duplicate tests are not needed here. + # Although, if ever switch the engine of the FGM method to some + # function other than optimize_linear. This test should be added. + raise SkipTest() + + def test_clips(self): + clip_min = -1.0 + clip_max = 1.0 + for norm in self.ord_list: + x_adv = self.attack( + model_fn=self.model, + x=self.normalized_x, + eps=0.3, + norm=norm, + clip_min=clip_min, + clip_max=clip_max, + ) + self.assertTrue(torch.all(x_adv <= clip_max)) + self.assertTrue(torch.all(x_adv >= clip_min)) + + def test_invalid_clips(self): + clip_min = 0.5 + clip_max = -0.5 + for norm in self.ord_list: + self.assertRaises( + ValueError, + self.attack, + model_fn=self.model, + x=self.x, + eps=0.1, + norm=norm, + clip_min=clip_min, + clip_max=clip_max, + ) + + def test_adv_example_success_rate_linf(self): + # use normalized_x to make sure the same eps gives uniformly high attack + # success rate across randomized tests + self.help_adv_examples_success_rate( + x=self.normalized_x, model=self.model, norm=np.inf, **self.attack_param + ) + + def test_targeted_adv_example_success_rate_linf(self): + self.help_targeted_adv_examples_success_rate( + x=self.normalized_x, model=self.model, norm=np.inf, **self.attack_param + ) + + def test_adv_example_success_rate_l1(self): + self.help_adv_examples_success_rate( + x=self.normalized_x, model=self.model, norm=1, **self.attack_param + ) + + def test_targeted_adv_example_success_rate_l1(self): + self.help_targeted_adv_examples_success_rate( + x=self.normalized_x, model=self.model, norm=1, **self.attack_param + ) + + def test_adv_example_success_rate_l2(self): + self.help_adv_examples_success_rate( + x=self.normalized_x, model=self.model, norm=2, **self.attack_param + ) + + def test_targeted_adv_example_success_rate_l2(self): + self.help_targeted_adv_examples_success_rate( + x=self.normalized_x, model=self.model, norm=2, **self.attack_param + ) + + +class TestProjectedGradientMethod(CommonAttackProperties): + def setUp(self): + super(TestProjectedGradientMethod, self).setUp() + self.attack = projected_gradient_descent + self.attack_param = { + "eps": 0.5, + "clip_min": -5, + "clip_max": 5, + "eps_iter": 0.05, + "nb_iter": 20, + } + + def test_invalid_input(self): + x = torch.tensor([[-2.0, 3.0]]) + for norm in self.ord_list: + if norm == 1: + self.assertRaises( + NotImplementedError, + self.attack, + model_fn=self.model, + x=x, + eps=0.1, + nb_iter=1, + eps_iter=0.05, + norm=norm, + clip_min=-1.0, + clip_max=1.0, + sanity_checks=True, + ) + else: + self.assertRaises( + AssertionError, + self.attack, + model_fn=self.model, + x=x, + eps=0.1, + nb_iter=1, + eps_iter=0.05, + norm=norm, + clip_min=-1.0, + clip_max=1.0, + sanity_checks=True, + ) + + def test_invalid_eps(self): + for norm in self.ord_list: + if norm == 1: + self.assertRaises( + NotImplementedError, + self.attack, + model_fn=self.model, + x=self.x, + eps=-0.1, + norm=norm, + nb_iter=1, + eps_iter=0.01, + ) + else: + self.assertRaises( + ValueError, + self.attack, + model_fn=self.model, + x=self.x, + eps=-0.1, + norm=norm, + nb_iter=1, + eps_iter=0.01, + ) + + def test_invalid_eps_iter(self): + for norm in self.ord_list: + if norm == 1: + self.assertRaises( + NotImplementedError, + self.attack, + model_fn=self.model, + x=self.x, + eps=0.1, + norm=norm, + nb_iter=1, + eps_iter=-0.01, + ) + else: + self.assertRaises( + ValueError, + self.attack, + model_fn=self.model, + x=self.x, + eps=0.1, + norm=norm, + nb_iter=1, + eps_iter=-0.01, + ) + + def test_eps_equals_zero(self): + for norm in self.ord_list: + if norm == 1: + self.assertRaises( + NotImplementedError, + self.attack, + model_fn=self.model, + x=self.x, + eps=0, + norm=norm, + nb_iter=10, + eps_iter=0.01, + ) + else: + self.assertClose( + self.attack( + model_fn=self.model, + x=self.x, + eps=0, + norm=norm, + nb_iter=10, + eps_iter=0.01, + ), + self.x, + ) + + def test_eps_iter_equals_zero(self): + for norm in self.ord_list: + if norm == 1: + self.assertRaises( + NotImplementedError, + self.attack, + model_fn=self.model, + x=self.x, + eps=0.5, + norm=norm, + nb_iter=10, + eps_iter=0, + ) + else: + self.assertClose( + self.attack( + model_fn=self.model, + x=self.x, + eps=0.5, + norm=norm, + nb_iter=10, + eps_iter=0, + ), + self.x, + ) + + def test_invalid_clips(self): + clip_min = 0.5 + clip_max = -0.5 + for norm in self.ord_list: + if norm == 1: + self.assertRaises( + NotImplementedError, + self.attack, + model_fn=self.model, + x=self.x, + eps=0.1, + norm=norm, + clip_min=clip_min, + clip_max=clip_max, + nb_iter=10, + eps_iter=0.01, + ) + else: + self.assertRaises( + ValueError, + self.attack, + model_fn=self.model, + x=self.x, + eps=0.1, + norm=norm, + clip_min=clip_min, + clip_max=clip_max, + nb_iter=10, + eps_iter=0.01, + ) + + def test_adv_example_success_rate_linf(self): + # use normalized_x to make sure the same eps gives uniformly high attack + # success rate across randomized tests + self.help_adv_examples_success_rate( + x=self.normalized_x, model=self.model, norm=np.inf, **self.attack_param + ) + + def test_targeted_adv_example_success_rate_linf(self): + self.help_targeted_adv_examples_success_rate( + x=self.normalized_x, model=self.model, norm=np.inf, **self.attack_param + ) + + def test_adv_example_success_rate_l1(self): + self.assertRaises( + NotImplementedError, + self.help_adv_examples_success_rate, + model=self.model, + x=self.normalized_x, + norm=1, + **self.attack_param + ) + # TODO uncomment the actual test below after we have implemented the L1 attack + # self.help_adv_examples_success_rate(x=self.normalized_x, + # model=self.model, norm=1, **self.attack_param) + + def test_targeted_adv_example_success_rate_l1(self): + self.assertRaises( + NotImplementedError, + self.help_targeted_adv_examples_success_rate, + x=self.normalized_x, + model=self.model, + norm=1, + **self.attack_param + ) + # TODO uncomment the actual test below after we have implemented the L1 attack + # self.help_targeted_adv_examples_success_rate(x=self.normalized_x, + # model=self.model, norm=1, **self.attack_param) + + def test_adv_example_success_rate_l2(self): + self.help_adv_examples_success_rate( + model=self.model, x=self.normalized_x, norm=2, **self.attack_param + ) + + def test_targeted_adv_example_success_rate_l2(self): + self.help_targeted_adv_examples_success_rate( + model=self.model, x=self.normalized_x, norm=2, **self.attack_param + ) + + def test_do_not_reach_lp_boundary(self): + for norm in self.ord_list: + if norm == 1: + self.assertRaises( + NotImplementedError, + self.attack, + model_fn=self.model, + x=self.normalized_x, + eps=0.5, + nb_iter=10, + norm=norm, + eps_iter=0.01, + ) + continue + else: + x_adv = self.attack( + model_fn=self.model, + x=self.normalized_x, + eps=0.5, + nb_iter=10, + norm=norm, + eps_iter=0.01, + ) + + if norm == np.inf: + delta, _ = torch.abs(x_adv - self.normalized_x).max(dim=1) + elif norm == 1: + delta = torch.abs(x_adv - self.normalized_x).sum(dim=1) + elif norm == 2: + delta = torch.pow(x_adv - self.normalized_x, 2).sum(dim=1).pow(0.5) + diff = torch.max(0.5 - delta) + self.assertTrue(diff > 0.25) + + def test_attack_strength(self): + x_adv = self.attack( + model_fn=self.model, + x=self.normalized_x, + eps=1.0, + eps_iter=0.05, + norm=np.inf, + clip_min=0.5, + clip_max=0.7, + nb_iter=5, + sanity_checks=False, + ) + _, ori_label = self.model(self.normalized_x).max(1) + _, adv_label = self.model(x_adv).max(1) + adv_acc = adv_label.eq(ori_label).sum().to( + torch.float + ) / self.normalized_x.size(0) + self.assertLess(adv_acc, 0.1) + + def test_eps(self): + # test if the attack respects the norm constraint + # NOTE clip_eta makes sure that at each step, adv_x respects the eps + # norm constraint. Therefore, this is essentially a test on clip_eta, + # which is implemented in a separate test_clip_eta + raise SkipTest() + + def test_clip_eta(self): + # NOTE: this has been tested with test_clip_eta in test_utils + raise SkipTest() + + def test_clips(self): + clip_min = -1.0 + clip_max = 1.0 + for norm in self.ord_list: + if norm == 1: + self.assertRaises( + NotImplementedError, + model_fn=self.model, + x=self.normalized_x, + eps=0.3, + eps_iter=0.03, + norm=norm, + nb_iter=10, + clip_min=clip_min, + clip_max=clip_max, + ) + continue + else: + x_adv = self.attack( + model_fn=self.model, + x=self.normalized_x, + eps=0.3, + eps_iter=0.03, + norm=norm, + nb_iter=10, + clip_min=clip_min, + clip_max=clip_max, + ) + self.assertTrue(torch.all(x_adv <= clip_max)) + self.assertTrue(torch.all(x_adv >= clip_min)) + + def test_attack_does_not_cache_graph_computation_for_nb_iter(self): + # TODO not sure what the original test does in tests_tf/test_attacks + pass + + def test_multiple_initial_random_step(self): + _, ori_label = self.model(self.normalized_x).max(1) + new_label_multi = ori_label.clone().detach() + + for _ in range(10): + x_adv = self.attack( + model_fn=self.model, + x=self.normalized_x, + eps=0.5, + eps_iter=0.05, + norm=np.inf, + clip_min=0.5, + clip_max=0.7, + nb_iter=2, + sanity_checks=False, + ) + _, new_label = self.model(x_adv).max(1) + + # examples for which we have not found adversarial examples + i = ori_label.eq(new_label_multi) + new_label_multi[i] = new_label[i] + + failed_attack = ori_label.eq(new_label_multi).sum().to( + torch.float + ) / self.normalized_x.size(0) + self.assertLess(failed_attack, 0.5) + + +class TestCarliniWagnerL2(CommonAttackProperties): + def setUp(self): + super(TestCarliniWagnerL2, self).setUp() + self.attack = carlini_wagner_l2 + self.attack_param = { + "n_classes": 2, + "max_iterations": 100, + "binary_search_steps": 3, + "initial_const": 1, + } + + def test_adv_example_success_rate(self): + self.help_adv_examples_success_rate( + model=self.model, + x=self.normalized_x, + rate=0.1, + clip_min=-5, + clip_max=5, + **self.attack_param + ) + + def test_targeted_adv_example_success_rate(self): + self.help_targeted_adv_examples_success_rate( + model=self.model, + x=self.normalized_x, + rate=0.9, + clip_min=-5, + clip_max=5, + **self.attack_param + ) + + def test_adv_examples_clipped_successfully(self): + x_adv = self.attack( + model_fn=self.model, + x=self.normalized_x, + clip_min=-0.2, + clip_max=0.3, + **self.attack_param + ) + self.assertGreater(torch.min(x_adv), -0.201) + self.assertLess(torch.max(x_adv), 0.301) + + def test_high_confidence_adv_example(self): + from copy import copy + + attack_param_copy = copy(self.attack_param) + attack_param_copy["binary_search_steps"] = 2 + + x = self.trivial_normalized_x - 0.5 + _, y = self.trivial_model(x).max(1) + + for confidence in [0, 2.3]: + x_adv = self.attack( + model_fn=self.trivial_model, + x=x, + lr=1e-2, + clip_min=-10, + clip_max=10, + confidence=confidence, + **attack_param_copy + ) + logits = self.trivial_model(x_adv) + target = logits[range(len(logits)), 1 - y] + other = logits[range(len(logits)), y] + self.assertClose(confidence, torch.min(target - other).detach(), atol=1e-1) + self.assertTrue( + torch.argmax(logits, 1).eq(y).sum().to(torch.float) / len(logits) == 0 + ) + + def test_high_confidence_targeted_adv_example(self): + from copy import copy + + attack_param_copy = copy(self.attack_param) + attack_param_copy["binary_search_steps"] = 2 + + for confidence in [0, 2.3]: + x_adv = self.attack( + model_fn=self.trivial_model, + x=self.trivial_normalized_x - 0.5, + lr=1e-2, + clip_min=-10, + clip_max=10, + targeted=True, + y=self.y_target, + confidence=confidence, + **attack_param_copy + ) + logits = self.trivial_model(x_adv) + target = logits[range(len(logits)), self.y_target] + other = logits[range(len(logits)), 1 - self.y_target] + self.assertClose(confidence, torch.min(target - other).detach(), atol=1e-1) + self.assertGreater( + torch.argmax(logits, 1).eq(self.y_target).sum().to(torch.float) + / len(logits), + 0.9, + ) + + +class TestSPSA(CommonAttackProperties): + def setUp(self): + super(TestSPSA, self).setUp() + self.attack = spsa + self.attack_param = { + "eps": 0.5, + "clip_min": -5, + "clip_max": 5, + "nb_iter": 50, + "model": self.model, + "x": self.normalized_x, + } + + def test_invalid_input(self): + x = torch.tensor([[-20.0, 30.0]]) + self.assertRaises( + AssertionError, + self.attack, + model_fn=self.model, + x=x, + eps=0.1, + nb_iter=1, + clip_min=-1.0, + clip_max=1.0, + sanity_checks=True, + ) + + def test_invalid_eps(self): + self.assertRaises( + ValueError, self.attack, model_fn=self.model, x=self.x, eps=-0.1, nb_iter=1 + ) + + def test_eps_equals_zero(self): + self.assertClose( + self.attack(model_fn=self.model, x=self.x, eps=0, nb_iter=10), self.x + ) + + def test_invalid_clips(self): + self.assertRaises( + ValueError, + self.attack, + model_fn=self.model, + x=self.x, + eps=0.1, + clip_min=0.5, + clip_max=-0.5, + nb_iter=10, + ) + + def test_adv_example_success_rate_linf(self): + # use normalized_x to make sure the same eps gives uniformly high attack + # success rate across randomized tests + self.help_adv_examples_success_rate(**self.attack_param) + + def test_targeted_adv_example_success_rate_linf(self): + self.help_targeted_adv_examples_success_rate(**self.attack_param) + + def test_adv_example_success_rate_l1(self): + self.assertRaises( + NotImplementedError, + self.help_adv_examples_success_rate, + norm=1, + **self.attack_param + ) + # TODO uncomment the actual test below after we have implemented the L1 attack + # self.help_adv_examples_success_rate( + # norm=1, **self.attack_param) + + def test_targeted_adv_example_success_rate_l1(self): + self.assertRaises( + NotImplementedError, + self.help_targeted_adv_examples_success_rate, + norm=1, + **self.attack_param + ) + # TODO uncomment the actual test below after we have implemented the L1 attack + # self.help_targeted_adv_examples_success_rate( + # norm=1, **self.attack_param) + + def test_adv_example_success_rate_l2(self): + self.help_adv_examples_success_rate(norm=2, **self.attack_param) + + def test_targeted_adv_example_success_rate_l2(self): + self.help_targeted_adv_examples_success_rate(norm=2, **self.attack_param) + + def test_attack_strength(self): + x_adv = self.attack( + model_fn=self.model, + x=self.normalized_x, + eps=1.0, + clip_min=0.5, + clip_max=0.7, + nb_iter=20, + sanity_checks=False, + ) + _, ori_label = self.model(self.normalized_x).max(1) + _, adv_label = self.model(x_adv).max(1) + adv_acc = adv_label.eq(ori_label).sum().to( + torch.float + ) / self.normalized_x.size(0) + self.assertLess(adv_acc, 0.1) + + def test_eps(self): + x_adv = self.attack( + model_fn=self.model, x=self.normalized_x, eps=0.5, nb_iter=10 + ) + delta, _ = torch.abs(x_adv - self.normalized_x).max(dim=1) + self.assertTrue(torch.all(delta <= 0.5 + 1e-6)) + + def test_clips(self): + clip_min = -1.0 + clip_max = 1.0 + x_adv = self.attack( + model_fn=self.model, + x=self.normalized_x, + eps=0.3, + nb_iter=10, + clip_min=clip_min, + clip_max=clip_max, + ) + self.assertTrue(torch.all(x_adv <= clip_max)) + self.assertTrue(torch.all(x_adv >= clip_min)) + + +class TestHopSkipJumpAttack(CommonAttackProperties): + def setUp(self): + super(TestHopSkipJumpAttack, self).setUp() + self.attack = hop_skip_jump_attack + + def test_generate_np_untargeted_l2(self): + x_val = torch.rand(50, 2) + bapp_params = { + "norm": 2, + "stepsize_search": "geometric_progression", + "num_iterations": 10, + "verbose": True, + } + x_adv = self.attack(model_fn=self.model, x=x_val, **bapp_params) + + _, ori_label = self.model(x_val).max(1) + _, adv_label = self.model(x_adv).max(1) + adv_acc = adv_label.eq(ori_label).sum().to(torch.float) / x_val.size(0) + + self.assertLess(adv_acc, 0.1) + + def test_generate_untargeted_linf(self): + x_val = torch.rand(50, 2) + bapp_params = { + "norm": np.inf, + "stepsize_search": "grid_search", + "num_iterations": 10, + "verbose": True, + } + x_adv = self.attack(model_fn=self.model, x=x_val, **bapp_params) + + _, ori_label = self.model(x_val).max(1) + _, adv_label = self.model(x_adv).max(1) + adv_acc = adv_label.eq(ori_label).sum().to(torch.float) / x_val.size(0) + + self.assertLess(adv_acc, 0.1) + + def test_generate_np_targeted_linf(self): + x_val = torch.rand(200, 2) + + _, ori_label = self.model(x_val).max(1) + x_val_pos = x_val[ori_label == 1] + x_val_neg = x_val[ori_label == 0] + + x_val_under_attack = torch.cat((x_val_pos[:25], x_val_neg[:25]), dim=0) + y_target = torch.cat( + [torch.zeros(25, dtype=torch.int64), torch.ones(25, dtype=torch.int64)] + ) + image_target = torch.cat((x_val_neg[25:50], x_val_pos[25:50]), dim=0) + + bapp_params = { + "norm": np.inf, + "stepsize_search": "geometric_progression", + "num_iterations": 10, + "verbose": True, + "y_target": y_target, + "image_target": image_target, + } + x_adv = self.attack(model_fn=self.model, x=x_val_under_attack, **bapp_params) + + _, new_labs = self.model(x_adv).max(1) + + adv_acc = new_labs.eq(y_target).sum().to(torch.float) / y_target.size(0) + + self.assertGreater(adv_acc, 0.9) + + def test_generate_targeted_l2(self): + # Create data in numpy arrays. + x_val = torch.rand(200, 2) + + _, ori_label = self.model(x_val).max(1) + x_val_pos = x_val[ori_label == 1] + x_val_neg = x_val[ori_label == 0] + + x_val_under_attack = torch.cat((x_val_pos[:25], x_val_neg[:25]), dim=0) + y_target = torch.cat( + [torch.zeros(25, dtype=torch.int64), torch.ones(25, dtype=torch.int64)] + ) + image_target = torch.cat((x_val_neg[25:50], x_val_pos[25:50]), dim=0) + + # Create graph. + bapp_params = { + "norm": "l2", + "stepsize_search": "grid_search", + "num_iterations": 10, + "verbose": True, + "y_target": y_target, + "image_target": image_target, + } + x_adv = self.attack(model_fn=self.model, x=x_val_under_attack, **bapp_params) + + _, new_labs = self.model(x_adv).max(1) + + adv_acc = new_labs.eq(y_target).sum().to(torch.float) / y_target.size(0) + + self.assertGreater(adv_acc, 0.9) + + +class TestSparseL1Descent(CommonAttackProperties): + def setUp(self): + super(TestSparseL1Descent, self).setUp() + self.attack = sparse_l1_descent + + def generate_adversarial_examples(self, **kwargs): + x_adv = self.attack(model_fn=self.model, x=self.normalized_x, **kwargs) + _, ori_label = self.model(self.normalized_x).max(1) + _, adv_label = self.model(x_adv).max(1) + adv_acc = adv_label.eq(ori_label).sum().to( + torch.float + ) / self.normalized_x.size(0) + + delta = torch.sum(torch.abs(x_adv - self.normalized_x), dim=1) + return x_adv, delta, adv_acc + + def generate_targeted_adversarial_examples(self, **kwargs): + y_target = torch.randint(low=0, high=2, size=(self.normalized_x.size(0),)) + x_adv = self.attack( + model_fn=self.model, + x=self.normalized_x, + y=y_target, + targeted=True, + **kwargs + ) + + _, adv_label = self.model(x_adv).max(1) + adv_success = adv_label.eq(y_target).sum().to( + torch.float + ) / self.normalized_x.size(0) + + delta = torch.sum(torch.abs(x_adv - self.normalized_x), dim=1) + return x_adv, delta, adv_success + + def test_invalid_input(self): + x_val = -torch.ones((2, 2)) + with self.assertRaises(AssertionError): + self.attack(self.model, x_val, eps=10.0, clip_min=0.0, clip_max=1.0) + + def test_gives_adversarial_example(self): + _, delta, adv_acc = self.generate_adversarial_examples( + eps=2, clip_min=-5, clip_max=5 + ) + self.assertLess(adv_acc, 0.5) + self.assertLess(torch.max(torch.abs(delta - 2)), 1e-3) + + def test_targeted_gives_adversarial_example(self): + _, delta, adv_acc = self.generate_targeted_adversarial_examples( + eps=10, clip_min=-5, clip_max=5 + ) + self.assertGreater(adv_acc, 0.7) + self.assertLessEqual(torch.max(delta), 10.001) + + def test_can_be_called_with_different_eps(self): + for eps in [10, 20, 30, 40]: + _, delta, _ = self.generate_adversarial_examples( + eps=eps, clip_min=-5, clip_max=5 + ) + self.assertLessEqual(torch.max(delta), eps + 1e-4) + + def test_clip_works_as_expected(self): + x_adv, _, _ = self.generate_adversarial_examples( + eps=10, + nb_iter=20, + rand_init=True, + clip_min=-0.2, + clip_max=0.1, + sanity_checks=False, + ) + + self.assertClose(torch.min(x_adv), -0.2) + self.assertClose(torch.max(x_adv), 0.1) + + def test_do_not_reach_lp_boundary(self): + """ + Make sure that iterative attack don't reach boundary of Lp + neighbourhood if nb_iter * eps_iter is relatively small compared to + epsilon. + """ + _, delta, _ = self.generate_adversarial_examples( + eps=0.5, clip_min=-5, clip_max=5, nb_iter=10, eps_iter=0.01 + ) + self.assertTrue(torch.max(0.5 - delta) > 0.25) + + def test_generate_np_gives_clipped_adversarial_examples(self): + x_adv, _, _ = self.generate_adversarial_examples( + eps=1.0, + eps_iter=0.1, + nb_iter=5, + clip_min=-0.2, + clip_max=0.3, + sanity_checks=False, + ) + + self.assertLess(-0.201, torch.min(x_adv)) + self.assertLess(torch.max(x_adv), 0.301) + + def test_clip_eta(self): + _, delta, _ = self.generate_adversarial_examples( + eps=1, clip_min=-5, clip_max=5, nb_iter=5, eps_iter=0.1 + ) + + # this projection is less numerically stable so give it some slack + self.assertLessEqual(torch.max(delta), 1.0 + 1e-6) + + def test_attack_strength(self): + # sanity checks turned off because this test initializes outside + # the valid range. + _, _, adv_acc = self.generate_adversarial_examples( + eps=10, + rand_init=True, + clip_min=0.5, + clip_max=0.7, + nb_iter=10, + sanity_checks=False, + ) + + self.assertLess(adv_acc, 0.4) + + def test_grad_clip(self): + """ + With clipped gradients, we achieve + np.mean(orig_labels == new_labels) == 0.0 + """ + + # sanity checks turned off because this test initializes outside + # the valid range. + _, _, adv_acc = self.generate_adversarial_examples( + eps=10, + rand_init=True, + clip_grad=True, + clip_min=0.5, + clip_max=0.7, + nb_iter=10, + sanity_checks=False, + ) + self.assertLess(adv_acc, 0.1) + + def test_sparsity(self): + # use a model with larger input dimensionality for this test. + model_fn = DummyModel(1000) + x_val = torch.rand(100, 1000) + + for q in [1, 9, 25.8, 50, 75.4, 90.2, 99, 99.9]: + x_adv = self.attack( + model_fn, + x_val, + eps=5.0, + grad_sparsity=q, + nb_iter=1, + sanity_checks=False, + ) + + numzero = torch.sum(x_adv - x_val == 0, dim=-1).float() + self.assertAlmostEqual(q * 1000.0 / 100.0, torch.mean(numzero), delta=1) + + def test_grad_sparsity_checks(self): + # test that the attacks allows `grad_sparsity` to be specified as a scalar + # in (0, 100) or as a vector. + + # scalar values out of range + with self.assertRaises(ValueError): + self.generate_adversarial_examples(grad_sparsity=0) + + with self.assertRaises(ValueError): + self.generate_adversarial_examples(grad_sparsity=100) + + # sparsity as 2D array should fail + with self.assertRaises(ValueError): + gs = torch.empty(100, 2).uniform_(90, 99) + self.generate_adversarial_examples(sanity_checks=False, grad_sparsity=gs) + + # sparsity as 1D array should succeed + gs = torch.empty(100).uniform_(90, 99) + self.generate_adversarial_examples(sanity_checks=False, grad_sparsity=gs) + + # sparsity vector of wrong size should fail + with self.assertRaises(ValueError) as context: + gs = torch.empty(101).uniform_(90, 99) + self.generate_adversarial_examples(sanity_checks=False, grad_sparsity=gs) diff --git a/cleverhans/torch/tests/test_utils.py b/cleverhans/torch/tests/test_utils.py new file mode 100644 index 000000000..29f11a99b --- /dev/null +++ b/cleverhans/torch/tests/test_utils.py @@ -0,0 +1,111 @@ +# pylint: disable=missing-docstring +import numpy as np +import torch + +import cleverhans.torch.utils as utils +from cleverhans.devtools.checks import CleverHansTest + + +class TestOptimizeLinear(CleverHansTest): + """ + Identical to the TestOptimizeLinear in tests_tf/test_attacks. + """ + + def setUp(self): + super(TestOptimizeLinear, self).setUp() + self.clip_eta = utils.clip_eta + self.rand_grad = torch.randn(100, 3, 2) + self.rand_eta = torch.randn(100, 3, 2) + self.red_ind = list(range(1, len(self.rand_grad.size()))) + # eps needs to be nonnegative + self.eps_list = [0, 0.1, 1.0, 3] + + def test_optimize_linear_linf(self): + grad = torch.tensor([[1.0, -2.0]]) + eta = utils.optimize_linear(grad, eps=1.0, norm=np.inf) + objective = torch.sum(grad * eta) + + self.assertEqual(grad.size(), eta.size()) + self.assertClose(objective, grad.abs().sum()) + self.assertClose(eta.abs(), 1.0) + + def test_optimize_linear_l2(self): + grad = torch.tensor([[0.5 ** 0.5, -(0.5 ** 0.5)]]) + eta = utils.optimize_linear(grad, eps=1.0, norm=2) + objective = torch.sum(grad * eta) + + self.assertEqual(grad.size(), eta.size()) + self.assertClose(objective, 1.0) + self.assertClose(eta.pow(2).sum().sqrt(), 1.0) + + def test_optimize_linear_l1(self): + grad = torch.tensor([[1.0, -2.0]]) + eta = utils.optimize_linear(grad, eps=1.0, norm=1) + objective = torch.sum(grad * eta) + + self.assertEqual(grad.size(), eta.size()) + self.assertClose(objective, 2.0) + self.assertClose(eta.abs().sum(), 1.0) + + def test_optimize_linear_l1_ties(self): + grad = torch.tensor([[2.0, -2.0]]) + eta = utils.optimize_linear(grad, eps=1.0, norm=1) + objective = torch.sum(grad * eta) + + self.assertEqual(grad.size(), eta.size()) + self.assertClose(objective, 2.0) + self.assertClose(eta.abs().sum(), 1.0) + + def test_optimize_linear_linf_satisfies_norm_constraint(self): + for eps in self.eps_list: + eta = utils.optimize_linear(self.rand_grad, eps=eps, norm=np.inf) + self.assertClose(eta.abs(), eps) + + def test_optimize_linear_l1_satisfies_norm_constraint(self): + for eps in self.eps_list: + eta = utils.optimize_linear(self.rand_grad, eps=eps, norm=1) + norm = eta.abs().sum(dim=self.red_ind) + self.assertTrue(torch.allclose(norm, eps * torch.ones_like(norm))) + + def test_optimize_linear_l2_satisfies_norm_constraint(self): + for eps in self.eps_list: + eta = utils.optimize_linear(self.rand_grad, eps=eps, norm=2) + # optimize_linear uses avoid_zero_div as the divisor for + # gradients with overly small l2 norms when performing norm + # normalizations on the gradients so as to safeguard against + # zero division error. Therefore, the replaced gradient vectors + # will not be l2-unit vectors after normalization. In this test, + # these gradients are filtered out by the one_mask + # below and are not tested. + # NOTE the value of avoid_zero_div should be the same as the + # avoid_zero_div used in the optimize_linear function + avoid_zero_div = torch.tensor(1e-12) + square = torch.max( + avoid_zero_div, + torch.sum(self.rand_grad ** 2, self.red_ind, keepdim=True), + ) + norm = eta.pow(2).sum(dim=self.red_ind, keepdim=True).sqrt() + one_mask = (square <= avoid_zero_div).to(torch.float) * norm + ( + square > avoid_zero_div + ).to(torch.float) + self.assertTrue(torch.allclose(norm, eps * one_mask)) + + def test_clip_eta_linf(self): + clipped = self.clip_eta(eta=self.rand_eta, norm=np.inf, eps=0.5) + self.assertTrue(torch.all(clipped <= 0.5)) + self.assertTrue(torch.all(clipped >= -0.5)) + + def test_clip_eta_l1(self): + self.assertRaises( + NotImplementedError, self.clip_eta, eta=self.rand_eta, norm=1, eps=0.5 + ) + + # TODO uncomment the actual test below after we have implemented the L1 attack + # clipped = self.clip_eta(eta=self.rand_eta, norm=1, eps=.5) + # norm = clipped.abs().sum(dim=self.red_ind) + # self.assertTrue(torch.all(norm <= .5001)) + + def test_clip_eta_l2(self): + clipped = self.clip_eta(eta=self.rand_eta, norm=2, eps=0.5) + norm = clipped.pow(2).sum(dim=self.red_ind).pow(0.5) + self.assertTrue(torch.all(norm <= 0.5001)) diff --git a/cleverhans/torch/utils.py b/cleverhans/torch/utils.py new file mode 100644 index 000000000..f5ada925a --- /dev/null +++ b/cleverhans/torch/utils.py @@ -0,0 +1,145 @@ +"""Utils for PyTorch""" + +import numpy as np + +import torch + + +def clip_eta(eta, norm, eps): + """ + PyTorch implementation of the clip_eta in utils_tf. + + :param eta: Tensor + :param norm: np.inf, 1, or 2 + :param eps: float + """ + if norm not in [np.inf, 1, 2]: + raise ValueError("norm must be np.inf, 1, or 2.") + + avoid_zero_div = torch.tensor(1e-12, dtype=eta.dtype, device=eta.device) + reduc_ind = list(range(1, len(eta.size()))) + if norm == np.inf: + eta = torch.clamp(eta, -eps, eps) + else: + if norm == 1: + raise NotImplementedError("L1 clip is not implemented.") + norm = torch.max( + avoid_zero_div, torch.sum(torch.abs(eta), dim=reduc_ind, keepdim=True) + ) + elif norm == 2: + norm = torch.sqrt( + torch.max( + avoid_zero_div, torch.sum(eta ** 2, dim=reduc_ind, keepdim=True) + ) + ) + factor = torch.min( + torch.tensor(1.0, dtype=eta.dtype, device=eta.device), eps / norm + ) + eta *= factor + return eta + + +def get_or_guess_labels(model, x, **kwargs): + """ + Get the label to use in generating an adversarial example for x. + The kwargs are fed directly from the kwargs of the attack. + If 'y' is in kwargs, then assume it's an untargeted attack and + use that as the label. + If 'y_target' is in kwargs and is not none, then assume it's a + targeted attack and use that as the label. + Otherwise, use the model's prediction as the label and perform an + untargeted attack. + + :param model: PyTorch model. Do not add a softmax gate to the output. + :param x: Tensor, shape (N, d_1, ...). + :param y: (optional) Tensor, shape (N). + :param y_target: (optional) Tensor, shape (N). + """ + if "y" in kwargs and "y_target" in kwargs: + raise ValueError("Can not set both 'y' and 'y_target'.") + if "y" in kwargs: + labels = kwargs["y"] + elif "y_target" in kwargs and kwargs["y_target"] is not None: + labels = kwargs["y_target"] + else: + _, labels = torch.max(model(x), 1) + return labels + + +def optimize_linear(grad, eps, norm=np.inf): + """ + Solves for the optimal input to a linear function under a norm constraint. + + Optimal_perturbation = argmax_{eta, ||eta||_{norm} < eps} dot(eta, grad) + + :param grad: Tensor, shape (N, d_1, ...). Batch of gradients + :param eps: float. Scalar specifying size of constraint region + :param norm: np.inf, 1, or 2. Order of norm constraint. + :returns: Tensor, shape (N, d_1, ...). Optimal perturbation + """ + + red_ind = list(range(1, len(grad.size()))) + avoid_zero_div = torch.tensor(1e-12, dtype=grad.dtype, device=grad.device) + if norm == np.inf: + # Take sign of gradient + optimal_perturbation = torch.sign(grad) + elif norm == 1: + abs_grad = torch.abs(grad) + sign = torch.sign(grad) + red_ind = list(range(1, len(grad.size()))) + abs_grad = torch.abs(grad) + ori_shape = [1] * len(grad.size()) + ori_shape[0] = grad.size(0) + + max_abs_grad, _ = torch.max(abs_grad.view(grad.size(0), -1), 1) + max_mask = abs_grad.eq(max_abs_grad.view(ori_shape)).to(torch.float) + num_ties = max_mask + for red_scalar in red_ind: + num_ties = torch.sum(num_ties, red_scalar, keepdim=True) + optimal_perturbation = sign * max_mask / num_ties + # TODO integrate below to a test file + # check that the optimal perturbations have been correctly computed + opt_pert_norm = optimal_perturbation.abs().sum(dim=red_ind) + assert torch.all(opt_pert_norm == torch.ones_like(opt_pert_norm)) + elif norm == 2: + square = torch.max(avoid_zero_div, torch.sum(grad ** 2, red_ind, keepdim=True)) + optimal_perturbation = grad / torch.sqrt(square) + # TODO integrate below to a test file + # check that the optimal perturbations have been correctly computed + opt_pert_norm = ( + optimal_perturbation.pow(2).sum(dim=red_ind, keepdim=True).sqrt() + ) + one_mask = (square <= avoid_zero_div).to(torch.float) * opt_pert_norm + ( + square > avoid_zero_div + ).to(torch.float) + assert torch.allclose(opt_pert_norm, one_mask, rtol=1e-05, atol=1e-08) + else: + raise NotImplementedError( + "Only L-inf, L1 and L2 norms are " "currently implemented." + ) + + # Scale perturbation to be the solution for the norm=eps rather than + # norm=1 problem + scaled_perturbation = eps * optimal_perturbation + return scaled_perturbation + + +def zero_out_clipped_grads(grad, x, clip_min, clip_max): + """ + Helper function to erase entries in the gradient where the update would be + clipped. + :param grad: The gradient + :param x: The current input + :param clip_min: Minimum input component value + :param clip_max: Maximum input component value + """ + signed_grad = torch.sign(grad) + + # Find input components that lie at the boundary of the input range, and + # where the gradient points in the wrong direction. + clip_low = torch.le(x, clip_min) & torch.lt(signed_grad, 0) + clip_high = torch.ge(x, clip_max) & torch.gt(signed_grad, 0) + clip = clip_low | clip_high + grad = torch.where(clip, torch.zeros_like(grad), grad) + + return grad diff --git a/cleverhans/train.py b/cleverhans/train.py deleted file mode 100644 index f7ffff8e2..000000000 --- a/cleverhans/train.py +++ /dev/null @@ -1,309 +0,0 @@ -""" -Multi-replica synchronous training - - -NOTE: This module is much more free to change than many other modules -in CleverHans. CleverHans is very conservative about changes to any -code that affects the output of benchmark tests (attacks, evaluation -methods, etc.). This module provides *model training* functionality -not *benchmarks* and thus is free to change rapidly to provide better -speed, accuracy, etc. -""" -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - -import logging -import os -import time -import warnings - -import math -import numpy as np -from six.moves import xrange -import tensorflow as tf - -from cleverhans import canary -from cleverhans.utils import _ArgsWrapper, create_logger -from cleverhans.utils import safe_zip -from cleverhans.utils_tf import infer_devices -from cleverhans.utils_tf import initialize_uninitialized_global_variables - - -_logger = create_logger("train") -_logger.setLevel(logging.INFO) - - -def train(sess, loss, x_train, y_train, - init_all=False, evaluate=None, feed=None, args=None, - rng=None, var_list=None, fprop_args=None, optimizer=None, - devices=None, x_batch_preprocessor=None, use_ema=False, - ema_decay=.998, run_canary=None, - loss_threshold=1e5, dataset_train=None, dataset_size=None): - """ - Run (optionally multi-replica, synchronous) training to minimize `loss` - :param sess: TF session to use when training the graph - :param loss: tensor, the loss to minimize - :param x_train: numpy array with training inputs or tf Dataset - :param y_train: numpy array with training outputs or tf Dataset - :param init_all: (boolean) If set to true, all TF variables in the session - are (re)initialized, otherwise only previously - uninitialized variables are initialized before training. - :param evaluate: function that is run after each training iteration - (typically to display the test/validation accuracy). - :param feed: An optional dictionary that is appended to the feeding - dictionary before the session runs. Can be used to feed - the learning phase of a Keras model for instance. - :param args: dict or argparse `Namespace` object. - Should contain `nb_epochs`, `learning_rate`, - `batch_size` - :param rng: Instance of numpy.random.RandomState - :param var_list: Optional list of parameters to train. - :param fprop_args: dict, extra arguments to pass to fprop (loss and model). - :param optimizer: Optimizer to be used for training - :param devices: list of device names to use for training - If None, defaults to: all GPUs, if GPUs are available - all devices, if no GPUs are available - :param x_batch_preprocessor: callable - Takes a single tensor containing an x_train batch as input - Returns a single tensor containing an x_train batch as output - Called to preprocess the data before passing the data to the Loss - :param use_ema: bool - If true, uses an exponential moving average of the model parameters - :param ema_decay: float or callable - The decay parameter for EMA, if EMA is used - If a callable rather than a float, this is a callable that takes - the epoch and batch as arguments and returns the ema_decay for - the current batch. - :param loss_threshold: float - Raise an exception if the loss exceeds this value. - This is intended to rapidly detect numerical problems. - Sometimes the loss may legitimately be higher than this value. In - such cases, raise the value. If needed it can be np.inf. - :param dataset_train: tf Dataset instance. - Used as a replacement for x_train, y_train for faster performance. - :param dataset_size: integer, the size of the dataset_train. - :return: True if model trained - """ - - # Check whether the hardware is working correctly - canary.run_canary() - if run_canary is not None: - warnings.warn("The `run_canary` argument is deprecated. The canary " - "is now much cheaper and thus runs all the time. The " - "canary now uses its own loss function so it is not " - "necessary to turn off the canary when training with " - " a stochastic loss. Simply quit passing `run_canary`." - "Passing `run_canary` may become an error on or after " - "2019-10-16.") - - args = _ArgsWrapper(args or {}) - fprop_args = fprop_args or {} - - # Check that necessary arguments were given (see doc above) - # Be sure to support 0 epochs for debugging purposes - if args.nb_epochs is None: - raise ValueError("`args` must specify number of epochs") - if optimizer is None: - if args.learning_rate is None: - raise ValueError("Learning rate was not given in args dict") - assert args.batch_size, "Batch size was not given in args dict" - - if rng is None: - rng = np.random.RandomState() - - if optimizer is None: - optimizer = tf.train.AdamOptimizer(learning_rate=args.learning_rate) - else: - if not isinstance(optimizer, tf.train.Optimizer): - raise ValueError("optimizer object must be from a child class of " - "tf.train.Optimizer") - - grads = [] - xs = [] - preprocessed_xs = [] - ys = [] - if dataset_train is not None: - assert x_train is None and y_train is None and x_batch_preprocessor is None - if dataset_size is None: - raise ValueError("You must provide a dataset size") - data_iterator = dataset_train.make_one_shot_iterator().get_next() - x_train, y_train = sess.run(data_iterator) - - devices = infer_devices(devices) - for device in devices: - with tf.device(device): - x = tf.placeholder(x_train.dtype, (None,) + x_train.shape[1:]) - y = tf.placeholder(y_train.dtype, (None,) + y_train.shape[1:]) - xs.append(x) - ys.append(y) - - if x_batch_preprocessor is not None: - x = x_batch_preprocessor(x) - - # We need to keep track of these so that the canary can feed - # preprocessed values. If the canary had to feed raw values, - # stochastic preprocessing could make the canary fail. - preprocessed_xs.append(x) - - loss_value = loss.fprop(x, y, **fprop_args) - - grads.append(optimizer.compute_gradients( - loss_value, var_list=var_list)) - num_devices = len(devices) - print("num_devices: ", num_devices) - - grad = avg_grads(grads) - # Trigger update operations within the default graph (such as batch_norm). - with tf.control_dependencies(tf.get_collection(tf.GraphKeys.UPDATE_OPS)): - train_step = optimizer.apply_gradients(grad) - - epoch_tf = tf.placeholder(tf.int32, []) - batch_tf = tf.placeholder(tf.int32, []) - - if use_ema: - if callable(ema_decay): - ema_decay = ema_decay(epoch_tf, batch_tf) - ema = tf.train.ExponentialMovingAverage(decay=ema_decay) - with tf.control_dependencies([train_step]): - train_step = ema.apply(var_list) - # Get pointers to the EMA's running average variables - avg_params = [ema.average(param) for param in var_list] - # Make temporary buffers used for swapping the live and running average - # parameters - tmp_params = [tf.Variable(param, trainable=False) - for param in var_list] - # Define the swapping operation - param_to_tmp = [tf.assign(tmp, param) - for tmp, param in safe_zip(tmp_params, var_list)] - with tf.control_dependencies(param_to_tmp): - avg_to_param = [tf.assign(param, avg) - for param, avg in safe_zip(var_list, avg_params)] - with tf.control_dependencies(avg_to_param): - tmp_to_avg = [tf.assign(avg, tmp) - for avg, tmp in safe_zip(avg_params, tmp_params)] - swap = tmp_to_avg - - batch_size = args.batch_size - - assert batch_size % num_devices == 0 - device_batch_size = batch_size // num_devices - - if init_all: - sess.run(tf.global_variables_initializer()) - else: - initialize_uninitialized_global_variables(sess) - - for epoch in xrange(args.nb_epochs): - if dataset_train is not None: - nb_batches = int(math.ceil(float(dataset_size) / batch_size)) - else: - # Indices to shuffle training set - index_shuf = list(range(len(x_train))) - # Randomly repeat a few training examples each epoch to avoid - # having a too-small batch - while len(index_shuf) % batch_size != 0: - index_shuf.append(rng.randint(len(x_train))) - nb_batches = len(index_shuf) // batch_size - rng.shuffle(index_shuf) - # Shuffling here versus inside the loop doesn't seem to affect - # timing very much, but shuffling here makes the code slightly - # easier to read - x_train_shuffled = x_train[index_shuf] - y_train_shuffled = y_train[index_shuf] - - prev = time.time() - for batch in range(nb_batches): - if dataset_train is not None: - x_train_shuffled, y_train_shuffled = sess.run(data_iterator) - start, end = 0, batch_size - else: - # Compute batch start and end indices - start = batch * batch_size - end = (batch + 1) * batch_size - # Perform one training step - diff = end - start - assert diff == batch_size - - feed_dict = {epoch_tf: epoch, batch_tf: batch} - for dev_idx in xrange(num_devices): - cur_start = start + dev_idx * device_batch_size - cur_end = start + (dev_idx + 1) * device_batch_size - feed_dict[xs[dev_idx]] = x_train_shuffled[cur_start:cur_end] - feed_dict[ys[dev_idx]] = y_train_shuffled[cur_start:cur_end] - if cur_end != end and dataset_train is None: - msg = ("batch_size (%d) must be a multiple of num_devices " - "(%d).\nCUDA_VISIBLE_DEVICES: %s" - "\ndevices: %s") - args = (batch_size, num_devices, - os.environ['CUDA_VISIBLE_DEVICES'], - str(devices)) - raise ValueError(msg % args) - if feed is not None: - feed_dict.update(feed) - - _, loss_numpy = sess.run( - [train_step, loss_value], feed_dict=feed_dict) - - if np.abs(loss_numpy) > loss_threshold: - raise ValueError("Extreme loss during training: ", loss_numpy) - if np.isnan(loss_numpy) or np.isinf(loss_numpy): - raise ValueError("NaN/Inf loss during training") - assert (dataset_train is not None or - end == len(index_shuf)) # Check that all examples were used - cur = time.time() - _logger.info("Epoch " + str(epoch) + " took " + - str(cur - prev) + " seconds") - if evaluate is not None: - if use_ema: - # Before running evaluation, load the running average - # parameters into the live slot, so we can see how well - # the EMA parameters are performing - sess.run(swap) - evaluate() - if use_ema: - # Swap the parameters back, so that we continue training - # on the live parameters - sess.run(swap) - if use_ema: - # When training is done, swap the running average parameters into - # the live slot, so that we use them when we deploy the model - sess.run(swap) - - return True - - -def avg_grads(tower_grads): - """Calculate the average gradient for each shared variable across all - towers. - Note that this function provides a synchronization point across all towers. - Args: - tower_grads: List of lists of (gradient, variable) tuples. The outer list - is over individual gradients. The inner list is over the gradient - calculation for each tower. - Returns: - List of pairs of (gradient, variable) where the gradient has been - averaged across all towers. - - Modified from this tutorial: https://tinyurl.com/n3jr2vm - """ - if len(tower_grads) == 1: - return tower_grads[0] - average_grads = [] - for grad_and_vars in zip(*tower_grads): - # Note that each grad_and_vars looks like the following: - # ((grad0_gpu0, var0_gpu0), ... , (grad0_gpuN, var0_gpuN)) - grads = [g for g, _ in grad_and_vars] - - # Average over the 'tower' dimension. - grad = tf.add_n(grads) / len(grads) - - # Keep in mind that the Variables are redundant because they are shared - # across towers. So .. we will just return the first tower's pointer to - # the Variable. - v = grad_and_vars[0][1] - assert all(v is grad_and_var[1] for grad_and_var in grad_and_vars) - grad_and_var = (grad, v) - average_grads.append(grad_and_var) - return average_grads diff --git a/cleverhans/utils.py b/cleverhans/utils.py index 77292e11a..fd0561afe 100644 --- a/cleverhans/utils.py +++ b/cleverhans/utils.py @@ -16,9 +16,22 @@ import numpy as np from six.moves import xrange -known_number_types = (int, float, np.float16, np.float32, np.float64, - np.int8, np.int16, np.int32, np.int32, np.int64, - np.uint8, np.uint16, np.uint32, np.uint64) +known_number_types = ( + int, + float, + np.float16, + np.float32, + np.float64, + np.int8, + np.int16, + np.int32, + np.int32, + np.int64, + np.uint8, + np.uint16, + np.uint32, + np.uint64, +) CLEVERHANS_ROOT = os.path.dirname(os.path.dirname(__file__)) @@ -26,324 +39,347 @@ class _ArgsWrapper(object): - """ - Wrapper that allows attribute access to dictionaries - """ + """ + Wrapper that allows attribute access to dictionaries + """ - def __init__(self, args): - if not isinstance(args, dict): - args = vars(args) - self.args = args + def __init__(self, args): + if not isinstance(args, dict): + args = vars(args) + self.args = args - def __getattr__(self, name): - return self.args.get(name) + def __getattr__(self, name): + return self.args.get(name) class AccuracyReport(object): - """ - An object summarizing the accuracy results for experiments involving - training on clean examples or adversarial examples, then evaluating - on clean or adversarial examples. - """ + """ + An object summarizing the accuracy results for experiments involving + training on clean examples or adversarial examples, then evaluating + on clean or adversarial examples. + """ - def __init__(self): - self.clean_train_clean_eval = 0. - self.clean_train_adv_eval = 0. - self.adv_train_clean_eval = 0. - self.adv_train_adv_eval = 0. + def __init__(self): + self.clean_train_clean_eval = 0.0 + self.clean_train_adv_eval = 0.0 + self.adv_train_clean_eval = 0.0 + self.adv_train_adv_eval = 0.0 - # Training data accuracy results to be used by tutorials - self.train_clean_train_clean_eval = 0. - self.train_clean_train_adv_eval = 0. - self.train_adv_train_clean_eval = 0. - self.train_adv_train_adv_eval = 0. + # Training data accuracy results to be used by tutorials + self.train_clean_train_clean_eval = 0.0 + self.train_clean_train_adv_eval = 0.0 + self.train_adv_train_clean_eval = 0.0 + self.train_adv_train_adv_eval = 0.0 def batch_indices(batch_nb, data_length, batch_size): - """ - This helper function computes a batch start and end index - :param batch_nb: the batch number - :param data_length: the total length of the data being parsed by batches - :param batch_size: the number of inputs in each batch - :return: pair of (start, end) indices - """ - # Batch start and end index - start = int(batch_nb * batch_size) - end = int((batch_nb + 1) * batch_size) - - # When there are not enough inputs left, we reuse some to complete the - # batch - if end > data_length: - shift = end - data_length - start -= shift - end -= shift - - return start, end + """ + This helper function computes a batch start and end index + :param batch_nb: the batch number + :param data_length: the total length of the data being parsed by batches + :param batch_size: the number of inputs in each batch + :return: pair of (start, end) indices + """ + # Batch start and end index + start = int(batch_nb * batch_size) + end = int((batch_nb + 1) * batch_size) + + # When there are not enough inputs left, we reuse some to complete the + # batch + if end > data_length: + shift = end - data_length + start -= shift + end -= shift + + return start, end def other_classes(nb_classes, class_ind): - """ - Returns a list of class indices excluding the class indexed by class_ind - :param nb_classes: number of classes in the task - :param class_ind: the class index to be omitted - :return: list of class indices excluding the class indexed by class_ind - """ - if class_ind < 0 or class_ind >= nb_classes: - error_str = "class_ind must be within the range (0, nb_classes - 1)" - raise ValueError(error_str) + """ + Returns a list of class indices excluding the class indexed by class_ind + :param nb_classes: number of classes in the task + :param class_ind: the class index to be omitted + :return: list of class indices excluding the class indexed by class_ind + """ + if class_ind < 0 or class_ind >= nb_classes: + error_str = "class_ind must be within the range (0, nb_classes - 1)" + raise ValueError(error_str) - other_classes_list = list(range(nb_classes)) - other_classes_list.remove(class_ind) + other_classes_list = list(range(nb_classes)) + other_classes_list.remove(class_ind) - return other_classes_list + return other_classes_list def to_categorical(y, nb_classes, num_classes=None): - """ - Converts a class vector (integers) to binary class matrix. - This is adapted from the Keras function with the same name. - :param y: class vector to be converted into a matrix - (integers from 0 to nb_classes). - :param nb_classes: nb_classes: total number of classes. - :param num_classses: depricated version of nb_classes - :return: A binary matrix representation of the input. - """ - if num_classes is not None: - if nb_classes is not None: - raise ValueError("Should not specify both nb_classes and its deprecated " - "alias, num_classes") - warnings.warn("`num_classes` is deprecated. Switch to `nb_classes`." - " `num_classes` may be removed on or after 2019-04-23.") - nb_classes = num_classes - del num_classes - y = np.array(y, dtype='int').ravel() - n = y.shape[0] - categorical = np.zeros((n, nb_classes)) - categorical[np.arange(n), y] = 1 - return categorical + """ + Converts a class vector (integers) to binary class matrix. + This is adapted from the Keras function with the same name. + :param y: class vector to be converted into a matrix + (integers from 0 to nb_classes). + :param nb_classes: nb_classes: total number of classes. + :param num_classses: depricated version of nb_classes + :return: A binary matrix representation of the input. + """ + if num_classes is not None: + if nb_classes is not None: + raise ValueError( + "Should not specify both nb_classes and its deprecated " + "alias, num_classes" + ) + warnings.warn( + "`num_classes` is deprecated. Switch to `nb_classes`." + " `num_classes` may be removed on or after 2019-04-23." + ) + nb_classes = num_classes + del num_classes + y = np.array(y, dtype="int").ravel() + n = y.shape[0] + categorical = np.zeros((n, nb_classes)) + categorical[np.arange(n), y] = 1 + return categorical def random_targets(gt, nb_classes): - """ - Take in an array of correct labels and randomly select a different label - for each label in the array. This is typically used to randomly select a - target class in targeted adversarial examples attacks (i.e., when the - search algorithm takes in both a source class and target class to compute - the adversarial example). - :param gt: the ground truth (correct) labels. They can be provided as a - 1D vector or 2D array of one-hot encoded labels. - :param nb_classes: The number of classes for this task. The random class - will be chosen between 0 and nb_classes such that it - is different from the correct class. - :return: A numpy array holding the randomly-selected target classes - encoded as one-hot labels. - """ - # If the ground truth labels are encoded as one-hot, convert to labels. - if len(gt.shape) == 2: - gt = np.argmax(gt, axis=1) - - # This vector will hold the randomly selected labels. - result = np.zeros(gt.shape, dtype=np.int32) - - for class_ind in xrange(nb_classes): - # Compute all indices in that class. - in_cl = gt == class_ind - size = np.sum(in_cl) - - # Compute the set of potential targets for this class. - potential_targets = other_classes(nb_classes, class_ind) - - # Draw with replacement random targets among the potential targets. - result[in_cl] = np.random.choice(potential_targets, size=size) - - # Encode vector of random labels as one-hot labels. - result = to_categorical(result, nb_classes) - result = result.astype(np.int32) - - return result + """ + Take in an array of correct labels and randomly select a different label + for each label in the array. This is typically used to randomly select a + target class in targeted adversarial examples attacks (i.e., when the + search algorithm takes in both a source class and target class to compute + the adversarial example). + :param gt: the ground truth (correct) labels. They can be provided as a + 1D vector or 2D array of one-hot encoded labels. + :param nb_classes: The number of classes for this task. The random class + will be chosen between 0 and nb_classes such that it + is different from the correct class. + :return: A numpy array holding the randomly-selected target classes + encoded as one-hot labels. + """ + # If the ground truth labels are encoded as one-hot, convert to labels. + if len(gt.shape) == 2: + gt = np.argmax(gt, axis=1) + + # This vector will hold the randomly selected labels. + result = np.zeros(gt.shape, dtype=np.int32) + + for class_ind in xrange(nb_classes): + # Compute all indices in that class. + in_cl = gt == class_ind + size = np.sum(in_cl) + + # Compute the set of potential targets for this class. + potential_targets = other_classes(nb_classes, class_ind) + + # Draw with replacement random targets among the potential targets. + result[in_cl] = np.random.choice(potential_targets, size=size) + + # Encode vector of random labels as one-hot labels. + result = to_categorical(result, nb_classes) + result = result.astype(np.int32) + + return result def pair_visual(*args, **kwargs): - """Deprecation wrapper""" - warnings.warn("`pair_visual` has moved to `cleverhans.plot.pyplot_image`. " - "cleverhans.utils.pair_visual may be removed on or after " - "2019-04-24.") - from cleverhans.plot.pyplot_image import pair_visual as new_pair_visual - return new_pair_visual(*args, **kwargs) + """Deprecation wrapper""" + warnings.warn( + "`pair_visual` has moved to `cleverhans.plot.pyplot_image`. " + "cleverhans.utils.pair_visual may be removed on or after " + "2019-04-24." + ) + from cleverhans.plot.pyplot_image import pair_visual as new_pair_visual + + return new_pair_visual(*args, **kwargs) def grid_visual(*args, **kwargs): - """Deprecation wrapper""" - warnings.warn("`grid_visual` has moved to `cleverhans.plot.pyplot_image`. " - "cleverhans.utils.grid_visual may be removed on or after " - "2019-04-24.") - from cleverhans.plot.pyplot_image import grid_visual as new_grid_visual - return new_grid_visual(*args, **kwargs) + """Deprecation wrapper""" + warnings.warn( + "`grid_visual` has moved to `cleverhans.plot.pyplot_image`. " + "cleverhans.utils.grid_visual may be removed on or after " + "2019-04-24." + ) + from cleverhans.plot.pyplot_image import grid_visual as new_grid_visual + + return new_grid_visual(*args, **kwargs) def get_logits_over_interval(*args, **kwargs): - """Deprecation wrapper""" - warnings.warn("`get_logits_over_interval` has moved to " - "`cleverhans.plot.pyplot_image`. " - "cleverhans.utils.get_logits_over_interval may be removed on " - "or after 2019-04-24.") - # pylint:disable=line-too-long - from cleverhans.plot.pyplot_image import get_logits_over_interval as new_get_logits_over_interval - return new_get_logits_over_interval(*args, **kwargs) + """Deprecation wrapper""" + warnings.warn( + "`get_logits_over_interval` has moved to " + "`cleverhans.plot.pyplot_image`. " + "cleverhans.utils.get_logits_over_interval may be removed on " + "or after 2019-04-24." + ) + # pylint:disable=line-too-long + from cleverhans.plot.pyplot_image import ( + get_logits_over_interval as new_get_logits_over_interval, + ) + + return new_get_logits_over_interval(*args, **kwargs) def linear_extrapolation_plot(*args, **kwargs): - """Deprecation wrapper""" - warnings.warn("`linear_extrapolation_plot` has moved to " - "`cleverhans.plot.pyplot_image`. " - "cleverhans.utils.linear_extrapolation_plot may be removed on " - "or after 2019-04-24.") - # pylint:disable=line-too-long - from cleverhans.plot.pyplot_image import linear_extrapolation_plot as new_linear_extrapolation_plot - return new_linear_extrapolation_plot(*args, **kwargs) + """Deprecation wrapper""" + warnings.warn( + "`linear_extrapolation_plot` has moved to " + "`cleverhans.plot.pyplot_image`. " + "cleverhans.utils.linear_extrapolation_plot may be removed on " + "or after 2019-04-24." + ) + # pylint:disable=line-too-long + from cleverhans.plot.pyplot_image import ( + linear_extrapolation_plot as new_linear_extrapolation_plot, + ) + + return new_linear_extrapolation_plot(*args, **kwargs) def set_log_level(level, name="cleverhans"): - """ - Sets the threshold for the cleverhans logger to level - :param level: the logger threshold. You can find values here: - https://docs.python.org/2/library/logging.html#levels - :param name: the name used for the cleverhans logger - """ - logging.getLogger(name).setLevel(level) + """ + Sets the threshold for the cleverhans logger to level + :param level: the logger threshold. You can find values here: + https://docs.python.org/2/library/logging.html#levels + :param name: the name used for the cleverhans logger + """ + logging.getLogger(name).setLevel(level) def get_log_level(name="cleverhans"): - """ - Gets the current threshold for the cleverhans logger - :param name: the name used for the cleverhans logger - """ - return logging.getLogger(name).getEffectiveLevel() + """ + Gets the current threshold for the cleverhans logger + :param name: the name used for the cleverhans logger + """ + return logging.getLogger(name).getEffectiveLevel() class TemporaryLogLevel(object): - """ - A ContextManager that changes a log level temporarily. + """ + A ContextManager that changes a log level temporarily. - Note that the log level will be set back to its original value when - the context manager exits, even if the log level has been changed - again in the meantime. - """ + Note that the log level will be set back to its original value when + the context manager exits, even if the log level has been changed + again in the meantime. + """ - def __init__(self, level, name): - self.name = name - self.level = level + def __init__(self, level, name): + self.name = name + self.level = level - def __enter__(self): - self.old_level = get_log_level(self.name) - set_log_level(self.level, self.name) + def __enter__(self): + self.old_level = get_log_level(self.name) + set_log_level(self.level, self.name) - def __exit__(self, type, value, traceback): - set_log_level(self.old_level, self.name) - return True + def __exit__(self, type, value, traceback): + set_log_level(self.old_level, self.name) + return True def create_logger(name): - """ - Create a logger object with the given name. + """ + Create a logger object with the given name. - If this is the first time that we call this method, then initialize the - formatter. - """ - base = logging.getLogger("cleverhans") - if len(base.handlers) == 0: - ch = logging.StreamHandler() - formatter = logging.Formatter('[%(levelname)s %(asctime)s %(name)s] ' + - '%(message)s') - ch.setFormatter(formatter) - base.addHandler(ch) + If this is the first time that we call this method, then initialize the + formatter. + """ + base = logging.getLogger("cleverhans") + if len(base.handlers) == 0: + ch = logging.StreamHandler() + formatter = logging.Formatter( + "[%(levelname)s %(asctime)s %(name)s] " + "%(message)s" + ) + ch.setFormatter(formatter) + base.addHandler(ch) - return base + return base def deterministic_dict(normal_dict): - """ - Returns a version of `normal_dict` whose iteration order is always the same - """ - out = OrderedDict() - for key in sorted(normal_dict.keys()): - out[key] = normal_dict[key] - return out + """ + Returns a version of `normal_dict` whose iteration order is always the same + """ + out = OrderedDict() + for key in sorted(normal_dict.keys()): + out[key] = normal_dict[key] + return out def ordered_union(l1, l2): - """ - Return the union of l1 and l2, with a deterministic ordering. - (Union of python sets does not necessarily have a consisten iteration - order) - :param l1: list of items - :param l2: list of items - :returns: list containing one copy of each item that is in l1 or in l2 - """ - out = [] - for e in l1 + l2: - if e not in out: - out.append(e) - return out + """ + Return the union of l1 and l2, with a deterministic ordering. + (Union of python sets does not necessarily have a consisten iteration + order) + :param l1: list of items + :param l2: list of items + :returns: list containing one copy of each item that is in l1 or in l2 + """ + out = [] + for e in l1 + l2: + if e not in out: + out.append(e) + return out def safe_zip(*args): - """like zip but with these properties: - - returns a list, rather than an iterator. This is the old Python2 zip behavior. - - a guarantee that all arguments are the same length. - (normal zip silently drops entries to make them the same length) - """ - length = len(args[0]) - if not all(len(arg) == length for arg in args): - raise ValueError("Lengths of arguments do not match: " - + str([len(arg) for arg in args])) - return list(zip(*args)) + """like zip but with these properties: + - returns a list, rather than an iterator. This is the old Python2 zip behavior. + - a guarantee that all arguments are the same length. + (normal zip silently drops entries to make them the same length) + """ + length = len(args[0]) + if not all(len(arg) == length for arg in args): + raise ValueError( + "Lengths of arguments do not match: " + str([len(arg) for arg in args]) + ) + return list(zip(*args)) def shell_call(command, **kwargs): - """Calls shell command with argument substitution. - - Args: - command: command represented as a list. Each element of the list is one - token of the command. For example "cp a b" becomes ['cp', 'a', 'b'] - If any element of the list looks like '${NAME}' then it will be replaced - by value from **kwargs with key 'NAME'. - **kwargs: dictionary with argument substitution - - Returns: - output of the command - - Raises: - subprocess.CalledProcessError if command return value is not zero - - This function is useful when you need to do variable substitution prior - running the command. Below are few examples of how it works: - - shell_call(['cp', 'a', 'b'], a='asd') calls command 'cp a b' - - shell_call(['cp', '${a}', 'b'], a='asd') calls command 'cp asd b', - '${a}; was replaced with 'asd' before calling the command - """ - # Regular expression to find instances of '${NAME}' in a string - CMD_VARIABLE_RE = re.compile('^\\$\\{(\\w+)\\}$') - command = list(command) - for i in range(len(command)): - m = CMD_VARIABLE_RE.match(command[i]) - if m: - var_id = m.group(1) - if var_id in kwargs: - command[i] = kwargs[var_id] - str_command = ' '.join(command) - logging.debug('Executing shell command: %s' % str_command) - return subprocess.check_output(command) + """Calls shell command with argument substitution. + + Args: + command: command represented as a list. Each element of the list is one + token of the command. For example "cp a b" becomes ['cp', 'a', 'b'] + If any element of the list looks like '${NAME}' then it will be replaced + by value from **kwargs with key 'NAME'. + **kwargs: dictionary with argument substitution + + Returns: + output of the command + + Raises: + subprocess.CalledProcessError if command return value is not zero + + This function is useful when you need to do variable substitution prior + running the command. Below are few examples of how it works: + + shell_call(['cp', 'a', 'b'], a='asd') calls command 'cp a b' + + shell_call(['cp', '${a}', 'b'], a='asd') calls command 'cp asd b', + '${a}; was replaced with 'asd' before calling the command + """ + # Regular expression to find instances of '${NAME}' in a string + CMD_VARIABLE_RE = re.compile("^\\$\\{(\\w+)\\}$") + command = list(command) + for i in range(len(command)): + m = CMD_VARIABLE_RE.match(command[i]) + if m: + var_id = m.group(1) + if var_id in kwargs: + command[i] = kwargs[var_id] + str_command = " ".join(command) + logging.debug("Executing shell command: %s" % str_command) + return subprocess.check_output(command) + def deep_copy(numpy_dict): - """ - Returns a copy of a dictionary whose values are numpy arrays. - Copies their values rather than copying references to them. - """ - out = {} - for key in numpy_dict: - out[key] = numpy_dict[key].copy() - return out + """ + Returns a copy of a dictionary whose values are numpy arrays. + Copies their values rather than copying references to them. + """ + out = {} + for key in numpy_dict: + out[key] = numpy_dict[key].copy() + return out diff --git a/cleverhans/utils_keras.py b/cleverhans/utils_keras.py deleted file mode 100644 index 5d898c456..000000000 --- a/cleverhans/utils_keras.py +++ /dev/null @@ -1,258 +0,0 @@ -""" -Model construction utilities based on keras -""" -from distutils.version import LooseVersion -import warnings -import tensorflow as tf - -from .model import Model, NoSuchLayerError - -# Assignment rather than import because direct import from within Keras -# doesn't work in tf 1.8 -Sequential = tf.keras.models.Sequential -Conv2D = tf.keras.layers.Conv2D -Dense = tf.keras.layers.Dense -Activation = tf.keras.layers.Activation -Flatten = tf.keras.layers.Flatten -KerasModel = tf.keras.models.Model - - -def conv_2d(filters, kernel_shape, strides, padding, input_shape=None): - """ - Defines the right convolutional layer according to the - version of Keras that is installed. - :param filters: (required integer) the dimensionality of the output - space (i.e. the number output of filters in the - convolution) - :param kernel_shape: (required tuple or list of 2 integers) specifies - the kernel shape of the convolution - :param strides: (required tuple or list of 2 integers) specifies - the strides of the convolution along the width and - height. - :param padding: (required string) can be either 'valid' (no padding around - input or feature map) or 'same' (pad to ensure that the - output feature map size is identical to the layer input) - :param input_shape: (optional) give input shape if this is the first - layer of the model - :return: the Keras layer - """ - if input_shape is not None: - return Conv2D(filters=filters, kernel_size=kernel_shape, - strides=strides, padding=padding, - input_shape=input_shape) - else: - return Conv2D(filters=filters, kernel_size=kernel_shape, - strides=strides, padding=padding) - - -def cnn_model(logits=False, input_ph=None, img_rows=28, img_cols=28, - channels=1, nb_filters=64, nb_classes=10): - """ - Defines a CNN model using Keras sequential model - :param logits: If set to False, returns a Keras model, otherwise will also - return logits tensor - :param input_ph: The TensorFlow tensor for the input - (needed if returning logits) - ("ph" stands for placeholder but it need not actually be a - placeholder) - :param img_rows: number of row in the image - :param img_cols: number of columns in the image - :param channels: number of color channels (e.g., 1 for MNIST) - :param nb_filters: number of convolutional filters per layer - :param nb_classes: the number of output classes - :return: - """ - model = Sequential() - - # Define the layers successively (convolution layers are version dependent) - if tf.keras.backend.image_data_format() == 'channels_first': - input_shape = (channels, img_rows, img_cols) - else: - assert tf.keras.backend.image_data_format() == 'channels_last' - input_shape = (img_rows, img_cols, channels) - - layers = [conv_2d(nb_filters, (8, 8), (2, 2), "same", - input_shape=input_shape), - Activation('relu'), - conv_2d((nb_filters * 2), (6, 6), (2, 2), "valid"), - Activation('relu'), - conv_2d((nb_filters * 2), (5, 5), (1, 1), "valid"), - Activation('relu'), - Flatten(), - Dense(nb_classes)] - - for layer in layers: - model.add(layer) - - if logits: - logits_tensor = model(input_ph) - model.add(Activation('softmax')) - - if logits: - return model, logits_tensor - else: - return model - - -class KerasModelWrapper(Model): - """ - An implementation of `Model` that wraps a Keras model. It - specifically exposes the hidden features of a model by creating new models. - The symbolic graph is reused and so there is little overhead. Splitting - in-place operations can incur an overhead. - """ - - def __init__(self, model): - """ - Create a wrapper for a Keras model - :param model: A Keras model - """ - super(KerasModelWrapper, self).__init__(None, None, {}) - - if model is None: - raise ValueError('model argument must be supplied.') - - self.model = model - self.keras_model = None - - def _get_softmax_name(self): - """ - Looks for the name of the softmax layer. - :return: Softmax layer name - """ - for layer in self.model.layers: - cfg = layer.get_config() - if 'activation' in cfg and cfg['activation'] == 'softmax': - return layer.name - - raise Exception("No softmax layers found") - - def _get_abstract_layer_name(self): - """ - Looks for the name of abstracted layer. - Usually these layers appears when model is stacked. - :return: List of abstracted layers - """ - abstract_layers = [] - for layer in self.model.layers: - if 'layers' in layer.get_config(): - abstract_layers.append(layer.name) - - return abstract_layers - - def _get_logits_name(self): - """ - Looks for the name of the layer producing the logits. - :return: name of layer producing the logits - """ - softmax_name = self._get_softmax_name() - softmax_layer = self.model.get_layer(softmax_name) - - if not isinstance(softmax_layer, Activation): - # In this case, the activation is part of another layer - return softmax_name - - if not hasattr(softmax_layer, '_inbound_nodes'): - raise RuntimeError("Please update keras to version >= 2.1.3") - - node = softmax_layer._inbound_nodes[0] - - if LooseVersion(tf.__version__) < LooseVersion('1.14.0'): - logits_name = node.inbound_layers[0].name - else: - logits_name = node.inbound_layers.name - - return logits_name - - def get_logits(self, x): - """ - :param x: A symbolic representation of the network input. - :return: A symbolic representation of the logits - """ - logits_name = self._get_logits_name() - logits_layer = self.get_layer(x, logits_name) - - # Need to deal with the case where softmax is part of the - # logits layer - if logits_name == self._get_softmax_name(): - softmax_logit_layer = self.get_layer(x, logits_name) - - # The final op is the softmax. Return its input - logits_layer = softmax_logit_layer._op.inputs[0] - - return logits_layer - - def get_probs(self, x): - """ - :param x: A symbolic representation of the network input. - :return: A symbolic representation of the probs - """ - name = self._get_softmax_name() - - return self.get_layer(x, name) - - def get_layer_names(self): - """ - :return: Names of all the layers kept by Keras - """ - layer_names = [x.name for x in self.model.layers] - return layer_names - - def fprop(self, x): - """ - Exposes all the layers of the model returned by get_layer_names. - :param x: A symbolic representation of the network input - :return: A dictionary mapping layer names to the symbolic - representation of their output. - """ - - if self.keras_model is None: - # Get the input layer - new_input = self.model.get_input_at(0) - - # Make a new model that returns each of the layers as output - abstract_layers = self._get_abstract_layer_name() - if abstract_layers: - warnings.warn( - "Abstract layer detected, picking last ouput node as default." - "This could happen due to using of stacked model.") - - layer_outputs = [] - # For those abstract model layers, return their last output node as - # default. - for x_layer in self.model.layers: - if x_layer.name not in abstract_layers: - layer_outputs.append(x_layer.output) - else: - layer_outputs.append(x_layer.get_output_at(-1)) - - self.keras_model = KerasModel(new_input, layer_outputs) - - # and get the outputs for that model on the input x - outputs = self.keras_model(x) - - # Keras only returns a list for outputs of length >= 1, if the model - # is only one layer, wrap a list - if len(self.model.layers) == 1: - outputs = [outputs] - - # compute the dict to return - fprop_dict = dict(zip(self.get_layer_names(), outputs)) - - return fprop_dict - - def get_layer(self, x, layer): - """ - Expose the hidden features of a model given a layer name. - :param x: A symbolic representation of the network input - :param layer: The name of the hidden layer to return features at. - :return: A symbolic representation of the hidden features - :raise: NoSuchLayerError if `layer` is not in the model. - """ - # Return the symbolic representation for this layer. - output = self.fprop(x) - try: - requested = output[layer] - except KeyError: - raise NoSuchLayerError() - return requested diff --git a/cleverhans/utils_mnist.py b/cleverhans/utils_mnist.py deleted file mode 100644 index c8b1764eb..000000000 --- a/cleverhans/utils_mnist.py +++ /dev/null @@ -1,38 +0,0 @@ -# pylint: disable=missing-docstring -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - -import os -import tempfile -import warnings - -from cleverhans import dataset - -utils_mnist_warning = "cleverhans.utils_mnist is deprecrated and will be " \ - "removed on or after 2019-03-26. Switch to " \ - "cleverhans.dataset instead." - - -def maybe_download_mnist_file(file_name, datadir=None, force=False): - warnings.warn(utils_mnist_warning) - url = os.path.join('http://yann.lecun.com/exdb/mnist/', file_name) - return dataset.maybe_download_file(url, datadir=None, force=False) - - -def download_and_parse_mnist_file(file_name, datadir=None, force=False): - warnings.warn(utils_mnist_warning) - return dataset.download_and_parse_mnist_file(file_name, datadir=None, - force=False) - - -def data_mnist(datadir=tempfile.gettempdir(), train_start=0, - train_end=60000, test_start=0, test_end=10000): - warnings.warn(utils_mnist_warning) - mnist = dataset.MNIST(train_start=train_start, - train_end=train_end, - test_start=test_start, - test_end=test_end, - center=False) - return mnist.get_set('train') + mnist.get_set('test') diff --git a/cleverhans/utils_pytorch.py b/cleverhans/utils_pytorch.py deleted file mode 100644 index 51f648ed9..000000000 --- a/cleverhans/utils_pytorch.py +++ /dev/null @@ -1,94 +0,0 @@ -"""Basic utilities for pytorch code""" - -import warnings -from random import getrandbits - -import numpy as np -import tensorflow as tf -import torch -from torch.autograd import Variable - - -# https://gist.github.com/kingspp/3ec7d9958c13b94310c1a365759aa3f4 -# Pyfunc Gradient Function -def _py_func_with_gradient(func, inp, Tout, stateful=True, name=None, - grad_func=None): - """ - PyFunc defined as given by Tensorflow - :param func: Custom Function - :param inp: Function Inputs - :param Tout: Ouput Type of out Custom Function - :param stateful: Calculate Gradients when stateful is True - :param name: Name of the PyFunction - :param grad: Custom Gradient Function - :return: - """ - # Generate random name in order to avoid conflicts with inbuilt names - rnd_name = 'PyFuncGrad-' + '%0x' % getrandbits(30 * 4) - - # Register Tensorflow Gradient - tf.RegisterGradient(rnd_name)(grad_func) - - # Get current graph - g = tf.get_default_graph() - - # Add gradient override map - with g.gradient_override_map({"PyFunc": rnd_name, - "PyFuncStateless": rnd_name}): - return tf.py_func(func, inp, Tout, stateful=stateful, name=name) - - -def convert_pytorch_model_to_tf(model, out_dims=None): - """ - Convert a pytorch model into a tensorflow op that allows backprop - :param model: A pytorch nn.Module object - :param out_dims: The number of output dimensions (classes) for the model - :return: A model function that maps an input (tf.Tensor) to the - output of the model (tf.Tensor) - """ - warnings.warn("convert_pytorch_model_to_tf is deprecated, switch to" - + " dedicated PyTorch support provided by CleverHans v4.") - - torch_state = { - 'logits': None, - 'x': None, - } - if not out_dims: - out_dims = list(model.modules())[-1].out_features - - def _fprop_fn(x_np): - """TODO: write this""" - x_tensor = torch.Tensor(x_np) - if torch.cuda.is_available(): - x_tensor = x_tensor.cuda() - torch_state['x'] = Variable(x_tensor, requires_grad=True) - torch_state['logits'] = model(torch_state['x']) - return torch_state['logits'].data.cpu().numpy() - - def _bprop_fn(x_np, grads_in_np): - """TODO: write this""" - _fprop_fn(x_np) - - grads_in_tensor = torch.Tensor(grads_in_np) - if torch.cuda.is_available(): - grads_in_tensor = grads_in_tensor.cuda() - - # Run our backprop through our logits to our xs - loss = torch.sum(torch_state['logits'] * grads_in_tensor) - loss.backward() - return torch_state['x'].grad.cpu().data.numpy() - - def _tf_gradient_fn(op, grads_in): - """TODO: write this""" - return tf.py_func(_bprop_fn, [op.inputs[0], grads_in], - Tout=[tf.float32]) - - def tf_model_fn(x_op): - """TODO: write this""" - out = _py_func_with_gradient(_fprop_fn, [x_op], Tout=[tf.float32], - stateful=True, - grad_func=_tf_gradient_fn)[0] - out.set_shape([None, out_dims]) - return out - - return tf_model_fn diff --git a/cleverhans/utils_tf.py b/cleverhans/utils_tf.py deleted file mode 100644 index 0e1ef4faf..000000000 --- a/cleverhans/utils_tf.py +++ /dev/null @@ -1,846 +0,0 @@ -"""Utility functions for writing TensorFlow code""" -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - -import logging -import math -import os -import time -import warnings - -import numpy as np -import six -from six.moves import xrange -import tensorflow as tf - -from cleverhans.compat import device_lib -from cleverhans.compat import reduce_sum, reduce_mean -from cleverhans.compat import reduce_max -from cleverhans.compat import softmax_cross_entropy_with_logits -from cleverhans.utils import batch_indices, _ArgsWrapper, create_logger - -_logger = create_logger("cleverhans.utils.tf") -_logger.setLevel(logging.INFO) - - -def model_loss(y, model, mean=True): - """ - Define loss of TF graph - :param y: correct labels - :param model: output of the model - :param mean: boolean indicating whether should return mean of loss - or vector of losses for each input of the batch - :return: return mean of loss if True, otherwise return vector with per - sample loss - """ - warnings.warn("This function is deprecated and will be removed on or after" - " 2019-04-05. Switch to cleverhans.train.train.") - op = model.op - if op.type == "Softmax": - logits, = op.inputs - else: - logits = model - - out = softmax_cross_entropy_with_logits(logits=logits, labels=y) - - if mean: - out = reduce_mean(out) - return out - - -def initialize_uninitialized_global_variables(sess): - """ - Only initializes the variables of a TensorFlow session that were not - already initialized. - :param sess: the TensorFlow session - :return: - """ - # List all global variables - global_vars = tf.global_variables() - - # Find initialized status for all variables - is_var_init = [tf.is_variable_initialized(var) for var in global_vars] - is_initialized = sess.run(is_var_init) - - # List all variables that were not initialized previously - not_initialized_vars = [var for (var, init) in - zip(global_vars, is_initialized) if not init] - - # Initialize all uninitialized variables found, if any - if len(not_initialized_vars): - sess.run(tf.variables_initializer(not_initialized_vars)) - - -def train(sess, loss, x, y, X_train, Y_train, save=False, - init_all=False, evaluate=None, feed=None, args=None, - rng=None, var_list=None, fprop_args=None, optimizer=None): - """ - Train a TF graph. - This function is deprecated. Prefer cleverhans.train.train when possible. - cleverhans.train.train supports multiple GPUs but this function is still - needed to support legacy models that do not support calling fprop more - than once. - - :param sess: TF session to use when training the graph - :param loss: tensor, the model training loss. - :param x: input placeholder - :param y: output placeholder (for labels) - :param X_train: numpy array with training inputs - :param Y_train: numpy array with training outputs - :param save: boolean controlling the save operation - :param init_all: (boolean) If set to true, all TF variables in the session - are (re)initialized, otherwise only previously - uninitialized variables are initialized before training. - :param evaluate: function that is run after each training iteration - (typically to display the test/validation accuracy). - :param feed: An optional dictionary that is appended to the feeding - dictionary before the session runs. Can be used to feed - the learning phase of a Keras model for instance. - :param args: dict or argparse `Namespace` object. - Should contain `nb_epochs`, `learning_rate`, - `batch_size` - If save is True, should also contain 'train_dir' - and 'filename' - :param rng: Instance of numpy.random.RandomState - :param var_list: Optional list of parameters to train. - :param fprop_args: dict, extra arguments to pass to fprop (loss and model). - :param optimizer: Optimizer to be used for training - :return: True if model trained - """ - warnings.warn("This function is deprecated and will be removed on or after" - " 2019-04-05. Switch to cleverhans.train.train.") - - args = _ArgsWrapper(args or {}) - fprop_args = fprop_args or {} - - # Check that necessary arguments were given (see doc above) - assert args.nb_epochs, "Number of epochs was not given in args dict" - if optimizer is None: - assert args.learning_rate is not None, ("Learning rate was not given " - "in args dict") - assert args.batch_size, "Batch size was not given in args dict" - - if save: - assert args.train_dir, "Directory for save was not given in args dict" - assert args.filename, "Filename for save was not given in args dict" - - if rng is None: - rng = np.random.RandomState() - - # Define optimizer - loss_value = loss.fprop(x, y, **fprop_args) - if optimizer is None: - optimizer = tf.train.AdamOptimizer(learning_rate=args.learning_rate) - else: - if not isinstance(optimizer, tf.train.Optimizer): - raise ValueError("optimizer object must be from a child class of " - "tf.train.Optimizer") - # Trigger update operations within the default graph (such as batch_norm). - with tf.control_dependencies(tf.get_collection(tf.GraphKeys.UPDATE_OPS)): - train_step = optimizer.minimize(loss_value, var_list=var_list) - - with sess.as_default(): - if hasattr(tf, "global_variables_initializer"): - if init_all: - tf.global_variables_initializer().run() - else: - initialize_uninitialized_global_variables(sess) - else: - warnings.warn("Update your copy of tensorflow; future versions of " - "CleverHans may drop support for this version.") - sess.run(tf.initialize_all_variables()) - - for epoch in xrange(args.nb_epochs): - # Compute number of batches - nb_batches = int(math.ceil(float(len(X_train)) / args.batch_size)) - assert nb_batches * args.batch_size >= len(X_train) - - # Indices to shuffle training set - index_shuf = list(range(len(X_train))) - rng.shuffle(index_shuf) - - prev = time.time() - for batch in range(nb_batches): - - # Compute batch start and end indices - start, end = batch_indices( - batch, len(X_train), args.batch_size) - - # Perform one training step - feed_dict = {x: X_train[index_shuf[start:end]], - y: Y_train[index_shuf[start:end]]} - if feed is not None: - feed_dict.update(feed) - train_step.run(feed_dict=feed_dict) - assert end >= len(X_train) # Check that all examples were used - cur = time.time() - _logger.info("Epoch " + str(epoch) + " took " + - str(cur - prev) + " seconds") - if evaluate is not None: - evaluate() - - if save: - save_path = os.path.join(args.train_dir, args.filename) - saver = tf.train.Saver() - saver.save(sess, save_path) - _logger.info("Completed model training and saved at: " + - str(save_path)) - else: - _logger.info("Completed model training.") - - return True - - -def model_eval(sess, x, y, predictions, X_test=None, Y_test=None, - feed=None, args=None): - """ - Compute the accuracy of a TF model on some data - :param sess: TF session to use - :param x: input placeholder - :param y: output placeholder (for labels) - :param predictions: model output predictions - :param X_test: numpy array with training inputs - :param Y_test: numpy array with training outputs - :param feed: An optional dictionary that is appended to the feeding - dictionary before the session runs. Can be used to feed - the learning phase of a Keras model for instance. - :param args: dict or argparse `Namespace` object. - Should contain `batch_size` - :return: a float with the accuracy value - """ - global _model_eval_cache - args = _ArgsWrapper(args or {}) - - assert args.batch_size, "Batch size was not given in args dict" - if X_test is None or Y_test is None: - raise ValueError("X_test argument and Y_test argument " - "must be supplied.") - - # Define accuracy symbolically - key = (y, predictions) - if key in _model_eval_cache: - correct_preds = _model_eval_cache[key] - else: - correct_preds = tf.equal(tf.argmax(y, axis=-1), - tf.argmax(predictions, axis=-1)) - _model_eval_cache[key] = correct_preds - - # Init result var - accuracy = 0.0 - - with sess.as_default(): - # Compute number of batches - nb_batches = int(math.ceil(float(len(X_test)) / args.batch_size)) - assert nb_batches * args.batch_size >= len(X_test) - - X_cur = np.zeros((args.batch_size,) + X_test.shape[1:], - dtype=X_test.dtype) - Y_cur = np.zeros((args.batch_size,) + Y_test.shape[1:], - dtype=Y_test.dtype) - for batch in range(nb_batches): - if batch % 100 == 0 and batch > 0: - _logger.debug("Batch " + str(batch)) - - # Must not use the `batch_indices` function here, because it - # repeats some examples. - # It's acceptable to repeat during training, but not eval. - start = batch * args.batch_size - end = min(len(X_test), start + args.batch_size) - - # The last batch may be smaller than all others. This should not - # affect the accuarcy disproportionately. - cur_batch_size = end - start - X_cur[:cur_batch_size] = X_test[start:end] - Y_cur[:cur_batch_size] = Y_test[start:end] - feed_dict = {x: X_cur, y: Y_cur} - if feed is not None: - feed_dict.update(feed) - cur_corr_preds = correct_preds.eval(feed_dict=feed_dict) - - accuracy += cur_corr_preds[:cur_batch_size].sum() - - assert end >= len(X_test) - - # Divide by number of examples to get final value - accuracy /= len(X_test) - - return accuracy - -_model_eval_cache = {} - - -def tf_model_load(sess, file_path=None): - """ - - :param sess: the session object to restore - :param file_path: path to the restored session, if None is - taken from FLAGS.train_dir and FLAGS.filename - :return: - """ - with sess.as_default(): - saver = tf.train.Saver() - if file_path is None: - error = 'file_path argument is missing.' - raise ValueError(error) - saver.restore(sess, file_path) - - return True - - -def batch_eval(*args, **kwargs): - """ - Wrapper around deprecated function. - """ - # Inside function to avoid circular import - from cleverhans.evaluation import batch_eval as new_batch_eval - warnings.warn("batch_eval has moved to cleverhans.evaluation. " - "batch_eval will be removed from utils_tf on or after " - "2019-03-09.") - return new_batch_eval(*args, **kwargs) - - -def model_argmax(sess, x, predictions, samples, feed=None): - """ - Helper function that computes the current class prediction - :param sess: TF session - :param x: the input placeholder - :param predictions: the model's symbolic output - :param samples: numpy array with input samples (dims must match x) - :param feed: An optional dictionary that is appended to the feeding - dictionary before the session runs. Can be used to feed - the learning phase of a Keras model for instance. - :return: the argmax output of predictions, i.e. the current predicted class - """ - feed_dict = {x: samples} - if feed is not None: - feed_dict.update(feed) - probabilities = sess.run(predictions, feed_dict) - - if samples.shape[0] == 1: - return np.argmax(probabilities) - else: - return np.argmax(probabilities, axis=1) - - -def l2_batch_normalize(x, epsilon=1e-12, scope=None): - """ - Helper function to normalize a batch of vectors. - :param x: the input placeholder - :param epsilon: stabilizes division - :return: the batch of l2 normalized vector - """ - with tf.name_scope(scope, "l2_batch_normalize") as name_scope: - x_shape = tf.shape(x) - x = tf.contrib.layers.flatten(x) - x /= (epsilon + reduce_max(tf.abs(x), 1, keepdims=True)) - square_sum = reduce_sum(tf.square(x), 1, keepdims=True) - x_inv_norm = tf.rsqrt(np.sqrt(epsilon) + square_sum) - x_norm = tf.multiply(x, x_inv_norm) - return tf.reshape(x_norm, x_shape, name_scope) - - -def kl_with_logits(p_logits, q_logits, scope=None, - loss_collection=tf.GraphKeys.REGULARIZATION_LOSSES): - """Helper function to compute kl-divergence KL(p || q) - """ - with tf.name_scope(scope, "kl_divergence") as name: - p = tf.nn.softmax(p_logits) - p_log = tf.nn.log_softmax(p_logits) - q_log = tf.nn.log_softmax(q_logits) - loss = reduce_mean(reduce_sum(p * (p_log - q_log), axis=1), - name=name) - tf.losses.add_loss(loss, loss_collection) - return loss - - -def clip_eta(eta, ord, eps): - """ - Helper function to clip the perturbation to epsilon norm ball. - :param eta: A tensor with the current perturbation. - :param ord: Order of the norm (mimics Numpy). - Possible values: np.inf, 1 or 2. - :param eps: Epsilon, bound of the perturbation. - """ - - # Clipping perturbation eta to self.ord norm ball - if ord not in [np.inf, 1, 2]: - raise ValueError('ord must be np.inf, 1, or 2.') - reduc_ind = list(xrange(1, len(eta.get_shape()))) - avoid_zero_div = 1e-12 - if ord == np.inf: - eta = clip_by_value(eta, -eps, eps) - elif ord == 1: - # Implements a projection algorithm onto the l1-ball from - # (Duchi et al. 2008) that runs in time O(d*log(d)) where d is the - # input dimension. - # Paper link (Duchi et al. 2008): https://dl.acm.org/citation.cfm?id=1390191 - - eps = tf.cast(eps, eta.dtype) - - dim = tf.reduce_prod(tf.shape(eta)[1:]) - eta_flat = tf.reshape(eta, (-1, dim)) - abs_eta = tf.abs(eta_flat) - - if 'sort' in dir(tf): - mu = -tf.sort(-abs_eta, axis=-1) - else: - # `tf.sort` is only available in TF 1.13 onwards - mu = tf.nn.top_k(abs_eta, k=dim, sorted=True)[0] - cumsums = tf.cumsum(mu, axis=-1) - js = tf.cast(tf.divide(1, tf.range(1, dim + 1)), eta.dtype) - t = tf.cast(tf.greater(mu - js * (cumsums - eps), 0), eta.dtype) - - rho = tf.argmax(t * cumsums, axis=-1) - rho_val = tf.reduce_max(t * cumsums, axis=-1) - theta = tf.divide(rho_val - eps, tf.cast(1 + rho, eta.dtype)) - - eta_sgn = tf.sign(eta_flat) - eta_proj = eta_sgn * tf.maximum(abs_eta - theta[:, tf.newaxis], 0) - eta_proj = tf.reshape(eta_proj, tf.shape(eta)) - - norm = tf.reduce_sum(tf.abs(eta), reduc_ind) - eta = tf.where(tf.greater(norm, eps), eta_proj, eta) - - elif ord == 2: - # avoid_zero_div must go inside sqrt to avoid a divide by zero - # in the gradient through this operation - norm = tf.sqrt(tf.maximum(avoid_zero_div, - reduce_sum(tf.square(eta), - reduc_ind, - keepdims=True))) - # We must *clip* to within the norm ball, not *normalize* onto the - # surface of the ball - factor = tf.minimum(1., div(eps, norm)) - eta = eta * factor - return eta - - -def zero_out_clipped_grads(grad, x, clip_min, clip_max): - """ - Helper function to erase entries in the gradient where the update would be - clipped. - :param grad: The gradient - :param x: The current input - :param clip_min: Minimum input component value - :param clip_max: Maximum input component value - """ - signed_grad = tf.sign(grad) - - # Find input components that lie at the boundary of the input range, and - # where the gradient points in the wrong direction. - clip_low = tf.logical_and(tf.less_equal(x, tf.cast(clip_min, x.dtype)), - tf.less(signed_grad, 0)) - clip_high = tf.logical_and(tf.greater_equal(x, tf.cast(clip_max, x.dtype)), - tf.greater(signed_grad, 0)) - clip = tf.logical_or(clip_low, clip_high) - grad = tf.where(clip, mul(grad, 0), grad) - - return grad - - -def random_exponential(shape, rate=1.0, dtype=tf.float32, seed=None): - """ - Helper function to sample from the exponential distribution, which is not - included in core TensorFlow. - - :shape: shape of the sampled tensor. - :rate: (optional) rate parameter of the exponential distribution, defaults to 1.0. - :dtype: (optional) data type of the sempled tensor, defaults to tf.float32. - :seed: (optional) custom seed to be used for sampling. - """ - return tf.random_gamma(shape, alpha=1, beta=1. / rate, dtype=dtype, seed=seed) - - -def random_laplace(shape, loc=0.0, scale=1.0, dtype=tf.float32, seed=None): - """ - Helper function to sample from the Laplace distribution, which is not - included in core TensorFlow. - - :shape: shape of the sampled tensor. - :loc: (optional) mean of the laplace distribution, defaults to 0.0. - :scale: (optional) scale parameter of the laplace diustribution, defaults to 1.0. - :dtype: (optional) data type of the sempled tensor, defaults to tf.float32. - :seed: (optional) custom seed to be used for sampling. - """ - z1 = random_exponential(shape, 1. / scale, dtype=dtype, seed=seed) - z2 = random_exponential(shape, 1. / scale, dtype=dtype, seed=seed) - return z1 - z2 + loc - - -def random_lp_vector(shape, ord, eps, dtype=tf.float32, seed=None): - """ - Helper function to generate uniformly random vectors from a norm ball of - radius epsilon. - :param shape: Output shape of the random sample. The shape is expected to be - of the form `(n, d1, d2, ..., dn)` where `n` is the number of - i.i.d. samples that will be drawn from a norm ball of dimension - `d1*d1*...*dn`. - :param ord: Order of the norm (mimics Numpy). - Possible values: np.inf, 1 or 2. - :param eps: Epsilon, radius of the norm ball. - """ - if ord not in [np.inf, 1, 2]: - raise ValueError('ord must be np.inf, 1, or 2.') - - if ord == np.inf: - r = tf.random_uniform(shape, -eps, eps, dtype=dtype, seed=seed) - else: - - # For ord=1 and ord=2, we use the generic technique from - # (Calafiore et al. 1998) to sample uniformly from a norm ball. - # Paper link (Calafiore et al. 1998): - # https://ieeexplore.ieee.org/stamp/stamp.jsp?arnumber=758215&tag=1 - # We first sample from the surface of the norm ball, and then scale by - # a factor `w^(1/d)` where `w~U[0,1]` is a standard uniform random variable - # and `d` is the dimension of the ball. In high dimensions, this is roughly - # equivalent to sampling from the surface of the ball. - - dim = tf.reduce_prod(shape[1:]) - - if ord == 1: - x = random_laplace((shape[0], dim), loc=1.0, scale=1.0, dtype=dtype, - seed=seed) - norm = tf.reduce_sum(tf.abs(x), axis=-1, keepdims=True) - elif ord == 2: - x = tf.random_normal((shape[0], dim), dtype=dtype, seed=seed) - norm = tf.sqrt(tf.reduce_sum(tf.square(x), axis=-1, keepdims=True)) - else: - raise ValueError('ord must be np.inf, 1, or 2.') - - w = tf.pow(tf.random.uniform((shape[0], 1), dtype=dtype, seed=seed), - 1.0 / tf.cast(dim, dtype)) - r = eps * tf.reshape(w * x / norm, shape) - - return r - - -def model_train(sess, x, y, predictions, X_train, Y_train, save=False, - predictions_adv=None, init_all=True, evaluate=None, - feed=None, args=None, rng=None, var_list=None): - """ - Train a TF graph - :param sess: TF session to use when training the graph - :param x: input placeholder - :param y: output placeholder (for labels) - :param predictions: model output predictions - :param X_train: numpy array with training inputs - :param Y_train: numpy array with training outputs - :param save: boolean controlling the save operation - :param predictions_adv: if set with the adversarial example tensor, - will run adversarial training - :param init_all: (boolean) If set to true, all TF variables in the session - are (re)initialized, otherwise only previously - uninitialized variables are initialized before training. - :param evaluate: function that is run after each training iteration - (typically to display the test/validation accuracy). - :param feed: An optional dictionary that is appended to the feeding - dictionary before the session runs. Can be used to feed - the learning phase of a Keras model for instance. - :param args: dict or argparse `Namespace` object. - Should contain `nb_epochs`, `learning_rate`, - `batch_size` - If save is True, should also contain 'train_dir' - and 'filename' - :param rng: Instance of numpy.random.RandomState - :param var_list: Optional list of parameters to train. - :return: True if model trained - """ - warnings.warn("This function is deprecated and will be removed on or after" - " 2019-04-05. Switch to cleverhans.train.train.") - args = _ArgsWrapper(args or {}) - - # Check that necessary arguments were given (see doc above) - assert args.nb_epochs, "Number of epochs was not given in args dict" - assert args.learning_rate, "Learning rate was not given in args dict" - assert args.batch_size, "Batch size was not given in args dict" - - if save: - assert args.train_dir, "Directory for save was not given in args dict" - assert args.filename, "Filename for save was not given in args dict" - - if rng is None: - rng = np.random.RandomState() - - # Define loss - loss = model_loss(y, predictions) - if predictions_adv is not None: - loss = (loss + model_loss(y, predictions_adv)) / 2 - - train_step = tf.train.AdamOptimizer(learning_rate=args.learning_rate) - train_step = train_step.minimize(loss, var_list=var_list) - - with sess.as_default(): - if hasattr(tf, "global_variables_initializer"): - if init_all: - tf.global_variables_initializer().run() - else: - initialize_uninitialized_global_variables(sess) - else: - warnings.warn("Update your copy of tensorflow; future versions of " - "CleverHans may drop support for this version.") - sess.run(tf.initialize_all_variables()) - - for epoch in xrange(args.nb_epochs): - # Compute number of batches - nb_batches = int(math.ceil(float(len(X_train)) / args.batch_size)) - assert nb_batches * args.batch_size >= len(X_train) - - # Indices to shuffle training set - index_shuf = list(range(len(X_train))) - rng.shuffle(index_shuf) - - prev = time.time() - for batch in range(nb_batches): - - # Compute batch start and end indices - start, end = batch_indices( - batch, len(X_train), args.batch_size) - - # Perform one training step - feed_dict = {x: X_train[index_shuf[start:end]], - y: Y_train[index_shuf[start:end]]} - if feed is not None: - feed_dict.update(feed) - train_step.run(feed_dict=feed_dict) - assert end >= len(X_train) # Check that all examples were used - cur = time.time() - _logger.info("Epoch " + str(epoch) + " took " + - str(cur - prev) + " seconds") - if evaluate is not None: - evaluate() - - if save: - save_path = os.path.join(args.train_dir, args.filename) - saver = tf.train.Saver() - saver.save(sess, save_path) - _logger.info("Completed model training and saved at: " + - str(save_path)) - else: - _logger.info("Completed model training.") - - return True - - -def infer_devices(devices=None): - """ - Returns the list of devices that multi-replica code should use. - :param devices: list of string device names, e.g. ["/GPU:0"] - If the user specifies this, `infer_devices` checks that it is - valid, and then uses this user-specified list. - If the user does not specify this, infer_devices uses: - - All available GPUs, if there are any - - CPU otherwise - """ - if devices is None: - devices = get_available_gpus() - if len(devices) == 0: - warnings.warn("No GPUS, running on CPU") - # Set device to empy string, tf will figure out whether to use - # XLA or not, etc., automatically - devices = [""] - else: - assert len(devices) > 0 - for device in devices: - assert isinstance(device, six.string_types), type(device) - return devices - - -def get_available_gpus(): - """ - Returns a list of string names of all available GPUs - """ - local_device_protos = device_lib.list_local_devices() - return [x.name for x in local_device_protos if x.device_type == 'GPU'] - - -def silence(): - """ - Silences tensorflaw's default printed messages - """ - os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3' - -def clip_by_value(t, clip_value_min, clip_value_max, name=None): - """ - A wrapper for clip_by_value that casts the clipping range if needed. - """ - def cast_clip(clip): - """ - Cast clipping range argument if needed. - """ - if t.dtype in (tf.float32, tf.float64): - if hasattr(clip, 'dtype'): - # Convert to tf dtype in case this is a numpy dtype - clip_dtype = tf.as_dtype(clip.dtype) - if clip_dtype != t.dtype: - return tf.cast(clip, t.dtype) - return clip - - clip_value_min = cast_clip(clip_value_min) - clip_value_max = cast_clip(clip_value_max) - - return tf.clip_by_value(t, clip_value_min, clip_value_max, name) - -def mul(a, b): - """ - A wrapper around tf multiplication that does more automatic casting of - the input. - """ - def multiply(a, b): - """Multiplication""" - return a * b - return op_with_scalar_cast(a, b, multiply) - -def div(a, b): - """ - A wrapper around tf division that does more automatic casting of - the input. - """ - def divide(a, b): - """Division""" - return a / b - return op_with_scalar_cast(a, b, divide) - -def op_with_scalar_cast(a, b, f): - """ - Builds the graph to compute f(a, b). - If only one of the two arguments is a scalar and the operation would - cause a type error without casting, casts the scalar to match the - tensor. - :param a: a tf-compatible array or scalar - :param b: a tf-compatible array or scalar - """ - - try: - return f(a, b) - except (TypeError, ValueError): - pass - - def is_scalar(x): - """Return True if `x` is a scalar""" - if hasattr(x, "get_shape"): - shape = x.get_shape() - return shape.ndims == 0 - if hasattr(x, "ndim"): - return x.ndim == 0 - assert isinstance(x, (int, float)) - return True - - a_scalar = is_scalar(a) - b_scalar = is_scalar(b) - - if a_scalar and b_scalar: - raise TypeError("Trying to apply " + str(f) + " with mixed types") - - if a_scalar and not b_scalar: - a = tf.cast(a, b.dtype) - - if b_scalar and not a_scalar: - b = tf.cast(b, a.dtype) - - return f(a, b) - -def assert_less_equal(*args, **kwargs): - """ - Wrapper for tf.assert_less_equal - Overrides tf.device so that the assert always goes on CPU. - The unwrapped version raises an exception if used with tf.device("/GPU:x"). - """ - with tf.device("/CPU:0"): - return tf.assert_less_equal(*args, **kwargs) - -def assert_greater_equal(*args, **kwargs): - """ - Wrapper for tf.assert_greater_equal. - Overrides tf.device so that the assert always goes on CPU. - The unwrapped version raises an exception if used with tf.device("/GPU:x"). - """ - with tf.device("/CPU:0"): - return tf.assert_greater_equal(*args, **kwargs) - -def assert_equal(*args, **kwargs): - """ - Wrapper for tf.assert_equal. - Overrides tf.device so that the assert always goes on CPU. - The unwrapped version raises an exception if used with tf.device("/GPU:x"). - """ - with tf.device("/CPU:0"): - return tf.assert_equal(*args, **kwargs) - -def jacobian_graph(predictions, x, nb_classes): - """ - Create the Jacobian graph to be ran later in a TF session - :param predictions: the model's symbolic output (linear output, - pre-softmax) - :param x: the input placeholder - :param nb_classes: the number of classes the model has - :return: - """ - - # This function will return a list of TF gradients - list_derivatives = [] - - # Define the TF graph elements to compute our derivatives for each class - for class_ind in xrange(nb_classes): - derivatives, = tf.gradients(predictions[:, class_ind], x) - list_derivatives.append(derivatives) - - return list_derivatives - -def jacobian_augmentation(sess, - x, - X_sub_prev, - Y_sub, - grads, - lmbda, - aug_batch_size=512, - feed=None): - """ - Augment an adversary's substitute training set using the Jacobian - of a substitute model to generate new synthetic inputs. - See https://arxiv.org/abs/1602.02697 for more details. - See cleverhans_tutorials/mnist_blackbox.py for example use case - :param sess: TF session in which the substitute model is defined - :param x: input TF placeholder for the substitute model - :param X_sub_prev: substitute training data available to the adversary - at the previous iteration - :param Y_sub: substitute training labels available to the adversary - at the previous iteration - :param grads: Jacobian symbolic graph for the substitute - (should be generated using utils_tf.jacobian_graph) - :return: augmented substitute data (will need to be labeled by oracle) - """ - assert len(x.get_shape()) == len(np.shape(X_sub_prev)) - assert len(grads) >= np.max(Y_sub) + 1 - assert len(X_sub_prev) == len(Y_sub) - - aug_batch_size = min(aug_batch_size, X_sub_prev.shape[0]) - - # Prepare input_shape (outside loop) for feeding dictionary below - input_shape = list(x.get_shape()) - input_shape[0] = 1 - - # Create new numpy array for adversary training data - # with twice as many components on the first dimension. - X_sub = np.vstack([X_sub_prev, X_sub_prev]) - num_samples = X_sub_prev.shape[0] - - # Creating and processing as batch - for p_idxs in range(0, num_samples, aug_batch_size): - X_batch = X_sub_prev[p_idxs:p_idxs + aug_batch_size, ...] - feed_dict = {x: X_batch} - if feed is not None: - feed_dict.update(feed) - - # Compute sign matrix - grad_val = sess.run([tf.sign(grads)], feed_dict=feed_dict)[0] - - # Create new synthetic point in adversary substitute training set - for (indx, ind) in zip(range(p_idxs, p_idxs + X_batch.shape[0]), - range(X_batch.shape[0])): - X_sub[num_samples + indx] = ( - X_batch[ind] + lmbda * grad_val[Y_sub[indx], ind, ...]) - - # Return augmented training data (needs to be labeled afterwards) - return X_sub diff --git a/cleverhans/utils_tfe.py b/cleverhans/utils_tfe.py deleted file mode 100644 index fc39542cf..000000000 --- a/cleverhans/utils_tfe.py +++ /dev/null @@ -1,218 +0,0 @@ -""" -Utility functions for writing tf eager code -""" -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - -import math -import os -import time - -import numpy as np -import tensorflow as tf -from six.moves import xrange - -from cleverhans.loss import LossCrossEntropy -from cleverhans.model import Model -from .utils import batch_indices, _ArgsWrapper, create_logger - -_logger = create_logger("cleverhans.utils.tfe") - - -def train(model, X_train=None, Y_train=None, save=False, - predictions_adv=None, evaluate=None, - args=None, rng=None, var_list=None, - attack=None, attack_args=None): - """ - Train a TF Eager model - :param model: cleverhans.model.Model - :param X_train: numpy array with training inputs - :param Y_train: numpy array with training outputs - :param save: boolean controlling the save operation - :param predictions_adv: if set with the adversarial example tensor, - will run adversarial training - :param evaluate: function that is run after each training iteration - (typically to display the test/validation accuracy). - :param args: dict or argparse `Namespace` object. - Should contain `nb_epochs`, `learning_rate`, - `batch_size` - If save is True, should also contain 'train_dir' - and 'filename' - :param rng: Instance of numpy.random.RandomState - :param var_list: List of variables to train. - :param attack: Instance of the class cleverhans.attacks.attacks_eager - :param attack_args: Parameters required for the attack. - :return: True if model trained - """ - assert isinstance(model, Model) - args = _ArgsWrapper(args or {}) - if ((attack is None) != (attack_args is None)): - raise ValueError("attack and attack_args must be " - "passed together.") - if X_train is None or Y_train is None: - raise ValueError("X_train argument and Y_train argument " - "must be supplied.") - # Check that necessary arguments were given (see doc above) - assert args.nb_epochs, "Number of epochs was not given in args dict" - assert args.learning_rate, "Learning rate was not given in args dict" - assert args.batch_size, "Batch size was not given in args dict" - - if save: - assert args.train_dir, "Directory for save was not given in args dict" - assert args.filename, "Filename for save was not given in args dict" - - if rng is None: - rng = np.random.RandomState() - - # Optimizer - tfe = tf.contrib.eager - optimizer = tf.train.AdamOptimizer(learning_rate=args.learning_rate) - batch_x = tfe.Variable(X_train[0:args.batch_size], dtype=tf.float32) - batch_y = tfe.Variable(Y_train[0:args.batch_size], dtype=tf.float32) - - # One epoch of training. - for epoch in xrange(args.nb_epochs): - # Compute number of batches - nb_batches = int(math.ceil(float(len(X_train)) / args.batch_size)) - assert nb_batches * args.batch_size >= len(X_train) - - # Indices to shuffle training set - index_shuf = list(range(len(X_train))) - rng.shuffle(index_shuf) - - prev = time.time() - for batch in range(nb_batches): - - # Compute batch start and end indices - start, end = batch_indices( - batch, len(X_train), args.batch_size) - - # Perform one training step - tf.assign(batch_x, X_train[index_shuf[start:end]]) - tf.assign(batch_y, Y_train[index_shuf[start:end]]) - # Compute grads - with tf.GradientTape() as tape: - # Define loss - loss_clean_obj = LossCrossEntropy(model, smoothing=0.) - loss_clean = loss_clean_obj.fprop(x=batch_x, y=batch_y) - loss = loss_clean - # Adversarial training - if attack is not None: - batch_adv_x = attack.generate(batch_x, **attack_args) - loss_adv_obj = LossCrossEntropy(model, smoothing=0.) - loss_adv = loss_adv_obj.fprop(x=batch_adv_x, y=batch_y) - loss = (loss_clean + loss_adv) / 2.0 - # Apply grads - model_variables = model.get_params() - grads = tape.gradient(loss, model_variables) - optimizer.apply_gradients(zip(grads, model_variables)) - - assert end >= len(X_train) # Check that all examples were used - cur = time.time() - _logger.info("Epoch " + str(epoch) + " took " + - str(cur - prev) + " seconds") - if evaluate is not None: - evaluate() - - if save: - save_path = os.path.join(args.train_dir, args.filename) - saver = tf.train.Saver() - saver.save(save_path, model_variables) - _logger.info("Completed model training and saved at: " + - str(save_path)) - else: - _logger.info("Completed model training.") - - return True - - -def model_eval(model, X_test=None, Y_test=None, args=None, - attack=None, attack_args=None): - """ - Compute the accuracy of a TF Eager model on some data - :param model: instance of cleverhans.model.Model_Eager - with pretrained weights for evaluation. - :param X_test: numpy array with training inputs - :param Y_test: numpy array with training outputs - :param args: dict or argparse `Namespace` object. - Should contain `batch_size` - :param attack: instance of the class cleverhans.attacks.attacks_eager - :param attack_args: parameters required for the attack. - :return: a float with the accuracy value - """ - args = _ArgsWrapper(args or {}) - - if ((attack is None) != (attack_args is None)): - raise ValueError("attack and attack_args must be " - "passed together.") - assert args.batch_size, "Batch size was not given in args dict" - if X_test is None or Y_test is None: - raise ValueError("X_test argument and Y_test argument " - "must be supplied.") - - # Init result var - accuracy = 0.0 - - # Compute number of batches - nb_batches = int(math.ceil(float(len(X_test)) / args.batch_size)) - assert nb_batches * args.batch_size >= len(X_test) - - X_cur = np.zeros((args.batch_size,) + X_test.shape[1:], - dtype=X_test.dtype) - Y_cur = np.zeros((args.batch_size,) + Y_test.shape[1:], - dtype=Y_test.dtype) - - tfe = tf.contrib.eager - batch_x = tfe.Variable(X_test[0:args.batch_size], dtype=tf.float32) - batch_y = tfe.Variable(Y_test[0:args.batch_size], dtype=tf.float32) - for batch in range(nb_batches): - if batch % 100 == 0 and batch > 0: - _logger.debug("Batch " + str(batch)) - - # Must not use the `batch_indices` function here, because it - # repeats some examples. - # It's acceptable to repeat during training, but not eval. - start = batch * args.batch_size - end = min(len(X_test), start + args.batch_size) - - # The last batch may be smaller than all others. This should not - # affect the accuarcy disproportionately. - cur_batch_size = end - start - X_cur[:cur_batch_size] = X_test[start:end] - Y_cur[:cur_batch_size] = Y_test[start:end] - tf.assign(batch_x, X_cur) - tf.assign(batch_y, Y_cur) - if attack is not None: - batch_adv_x = attack.generate(batch_x, **attack_args) - predictions = model.get_probs(batch_adv_x) - else: - predictions = model.get_probs(batch_x) - cur_corr_preds = tf.equal(tf.argmax(batch_y, axis=-1), - tf.argmax(predictions, axis=-1)) - - accuracy += cur_corr_preds.numpy()[:cur_batch_size].sum() - - assert end >= len(X_test) - - # Divide by number of examples to get final value - accuracy /= len(X_test) - - return accuracy - - -def model_argmax(model, samples): - """ - Helper function that computes the current class prediction - :param samples: numpy array with input samples (dims must match x) - :return: the argmax output of predictions, i.e. the current predicted class - """ - tfe = tf.contrib.eager - tf_samples = tfe.Variable(samples) - probabilities = model.get_probs(tf_samples) - - if samples.shape[0] == 1: - return tf.argmax(probabilities) - else: - return tf.argmax(probabilities, axis=1) diff --git a/cleverhans_tutorials/__init__.py b/cleverhans_tutorials/__init__.py deleted file mode 100644 index c2868b0b2..000000000 --- a/cleverhans_tutorials/__init__.py +++ /dev/null @@ -1,24 +0,0 @@ -""" -The CleverHans tutorials. -While mostly designed to be run as standalone scripts, the tutorials together also form an importable module. -Module importation is mostly intended to support writing unit tests of the tutorials themselves, etc. -The tutorial code is not part of our API contract and can change rapidly without warning. -""" -import os -import warnings - -import cleverhans - - -def check_installation(cur_file): - """Warn user if running cleverhans from a different directory than tutorial.""" - cur_dir = os.path.split(os.path.dirname(os.path.abspath(cur_file)))[0] - ch_dir = os.path.split(cleverhans.__path__[0])[0] - if cur_dir != ch_dir: - warnings.warn("It appears that you have at least two versions of " - "cleverhans installed, one at %s and one at" - " %s. You are running the tutorial script from the " - "former but python imported the library module from the " - "latter. This may cause errors, for example if the tutorial" - " version is newer than the library version and attempts to" - " call new features." % (cur_dir, ch_dir)) diff --git a/cleverhans_tutorials/cifar10_tutorial_tf.py b/cleverhans_tutorials/cifar10_tutorial_tf.py deleted file mode 100644 index a8474bc6c..000000000 --- a/cleverhans_tutorials/cifar10_tutorial_tf.py +++ /dev/null @@ -1,228 +0,0 @@ -""" -This tutorial shows how to generate adversarial examples using FGSM -and train a model using adversarial training with TensorFlow. -The original paper can be found at: -https://arxiv.org/abs/1412.6572 -""" -# pylint: disable=missing-docstring -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - -import logging -import numpy as np -import tensorflow as tf - -from cleverhans.attacks import FastGradientMethod -from cleverhans.augmentation import random_horizontal_flip, random_shift -from cleverhans.compat import flags -from cleverhans.dataset import CIFAR10 -from cleverhans.loss import CrossEntropy -from cleverhans.model_zoo.all_convolutional import ModelAllConvolutional -from cleverhans.train import train -from cleverhans.utils import AccuracyReport, set_log_level -from cleverhans.utils_tf import model_eval - -FLAGS = flags.FLAGS - -NB_EPOCHS = 6 -BATCH_SIZE = 128 -LEARNING_RATE = 0.001 -CLEAN_TRAIN = True -BACKPROP_THROUGH_ATTACK = False -NB_FILTERS = 64 - - -def cifar10_tutorial(train_start=0, train_end=60000, test_start=0, - test_end=10000, nb_epochs=NB_EPOCHS, batch_size=BATCH_SIZE, - learning_rate=LEARNING_RATE, - clean_train=CLEAN_TRAIN, - testing=False, - backprop_through_attack=BACKPROP_THROUGH_ATTACK, - nb_filters=NB_FILTERS, num_threads=None, - label_smoothing=0.1): - """ - CIFAR10 cleverhans tutorial - :param train_start: index of first training set example - :param train_end: index of last training set example - :param test_start: index of first test set example - :param test_end: index of last test set example - :param nb_epochs: number of epochs to train model - :param batch_size: size of training batches - :param learning_rate: learning rate for training - :param clean_train: perform normal training on clean examples only - before performing adversarial training. - :param testing: if true, complete an AccuracyReport for unit tests - to verify that performance is adequate - :param backprop_through_attack: If True, backprop through adversarial - example construction process during - adversarial training. - :param label_smoothing: float, amount of label smoothing for cross entropy - :return: an AccuracyReport object - """ - - # Object used to keep track of (and return) key accuracies - report = AccuracyReport() - - # Set TF random seed to improve reproducibility - tf.set_random_seed(1234) - - # Set logging level to see debug information - set_log_level(logging.DEBUG) - - # Create TF session - if num_threads: - config_args = dict(intra_op_parallelism_threads=1) - else: - config_args = {} - sess = tf.Session(config=tf.ConfigProto(**config_args)) - - # Get CIFAR10 data - data = CIFAR10(train_start=train_start, train_end=train_end, - test_start=test_start, test_end=test_end) - dataset_size = data.x_train.shape[0] - dataset_train = data.to_tensorflow()[0] - dataset_train = dataset_train.map( - lambda x, y: (random_shift(random_horizontal_flip(x)), y), 4) - dataset_train = dataset_train.batch(batch_size) - dataset_train = dataset_train.prefetch(16) - x_train, y_train = data.get_set('train') - x_test, y_test = data.get_set('test') - - # Use Image Parameters - img_rows, img_cols, nchannels = x_test.shape[1:4] - nb_classes = y_test.shape[1] - - # Define input TF placeholder - x = tf.placeholder(tf.float32, shape=(None, img_rows, img_cols, - nchannels)) - y = tf.placeholder(tf.float32, shape=(None, nb_classes)) - - # Train an MNIST model - train_params = { - 'nb_epochs': nb_epochs, - 'batch_size': batch_size, - 'learning_rate': learning_rate - } - eval_params = {'batch_size': batch_size} - fgsm_params = { - 'eps': 0.3, - 'clip_min': 0., - 'clip_max': 1. - } - rng = np.random.RandomState([2017, 8, 30]) - - def do_eval(preds, x_set, y_set, report_key, is_adv=None): - acc = model_eval(sess, x, y, preds, x_set, y_set, args=eval_params) - setattr(report, report_key, acc) - if is_adv is None: - report_text = None - elif is_adv: - report_text = 'adversarial' - else: - report_text = 'legitimate' - if report_text: - print('Test accuracy on %s examples: %0.4f' % (report_text, acc)) - - if clean_train: - model = ModelAllConvolutional('model1', nb_classes, nb_filters, - input_shape=[32, 32, 3]) - preds = model.get_logits(x) - loss = CrossEntropy(model, smoothing=label_smoothing) - - def evaluate(): - do_eval(preds, x_test, y_test, 'clean_train_clean_eval', False) - - train(sess, loss, None, None, - dataset_train=dataset_train, dataset_size=dataset_size, - evaluate=evaluate, args=train_params, rng=rng, - var_list=model.get_params()) - - # Calculate training error - if testing: - do_eval(preds, x_train, y_train, 'train_clean_train_clean_eval') - - # Initialize the Fast Gradient Sign Method (FGSM) attack object and - # graph - fgsm = FastGradientMethod(model, sess=sess) - adv_x = fgsm.generate(x, **fgsm_params) - preds_adv = model.get_logits(adv_x) - - # Evaluate the accuracy of the MNIST model on adversarial examples - do_eval(preds_adv, x_test, y_test, 'clean_train_adv_eval', True) - - # Calculate training error - if testing: - do_eval(preds_adv, x_train, y_train, 'train_clean_train_adv_eval') - - print('Repeating the process, using adversarial training') - - # Create a new model and train it to be robust to FastGradientMethod - model2 = ModelAllConvolutional('model2', nb_classes, nb_filters, - input_shape=[32, 32, 3]) - fgsm2 = FastGradientMethod(model2, sess=sess) - - def attack(x): - return fgsm2.generate(x, **fgsm_params) - - loss2 = CrossEntropy(model2, smoothing=label_smoothing, attack=attack) - preds2 = model2.get_logits(x) - adv_x2 = attack(x) - - if not backprop_through_attack: - # For the fgsm attack used in this tutorial, the attack has zero - # gradient so enabling this flag does not change the gradient. - # For some other attacks, enabling this flag increases the cost of - # training, but gives the defender the ability to anticipate how - # the atacker will change their strategy in response to updates to - # the defender's parameters. - adv_x2 = tf.stop_gradient(adv_x2) - preds2_adv = model2.get_logits(adv_x2) - - def evaluate2(): - # Accuracy of adversarially trained model on legitimate test inputs - do_eval(preds2, x_test, y_test, 'adv_train_clean_eval', False) - # Accuracy of the adversarially trained model on adversarial examples - do_eval(preds2_adv, x_test, y_test, 'adv_train_adv_eval', True) - - # Perform and evaluate adversarial training - train(sess, loss2, None, None, - dataset_train=dataset_train, dataset_size=dataset_size, - evaluate=evaluate2, args=train_params, rng=rng, - var_list=model2.get_params()) - - # Calculate training errors - if testing: - do_eval(preds2, x_train, y_train, 'train_adv_train_clean_eval') - do_eval(preds2_adv, x_train, y_train, 'train_adv_train_adv_eval') - - return report - - -def main(argv=None): - from cleverhans_tutorials import check_installation - check_installation(__file__) - - cifar10_tutorial(nb_epochs=FLAGS.nb_epochs, batch_size=FLAGS.batch_size, - learning_rate=FLAGS.learning_rate, - clean_train=FLAGS.clean_train, - backprop_through_attack=FLAGS.backprop_through_attack, - nb_filters=FLAGS.nb_filters) - - -if __name__ == '__main__': - flags.DEFINE_integer('nb_filters', NB_FILTERS, - 'Model size multiplier') - flags.DEFINE_integer('nb_epochs', NB_EPOCHS, - 'Number of epochs to train model') - flags.DEFINE_integer('batch_size', BATCH_SIZE, - 'Size of training batches') - flags.DEFINE_float('learning_rate', LEARNING_RATE, - 'Learning rate for training') - flags.DEFINE_bool('clean_train', CLEAN_TRAIN, 'Train on clean examples') - flags.DEFINE_bool('backprop_through_attack', BACKPROP_THROUGH_ATTACK, - ('If True, backprop through adversarial example ' - 'construction process during adversarial training')) - - tf.app.run() diff --git a/cleverhans_tutorials/evaluate_pickled_model.py b/cleverhans_tutorials/evaluate_pickled_model.py deleted file mode 100644 index 147eb1e25..000000000 --- a/cleverhans_tutorials/evaluate_pickled_model.py +++ /dev/null @@ -1,108 +0,0 @@ -""" -This script evaluates trained models that have been saved to the filesystem. -See mnist_tutorial_picklable.py for instructions. -""" -# pylint: disable=missing-docstring -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - -import logging - -import tensorflow as tf - -from cleverhans.compat import flags -from cleverhans.dataset import MNIST -from cleverhans.attacks import FastGradientMethod -from cleverhans.utils import set_log_level -from cleverhans.utils_tf import model_eval, silence -from cleverhans.serial import load -silence() - -FLAGS = flags.FLAGS - - -def evaluate_model(filepath, - train_start=0, train_end=60000, test_start=0, - test_end=10000, batch_size=128, - testing=False, num_threads=None): - """ - Run evaluation on a saved model - :param filepath: path to model to evaluate - :param train_start: index of first training set example - :param train_end: index of last training set example - :param test_start: index of first test set example - :param test_end: index of last test set example - :param batch_size: size of evaluation batches - """ - - # Set TF random seed to improve reproducibility - tf.set_random_seed(1234) - - # Set logging level to see debug information - set_log_level(logging.INFO) - - # Create TF session - if num_threads: - config_args = dict(intra_op_parallelism_threads=1) - else: - config_args = {} - sess = tf.Session(config=tf.ConfigProto(**config_args)) - - # Get MNIST test data - mnist = MNIST(train_start=train_start, train_end=train_end, - test_start=test_start, test_end=test_end) - x_train, y_train = mnist.get_set('train') - x_test, y_test = mnist.get_set('test') - - # Use Image Parameters - img_rows, img_cols, nchannels = x_train.shape[1:4] - nb_classes = y_train.shape[1] - - # Define input TF placeholder - x = tf.placeholder(tf.float32, shape=(None, img_rows, img_cols, - nchannels)) - y = tf.placeholder(tf.float32, shape=(None, nb_classes)) - - eval_params = {'batch_size': batch_size} - fgsm_params = { - 'eps': 0.3, - 'clip_min': 0., - 'clip_max': 1. - } - - def do_eval(preds, x_set, y_set, report_key, is_adv=None): - acc = model_eval(sess, x, y, preds, x_set, y_set, args=eval_params) - if is_adv is None: - report_text = None - elif is_adv: - report_text = 'adversarial' - else: - report_text = 'legitimate' - if report_text: - print('Test accuracy on %s examples: %0.4f' % (report_text, acc)) - - with sess.as_default(): - model = load(filepath) - assert len(model.get_params()) > 0 - - # Initialize the Fast Gradient Sign Method (FGSM) attack object and - # graph - fgsm = FastGradientMethod(model, sess=sess) - adv_x = fgsm.generate(x, **fgsm_params) - preds_adv = model.get_logits(adv_x) - preds = model.get_logits(x) - - # Evaluate the accuracy of the MNIST model on adversarial examples - do_eval(preds, x_test, y_test, 'train_clean_train_clean_eval', False) - do_eval(preds_adv, x_test, y_test, 'clean_train_adv_eval', True) - - -def main(argv=None): - _, filepath = argv - evaluate_model(filepath=filepath) - - -if __name__ == '__main__': - tf.app.run() diff --git a/cleverhans_tutorials/mnist_blackbox.py b/cleverhans_tutorials/mnist_blackbox.py deleted file mode 100644 index b794bc01b..000000000 --- a/cleverhans_tutorials/mnist_blackbox.py +++ /dev/null @@ -1,323 +0,0 @@ -""" -This tutorial shows how to generate adversarial examples -using FGSM in black-box setting. -The original paper can be found at: -https://arxiv.org/abs/1602.02697 -""" -# pylint: disable=missing-docstring -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - -import functools -import logging -import numpy as np -from six.moves import xrange -import tensorflow as tf - -from cleverhans.attacks import FastGradientMethod -from cleverhans.utils_tf import jacobian_graph, jacobian_augmentation -from cleverhans.compat import flags -from cleverhans.dataset import MNIST -from cleverhans.initializers import HeReLuNormalInitializer -from cleverhans.loss import CrossEntropy -from cleverhans.model import Model -from cleverhans.train import train -from cleverhans.utils import set_log_level -from cleverhans.utils import TemporaryLogLevel -from cleverhans.utils import to_categorical -from cleverhans.utils_tf import model_eval, batch_eval - -from cleverhans.model_zoo.basic_cnn import ModelBasicCNN - -FLAGS = flags.FLAGS - -NB_CLASSES = 10 -BATCH_SIZE = 128 -LEARNING_RATE = .001 -NB_EPOCHS = 10 -HOLDOUT = 150 -DATA_AUG = 6 -NB_EPOCHS_S = 10 -LMBDA = .1 -AUG_BATCH_SIZE = 512 - - -def setup_tutorial(): - """ - Helper function to check correct configuration of tf for tutorial - :return: True if setup checks completed - """ - - # Set TF random seed to improve reproducibility - tf.set_random_seed(1234) - - return True - - -def prep_bbox(sess, x, y, x_train, y_train, x_test, y_test, - nb_epochs, batch_size, learning_rate, - rng, nb_classes=10, img_rows=28, img_cols=28, nchannels=1): - """ - Define and train a model that simulates the "remote" - black-box oracle described in the original paper. - :param sess: the TF session - :param x: the input placeholder for MNIST - :param y: the ouput placeholder for MNIST - :param x_train: the training data for the oracle - :param y_train: the training labels for the oracle - :param x_test: the testing data for the oracle - :param y_test: the testing labels for the oracle - :param nb_epochs: number of epochs to train model - :param batch_size: size of training batches - :param learning_rate: learning rate for training - :param rng: numpy.random.RandomState - :return: - """ - - # Define TF model graph (for the black-box model) - nb_filters = 64 - model = ModelBasicCNN('model1', nb_classes, nb_filters) - loss = CrossEntropy(model, smoothing=0.1) - predictions = model.get_logits(x) - print("Defined TensorFlow model graph.") - - # Train an MNIST model - train_params = { - 'nb_epochs': nb_epochs, - 'batch_size': batch_size, - 'learning_rate': learning_rate - } - train(sess, loss, x_train, y_train, args=train_params, rng=rng) - - # Print out the accuracy on legitimate data - eval_params = {'batch_size': batch_size} - accuracy = model_eval(sess, x, y, predictions, x_test, y_test, - args=eval_params) - print('Test accuracy of black-box on legitimate test ' - 'examples: ' + str(accuracy)) - - return model, predictions, accuracy - - -class ModelSubstitute(Model): - def __init__(self, scope, nb_classes, nb_filters=200, **kwargs): - del kwargs - Model.__init__(self, scope, nb_classes, locals()) - self.nb_filters = nb_filters - - def fprop(self, x, **kwargs): - del kwargs - my_dense = functools.partial( - tf.layers.dense, kernel_initializer=HeReLuNormalInitializer) - with tf.variable_scope(self.scope, reuse=tf.AUTO_REUSE): - y = tf.layers.flatten(x) - y = my_dense(y, self.nb_filters, activation=tf.nn.relu) - y = my_dense(y, self.nb_filters, activation=tf.nn.relu) - logits = my_dense(y, self.nb_classes) - return {self.O_LOGITS: logits, - self.O_PROBS: tf.nn.softmax(logits=logits)} - - -def train_sub(sess, x, y, bbox_preds, x_sub, y_sub, nb_classes, - nb_epochs_s, batch_size, learning_rate, data_aug, lmbda, - aug_batch_size, rng, img_rows=28, img_cols=28, - nchannels=1): - """ - This function creates the substitute by alternatively - augmenting the training data and training the substitute. - :param sess: TF session - :param x: input TF placeholder - :param y: output TF placeholder - :param bbox_preds: output of black-box model predictions - :param x_sub: initial substitute training data - :param y_sub: initial substitute training labels - :param nb_classes: number of output classes - :param nb_epochs_s: number of epochs to train substitute model - :param batch_size: size of training batches - :param learning_rate: learning rate for training - :param data_aug: number of times substitute training data is augmented - :param lmbda: lambda from arxiv.org/abs/1602.02697 - :param rng: numpy.random.RandomState instance - :return: - """ - # Define TF model graph (for the black-box model) - model_sub = ModelSubstitute('model_s', nb_classes) - preds_sub = model_sub.get_logits(x) - loss_sub = CrossEntropy(model_sub, smoothing=0) - - print("Defined TensorFlow model graph for the substitute.") - - # Define the Jacobian symbolically using TensorFlow - grads = jacobian_graph(preds_sub, x, nb_classes) - - # Train the substitute and augment dataset alternatively - for rho in xrange(data_aug): - print("Substitute training epoch #" + str(rho)) - train_params = { - 'nb_epochs': nb_epochs_s, - 'batch_size': batch_size, - 'learning_rate': learning_rate - } - with TemporaryLogLevel(logging.WARNING, "cleverhans.utils.tf"): - train(sess, loss_sub, x_sub, to_categorical(y_sub, nb_classes), - init_all=False, args=train_params, rng=rng, - var_list=model_sub.get_params()) - - # If we are not at last substitute training iteration, augment dataset - if rho < data_aug - 1: - print("Augmenting substitute training data.") - # Perform the Jacobian augmentation - lmbda_coef = 2*int(int(rho/3)%2==0)-1 - x_sub = jacobian_augmentation(sess, x, x_sub, y_sub, grads, - lmbda_coef * lmbda, aug_batch_size) - - print("Labeling substitute training data.") - # Label the newly generated synthetic points using the black-box - y_sub = np.hstack([y_sub, y_sub]) - x_sub_prev = x_sub[int(len(x_sub)/2):] - eval_params = {'batch_size': batch_size} - bbox_val = batch_eval(sess, [x], [bbox_preds], [x_sub_prev], - args=eval_params)[0] - # Note here that we take the argmax because the adversary - # only has access to the label (not the probabilities) output - # by the black-box model - y_sub[int(len(x_sub)/2):] = np.argmax(bbox_val, axis=1) - - return model_sub, preds_sub - - -def mnist_blackbox(train_start=0, train_end=60000, test_start=0, - test_end=10000, nb_classes=NB_CLASSES, - batch_size=BATCH_SIZE, learning_rate=LEARNING_RATE, - nb_epochs=NB_EPOCHS, holdout=HOLDOUT, data_aug=DATA_AUG, - nb_epochs_s=NB_EPOCHS_S, lmbda=LMBDA, - aug_batch_size=AUG_BATCH_SIZE): - """ - MNIST tutorial for the black-box attack from arxiv.org/abs/1602.02697 - :param train_start: index of first training set example - :param train_end: index of last training set example - :param test_start: index of first test set example - :param test_end: index of last test set example - :return: a dictionary with: - * black-box model accuracy on test set - * substitute model accuracy on test set - * black-box model accuracy on adversarial examples transferred - from the substitute model - """ - - # Set logging level to see debug information - set_log_level(logging.DEBUG) - - # Dictionary used to keep track and return key accuracies - accuracies = {} - - # Perform tutorial setup - assert setup_tutorial() - - # Create TF session - sess = tf.Session() - - # Get MNIST data - mnist = MNIST(train_start=train_start, train_end=train_end, - test_start=test_start, test_end=test_end) - x_train, y_train = mnist.get_set('train') - x_test, y_test = mnist.get_set('test') - - # Initialize substitute training set reserved for adversary - x_sub = x_test[:holdout] - y_sub = np.argmax(y_test[:holdout], axis=1) - - # Redefine test set as remaining samples unavailable to adversaries - x_test = x_test[holdout:] - y_test = y_test[holdout:] - - # Obtain Image parameters - img_rows, img_cols, nchannels = x_train.shape[1:4] - nb_classes = y_train.shape[1] - - # Define input TF placeholder - x = tf.placeholder(tf.float32, shape=(None, img_rows, img_cols, - nchannels)) - y = tf.placeholder(tf.float32, shape=(None, nb_classes)) - - # Seed random number generator so tutorial is reproducible - rng = np.random.RandomState([2017, 8, 30]) - - # Simulate the black-box model locally - # You could replace this by a remote labeling API for instance - print("Preparing the black-box model.") - prep_bbox_out = prep_bbox(sess, x, y, x_train, y_train, x_test, y_test, - nb_epochs, batch_size, learning_rate, - rng, nb_classes, img_rows, img_cols, nchannels) - model, bbox_preds, accuracies['bbox'] = prep_bbox_out - - # Train substitute using method from https://arxiv.org/abs/1602.02697 - print("Training the substitute model.") - train_sub_out = train_sub(sess, x, y, bbox_preds, x_sub, y_sub, - nb_classes, nb_epochs_s, batch_size, - learning_rate, data_aug, lmbda, aug_batch_size, - rng, img_rows, img_cols, nchannels) - model_sub, preds_sub = train_sub_out - - # Evaluate the substitute model on clean test examples - eval_params = {'batch_size': batch_size} - acc = model_eval(sess, x, y, preds_sub, x_test, y_test, args=eval_params) - accuracies['sub'] = acc - - # Initialize the Fast Gradient Sign Method (FGSM) attack object. - fgsm_par = {'eps': 0.3, 'ord': np.inf, 'clip_min': 0., 'clip_max': 1.} - fgsm = FastGradientMethod(model_sub, sess=sess) - - # Craft adversarial examples using the substitute - eval_params = {'batch_size': batch_size} - x_adv_sub = fgsm.generate(x, **fgsm_par) - - # Evaluate the accuracy of the "black-box" model on adversarial examples - accuracy = model_eval(sess, x, y, model.get_logits(x_adv_sub), - x_test, y_test, args=eval_params) - print('Test accuracy of oracle on adversarial examples generated ' - 'using the substitute: ' + str(accuracy)) - accuracies['bbox_on_sub_adv_ex'] = accuracy - - return accuracies - - -def main(argv=None): - from cleverhans_tutorials import check_installation - check_installation(__file__) - - mnist_blackbox(nb_classes=FLAGS.nb_classes, batch_size=FLAGS.batch_size, - learning_rate=FLAGS.learning_rate, - nb_epochs=FLAGS.nb_epochs, holdout=FLAGS.holdout, - data_aug=FLAGS.data_aug, nb_epochs_s=FLAGS.nb_epochs_s, - lmbda=FLAGS.lmbda, aug_batch_size=FLAGS.data_aug_batch_size) - - -if __name__ == '__main__': - - # General flags - flags.DEFINE_integer('nb_classes', NB_CLASSES, - 'Number of classes in problem') - flags.DEFINE_integer('batch_size', BATCH_SIZE, - 'Size of training batches') - flags.DEFINE_float('learning_rate', LEARNING_RATE, - 'Learning rate for training') - - # Flags related to oracle - flags.DEFINE_integer('nb_epochs', NB_EPOCHS, - 'Number of epochs to train model') - - # Flags related to substitute - flags.DEFINE_integer('holdout', HOLDOUT, - 'Test set holdout for adversary') - flags.DEFINE_integer('data_aug', DATA_AUG, - 'Number of substitute data augmentations') - flags.DEFINE_integer('nb_epochs_s', NB_EPOCHS_S, - 'Training epochs for substitute') - flags.DEFINE_float('lmbda', LMBDA, 'Lambda from arxiv.org/abs/1602.02697') - flags.DEFINE_integer('data_aug_batch_size', AUG_BATCH_SIZE, - 'Batch size for augmentation') - - tf.app.run() diff --git a/cleverhans_tutorials/mnist_tutorial_cw.py b/cleverhans_tutorials/mnist_tutorial_cw.py deleted file mode 100644 index a40db9744..000000000 --- a/cleverhans_tutorials/mnist_tutorial_cw.py +++ /dev/null @@ -1,269 +0,0 @@ -""" -This tutorial shows how to generate adversarial examples -using C&W attack in white-box setting. -The original paper can be found at: -https://nicholas.carlini.com/papers/2017_sp_nnrobustattacks.pdf -""" -# pylint: disable=missing-docstring -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - -import logging -import os -import numpy as np -import tensorflow as tf - -from cleverhans.attacks import CarliniWagnerL2 -from cleverhans.compat import flags -from cleverhans.dataset import MNIST -from cleverhans.loss import CrossEntropy -from cleverhans.utils import grid_visual, AccuracyReport -from cleverhans.utils import set_log_level -from cleverhans.utils_tf import model_eval, tf_model_load -from cleverhans.train import train -from cleverhans.model_zoo.basic_cnn import ModelBasicCNN - -FLAGS = flags.FLAGS - -VIZ_ENABLED = True -BATCH_SIZE = 128 -NB_EPOCHS = 6 -SOURCE_SAMPLES = 10 -LEARNING_RATE = .001 -CW_LEARNING_RATE = .2 -ATTACK_ITERATIONS = 100 -MODEL_PATH = os.path.join('models', 'mnist') -TARGETED = True - - -def mnist_tutorial_cw(train_start=0, train_end=60000, test_start=0, - test_end=10000, viz_enabled=VIZ_ENABLED, - nb_epochs=NB_EPOCHS, batch_size=BATCH_SIZE, - source_samples=SOURCE_SAMPLES, - learning_rate=LEARNING_RATE, - attack_iterations=ATTACK_ITERATIONS, - model_path=MODEL_PATH, - targeted=TARGETED): - """ - MNIST tutorial for Carlini and Wagner's attack - :param train_start: index of first training set example - :param train_end: index of last training set example - :param test_start: index of first test set example - :param test_end: index of last test set example - :param viz_enabled: (boolean) activate plots of adversarial examples - :param nb_epochs: number of epochs to train model - :param batch_size: size of training batches - :param nb_classes: number of output classes - :param source_samples: number of test inputs to attack - :param learning_rate: learning rate for training - :param model_path: path to the model file - :param targeted: should we run a targeted attack? or untargeted? - :return: an AccuracyReport object - """ - # Object used to keep track of (and return) key accuracies - report = AccuracyReport() - - # Set TF random seed to improve reproducibility - tf.set_random_seed(1234) - - # Create TF session - sess = tf.Session() - print("Created TensorFlow session.") - - set_log_level(logging.DEBUG) - - # Get MNIST test data - mnist = MNIST(train_start=train_start, train_end=train_end, - test_start=test_start, test_end=test_end) - x_train, y_train = mnist.get_set('train') - x_test, y_test = mnist.get_set('test') - - # Obtain Image Parameters - img_rows, img_cols, nchannels = x_train.shape[1:4] - nb_classes = y_train.shape[1] - - # Define input TF placeholder - x = tf.placeholder(tf.float32, shape=(None, img_rows, img_cols, - nchannels)) - y = tf.placeholder(tf.float32, shape=(None, nb_classes)) - nb_filters = 64 - - # Define TF model graph - model = ModelBasicCNN('model1', nb_classes, nb_filters) - preds = model.get_logits(x) - loss = CrossEntropy(model, smoothing=0.1) - print("Defined TensorFlow model graph.") - - ########################################################################### - # Training the model using TensorFlow - ########################################################################### - - # Train an MNIST model - train_params = { - 'nb_epochs': nb_epochs, - 'batch_size': batch_size, - 'learning_rate': learning_rate, - 'filename': os.path.split(model_path)[-1] - } - - rng = np.random.RandomState([2017, 8, 30]) - # check if we've trained before, and if we have, use that pre-trained model - if os.path.exists(model_path + ".meta"): - tf_model_load(sess, model_path) - else: - train(sess, loss, x_train, y_train, args=train_params, rng=rng) - saver = tf.train.Saver() - saver.save(sess, model_path) - - # Evaluate the accuracy of the MNIST model on legitimate test examples - eval_params = {'batch_size': batch_size} - accuracy = model_eval(sess, x, y, preds, x_test, y_test, args=eval_params) - assert x_test.shape[0] == test_end - test_start, x_test.shape - print('Test accuracy on legitimate test examples: {0}'.format(accuracy)) - report.clean_train_clean_eval = accuracy - - ########################################################################### - # Craft adversarial examples using Carlini and Wagner's approach - ########################################################################### - nb_adv_per_sample = str(nb_classes - 1) if targeted else '1' - print('Crafting ' + str(source_samples) + ' * ' + nb_adv_per_sample + - ' adversarial examples') - print("This could take some time ...") - - # Instantiate a CW attack object - cw = CarliniWagnerL2(model, sess=sess) - - if viz_enabled: - assert source_samples == nb_classes - idxs = [np.where(np.argmax(y_test, axis=1) == i)[0][0] - for i in range(nb_classes)] - if targeted: - if viz_enabled: - # Initialize our array for grid visualization - grid_shape = (nb_classes, nb_classes, img_rows, img_cols, - nchannels) - grid_viz_data = np.zeros(grid_shape, dtype='f') - - adv_inputs = np.array( - [[instance] * nb_classes for instance in x_test[idxs]], - dtype=np.float32) - else: - adv_inputs = np.array( - [[instance] * nb_classes for - instance in x_test[:source_samples]], dtype=np.float32) - - one_hot = np.zeros((nb_classes, nb_classes)) - one_hot[np.arange(nb_classes), np.arange(nb_classes)] = 1 - - adv_inputs = adv_inputs.reshape( - (source_samples * nb_classes, img_rows, img_cols, nchannels)) - adv_ys = np.array([one_hot] * source_samples, - dtype=np.float32).reshape((source_samples * - nb_classes, nb_classes)) - yname = "y_target" - else: - if viz_enabled: - # Initialize our array for grid visualization - grid_shape = (nb_classes, 2, img_rows, img_cols, nchannels) - grid_viz_data = np.zeros(grid_shape, dtype='f') - - adv_inputs = x_test[idxs] - else: - adv_inputs = x_test[:source_samples] - - adv_ys = None - yname = "y" - - if targeted: - cw_params_batch_size = source_samples * nb_classes - else: - cw_params_batch_size = source_samples - cw_params = {'binary_search_steps': 1, - yname: adv_ys, - 'max_iterations': attack_iterations, - 'learning_rate': CW_LEARNING_RATE, - 'batch_size': cw_params_batch_size, - 'initial_const': 10} - - adv = cw.generate_np(adv_inputs, - **cw_params) - - eval_params = {'batch_size': np.minimum(nb_classes, source_samples)} - if targeted: - adv_accuracy = model_eval( - sess, x, y, preds, adv, adv_ys, args=eval_params) - else: - if viz_enabled: - err = model_eval(sess, x, y, preds, adv, y_test[idxs], args=eval_params) - adv_accuracy = 1 - err - else: - err = model_eval(sess, x, y, preds, adv, y_test[:source_samples], - args=eval_params) - adv_accuracy = 1 - err - - if viz_enabled: - for j in range(nb_classes): - if targeted: - for i in range(nb_classes): - grid_viz_data[i, j] = adv[i * nb_classes + j] - else: - grid_viz_data[j, 0] = adv_inputs[j] - grid_viz_data[j, 1] = adv[j] - - print(grid_viz_data.shape) - - print('--------------------------------------') - - # Compute the number of adversarial examples that were successfully found - print('Avg. rate of successful adv. examples {0:.4f}'.format(adv_accuracy)) - report.clean_train_adv_eval = 1. - adv_accuracy - - # Compute the average distortion introduced by the algorithm - percent_perturbed = np.mean(np.sum((adv - adv_inputs)**2, - axis=(1, 2, 3))**.5) - print('Avg. L_2 norm of perturbations {0:.4f}'.format(percent_perturbed)) - - # Close TF session - sess.close() - - # Finally, block & display a grid of all the adversarial examples - if viz_enabled: - _ = grid_visual(grid_viz_data) - - return report - - -def main(argv=None): - from cleverhans_tutorials import check_installation - check_installation(__file__) - - mnist_tutorial_cw(viz_enabled=FLAGS.viz_enabled, - nb_epochs=FLAGS.nb_epochs, - batch_size=FLAGS.batch_size, - source_samples=FLAGS.source_samples, - learning_rate=FLAGS.learning_rate, - attack_iterations=FLAGS.attack_iterations, - model_path=FLAGS.model_path, - targeted=FLAGS.targeted) - - -if __name__ == '__main__': - flags.DEFINE_boolean('viz_enabled', VIZ_ENABLED, - 'Visualize adversarial ex.') - flags.DEFINE_integer('nb_epochs', NB_EPOCHS, - 'Number of epochs to train model') - flags.DEFINE_integer('batch_size', BATCH_SIZE, 'Size of training batches') - flags.DEFINE_integer('source_samples', SOURCE_SAMPLES, - 'Number of test inputs to attack') - flags.DEFINE_float('learning_rate', LEARNING_RATE, - 'Learning rate for training') - flags.DEFINE_string('model_path', MODEL_PATH, - 'Path to save or load the model file') - flags.DEFINE_integer('attack_iterations', ATTACK_ITERATIONS, - 'Number of iterations to run attack; 1000 is good') - flags.DEFINE_boolean('targeted', TARGETED, - 'Run the tutorial in targeted mode?') - - tf.app.run() diff --git a/cleverhans_tutorials/mnist_tutorial_jsma.py b/cleverhans_tutorials/mnist_tutorial_jsma.py deleted file mode 100644 index fec8bea1e..000000000 --- a/cleverhans_tutorials/mnist_tutorial_jsma.py +++ /dev/null @@ -1,233 +0,0 @@ -""" -This tutorial shows how to generate adversarial examples -using JSMA in white-box setting. -The original paper can be found at: -https://arxiv.org/abs/1511.07528 -""" -# pylint: disable=missing-docstring -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - -import logging -import numpy as np -from six.moves import xrange -import tensorflow as tf - -from cleverhans.attacks import SaliencyMapMethod -from cleverhans.compat import flags -from cleverhans.dataset import MNIST -from cleverhans.loss import CrossEntropy -from cleverhans.utils import other_classes, set_log_level -from cleverhans.utils import pair_visual, grid_visual, AccuracyReport -from cleverhans.utils_tf import model_eval, model_argmax -from cleverhans.train import train -from cleverhans.model_zoo.basic_cnn import ModelBasicCNN - -FLAGS = flags.FLAGS - -VIZ_ENABLED = True -NB_EPOCHS = 6 -BATCH_SIZE = 128 -LEARNING_RATE = .001 -SOURCE_SAMPLES = 10 - - -def mnist_tutorial_jsma(train_start=0, train_end=60000, test_start=0, - test_end=10000, viz_enabled=VIZ_ENABLED, - nb_epochs=NB_EPOCHS, batch_size=BATCH_SIZE, - source_samples=SOURCE_SAMPLES, - learning_rate=LEARNING_RATE): - """ - MNIST tutorial for the Jacobian-based saliency map approach (JSMA) - :param train_start: index of first training set example - :param train_end: index of last training set example - :param test_start: index of first test set example - :param test_end: index of last test set example - :param viz_enabled: (boolean) activate plots of adversarial examples - :param nb_epochs: number of epochs to train model - :param batch_size: size of training batches - :param nb_classes: number of output classes - :param source_samples: number of test inputs to attack - :param learning_rate: learning rate for training - :return: an AccuracyReport object - """ - # Object used to keep track of (and return) key accuracies - report = AccuracyReport() - - # Set TF random seed to improve reproducibility - tf.set_random_seed(1234) - - # Create TF session and set as Keras backend session - sess = tf.Session() - print("Created TensorFlow session.") - - set_log_level(logging.DEBUG) - - # Get MNIST test data - mnist = MNIST(train_start=train_start, train_end=train_end, - test_start=test_start, test_end=test_end) - x_train, y_train = mnist.get_set('train') - x_test, y_test = mnist.get_set('test') - - # Obtain Image Parameters - img_rows, img_cols, nchannels = x_train.shape[1:4] - nb_classes = y_train.shape[1] - - # Define input TF placeholder - x = tf.placeholder(tf.float32, shape=(None, img_rows, img_cols, - nchannels)) - y = tf.placeholder(tf.float32, shape=(None, nb_classes)) - - nb_filters = 64 - # Define TF model graph - model = ModelBasicCNN('model1', nb_classes, nb_filters) - preds = model.get_logits(x) - loss = CrossEntropy(model, smoothing=0.1) - print("Defined TensorFlow model graph.") - - ########################################################################### - # Training the model using TensorFlow - ########################################################################### - - # Train an MNIST model - train_params = { - 'nb_epochs': nb_epochs, - 'batch_size': batch_size, - 'learning_rate': learning_rate - } - sess.run(tf.global_variables_initializer()) - rng = np.random.RandomState([2017, 8, 30]) - train(sess, loss, x_train, y_train, args=train_params, rng=rng) - - # Evaluate the accuracy of the MNIST model on legitimate test examples - eval_params = {'batch_size': batch_size} - accuracy = model_eval(sess, x, y, preds, x_test, y_test, args=eval_params) - assert x_test.shape[0] == test_end - test_start, x_test.shape - print('Test accuracy on legitimate test examples: {0}'.format(accuracy)) - report.clean_train_clean_eval = accuracy - - ########################################################################### - # Craft adversarial examples using the Jacobian-based saliency map approach - ########################################################################### - print('Crafting ' + str(source_samples) + ' * ' + str(nb_classes - 1) + - ' adversarial examples') - - # Keep track of success (adversarial example classified in target) - results = np.zeros((nb_classes, source_samples), dtype='i') - - # Rate of perturbed features for each test set example and target class - perturbations = np.zeros((nb_classes, source_samples), dtype='f') - - # Initialize our array for grid visualization - grid_shape = (nb_classes, nb_classes, img_rows, img_cols, nchannels) - grid_viz_data = np.zeros(grid_shape, dtype='f') - - # Instantiate a SaliencyMapMethod attack object - jsma = SaliencyMapMethod(model, sess=sess) - jsma_params = {'theta': 1., 'gamma': 0.1, - 'clip_min': 0., 'clip_max': 1., - 'y_target': None} - - figure = None - # Loop over the samples we want to perturb into adversarial examples - for sample_ind in xrange(0, source_samples): - print('--------------------------------------') - print('Attacking input %i/%i' % (sample_ind + 1, source_samples)) - sample = x_test[sample_ind:(sample_ind + 1)] - - # We want to find an adversarial example for each possible target class - # (i.e. all classes that differ from the label given in the dataset) - current_class = int(np.argmax(y_test[sample_ind])) - target_classes = other_classes(nb_classes, current_class) - - # For the grid visualization, keep original images along the diagonal - grid_viz_data[current_class, current_class, :, :, :] = np.reshape( - sample, (img_rows, img_cols, nchannels)) - - # Loop over all target classes - for target in target_classes: - print('Generating adv. example for target class %i' % target) - - # This call runs the Jacobian-based saliency map approach - one_hot_target = np.zeros((1, nb_classes), dtype=np.float32) - one_hot_target[0, target] = 1 - jsma_params['y_target'] = one_hot_target - adv_x = jsma.generate_np(sample, **jsma_params) - - # Check if success was achieved - res = int(model_argmax(sess, x, preds, adv_x) == target) - - # Compute number of modified features - adv_x_reshape = adv_x.reshape(-1) - test_in_reshape = x_test[sample_ind].reshape(-1) - nb_changed = np.where(adv_x_reshape != test_in_reshape)[0].shape[0] - percent_perturb = float(nb_changed) / adv_x.reshape(-1).shape[0] - - # Display the original and adversarial images side-by-side - if viz_enabled: - figure = pair_visual( - np.reshape(sample, (img_rows, img_cols, nchannels)), - np.reshape(adv_x, (img_rows, img_cols, nchannels)), figure) - - # Add our adversarial example to our grid data - grid_viz_data[target, current_class, :, :, :] = np.reshape( - adv_x, (img_rows, img_cols, nchannels)) - - # Update the arrays for later analysis - results[target, sample_ind] = res - perturbations[target, sample_ind] = percent_perturb - - print('--------------------------------------') - - # Compute the number of adversarial examples that were successfully found - nb_targets_tried = ((nb_classes - 1) * source_samples) - succ_rate = float(np.sum(results)) / nb_targets_tried - print('Avg. rate of successful adv. examples {0:.4f}'.format(succ_rate)) - report.clean_train_adv_eval = 1. - succ_rate - - # Compute the average distortion introduced by the algorithm - percent_perturbed = np.mean(perturbations[np.where(perturbations!=0)]) - print('Avg. rate of perturbed features {0:.4f}'.format(percent_perturbed)) - - # Compute the average distortion introduced for successful samples only - percent_perturb_succ = np.mean(perturbations[np.where(perturbations!=0)] * (results[np.where(perturbations!=0)] == 1)) - print('Avg. rate of perturbed features for successful ' - 'adversarial examples {0:.4f}'.format(percent_perturb_succ)) - - # Close TF session - sess.close() - - # Finally, block & display a grid of all the adversarial examples - if viz_enabled: - import matplotlib.pyplot as plt - plt.close(figure) - _ = grid_visual(grid_viz_data) - - return report - - -def main(argv=None): - from cleverhans_tutorials import check_installation - check_installation(__file__) - - mnist_tutorial_jsma(viz_enabled=FLAGS.viz_enabled, - nb_epochs=FLAGS.nb_epochs, - batch_size=FLAGS.batch_size, - source_samples=FLAGS.source_samples, - learning_rate=FLAGS.learning_rate) - - -if __name__ == '__main__': - flags.DEFINE_boolean('viz_enabled', VIZ_ENABLED, - 'Visualize adversarial ex.') - flags.DEFINE_integer('nb_epochs', NB_EPOCHS, - 'Number of epochs to train model') - flags.DEFINE_integer('batch_size', BATCH_SIZE, 'Size of training batches') - flags.DEFINE_integer('source_samples', SOURCE_SAMPLES, - 'Nb of test inputs to attack') - flags.DEFINE_float('learning_rate', LEARNING_RATE, - 'Learning rate for training') - - tf.app.run() diff --git a/cleverhans_tutorials/mnist_tutorial_keras.py b/cleverhans_tutorials/mnist_tutorial_keras.py deleted file mode 100644 index b9f4a01e9..000000000 --- a/cleverhans_tutorials/mnist_tutorial_keras.py +++ /dev/null @@ -1,218 +0,0 @@ -""" -This tutorial shows how to generate adversarial examples using FGSM -and train a model using adversarial training with Keras. -The original paper can be found at: -https://arxiv.org/abs/1412.6572 -""" -# pylint: disable=missing-docstring -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - -import tensorflow as tf -from tensorflow import keras - -from cleverhans.attacks import FastGradientMethod -from cleverhans.compat import flags -from cleverhans.dataset import MNIST -from cleverhans.utils import AccuracyReport -from cleverhans.utils_keras import cnn_model -from cleverhans.utils_keras import KerasModelWrapper - -FLAGS = flags.FLAGS - -NB_EPOCHS = 6 -BATCH_SIZE = 128 -LEARNING_RATE = .001 - - -def mnist_tutorial(train_start=0, train_end=60000, test_start=0, - test_end=10000, nb_epochs=NB_EPOCHS, batch_size=BATCH_SIZE, - learning_rate=LEARNING_RATE, testing=False, - label_smoothing=0.1): - """ - MNIST CleverHans tutorial - :param train_start: index of first training set example - :param train_end: index of last training set example - :param test_start: index of first test set example - :param test_end: index of last test set example - :param nb_epochs: number of epochs to train model - :param batch_size: size of training batches - :param learning_rate: learning rate for training - :param testing: if true, training error is calculated - :param label_smoothing: float, amount of label smoothing for cross entropy - :return: an AccuracyReport object - """ - - # Object used to keep track of (and return) key accuracies - report = AccuracyReport() - - # Set TF random seed to improve reproducibility - tf.set_random_seed(1234) - # Force TensorFlow to use single thread to improve reproducibility - config = tf.ConfigProto(intra_op_parallelism_threads=1, - inter_op_parallelism_threads=1) - - if keras.backend.image_data_format() != 'channels_last': - raise NotImplementedError("this tutorial requires keras to be configured to channels_last format") - - # Create TF session and set as Keras backend session - sess = tf.Session(config=config) - keras.backend.set_session(sess) - - # Get MNIST test data - mnist = MNIST(train_start=train_start, train_end=train_end, - test_start=test_start, test_end=test_end) - x_train, y_train = mnist.get_set('train') - x_test, y_test = mnist.get_set('test') - - # Obtain Image Parameters - img_rows, img_cols, nchannels = x_train.shape[1:4] - nb_classes = y_train.shape[1] - - # Label smoothing - y_train -= label_smoothing * (y_train - 1. / nb_classes) - - # Define Keras model - model = cnn_model(img_rows=img_rows, img_cols=img_cols, - channels=nchannels, nb_filters=64, - nb_classes=nb_classes) - print("Defined Keras model.") - - # To be able to call the model in the custom loss, we need to call it once - # before, see https://github.com/tensorflow/tensorflow/issues/23769 - model(model.input) - - # Initialize the Fast Gradient Sign Method (FGSM) attack object - wrap = KerasModelWrapper(model) - fgsm = FastGradientMethod(wrap, sess=sess) - fgsm_params = {'eps': 0.3, - 'clip_min': 0., - 'clip_max': 1.} - - adv_acc_metric = get_adversarial_acc_metric(model, fgsm, fgsm_params) - model.compile( - optimizer=keras.optimizers.Adam(learning_rate), - loss='categorical_crossentropy', - metrics=['accuracy', adv_acc_metric] - ) - - # Train an MNIST model - model.fit(x_train, y_train, - batch_size=batch_size, - epochs=nb_epochs, - validation_data=(x_test, y_test), - verbose=2) - - # Evaluate the accuracy on legitimate and adversarial test examples - _, acc, adv_acc = model.evaluate(x_test, y_test, - batch_size=batch_size, - verbose=0) - report.clean_train_clean_eval = acc - report.clean_train_adv_eval = adv_acc - print('Test accuracy on legitimate examples: %0.4f' % acc) - print('Test accuracy on adversarial examples: %0.4f\n' % adv_acc) - - # Calculate training error - if testing: - _, train_acc, train_adv_acc = model.evaluate(x_train, y_train, - batch_size=batch_size, - verbose=0) - report.train_clean_train_clean_eval = train_acc - report.train_clean_train_adv_eval = train_adv_acc - - print("Repeating the process, using adversarial training") - # Redefine Keras model - model_2 = cnn_model(img_rows=img_rows, img_cols=img_cols, - channels=nchannels, nb_filters=64, - nb_classes=nb_classes) - model_2(model_2.input) - wrap_2 = KerasModelWrapper(model_2) - fgsm_2 = FastGradientMethod(wrap_2, sess=sess) - - # Use a loss function based on legitimate and adversarial examples - adv_loss_2 = get_adversarial_loss(model_2, fgsm_2, fgsm_params) - adv_acc_metric_2 = get_adversarial_acc_metric(model_2, fgsm_2, fgsm_params) - model_2.compile( - optimizer=keras.optimizers.Adam(learning_rate), - loss=adv_loss_2, - metrics=['accuracy', adv_acc_metric_2] - ) - - # Train an MNIST model - model_2.fit(x_train, y_train, - batch_size=batch_size, - epochs=nb_epochs, - validation_data=(x_test, y_test), - verbose=2) - - # Evaluate the accuracy on legitimate and adversarial test examples - _, acc, adv_acc = model_2.evaluate(x_test, y_test, - batch_size=batch_size, - verbose=0) - report.adv_train_clean_eval = acc - report.adv_train_adv_eval = adv_acc - print('Test accuracy on legitimate examples: %0.4f' % acc) - print('Test accuracy on adversarial examples: %0.4f\n' % adv_acc) - - # Calculate training error - if testing: - _, train_acc, train_adv_acc = model_2.evaluate(x_train, y_train, - batch_size=batch_size, - verbose=0) - report.train_adv_train_clean_eval = train_acc - report.train_adv_train_adv_eval = train_adv_acc - - return report - - -def get_adversarial_acc_metric(model, fgsm, fgsm_params): - def adv_acc(y, _): - # Generate adversarial examples - x_adv = fgsm.generate(model.input, **fgsm_params) - # Consider the attack to be constant - x_adv = tf.stop_gradient(x_adv) - - # Accuracy on the adversarial examples - preds_adv = model(x_adv) - return keras.metrics.categorical_accuracy(y, preds_adv) - - return adv_acc - - -def get_adversarial_loss(model, fgsm, fgsm_params): - def adv_loss(y, preds): - # Cross-entropy on the legitimate examples - cross_ent = keras.losses.categorical_crossentropy(y, preds) - - # Generate adversarial examples - x_adv = fgsm.generate(model.input, **fgsm_params) - # Consider the attack to be constant - x_adv = tf.stop_gradient(x_adv) - - # Cross-entropy on the adversarial examples - preds_adv = model(x_adv) - cross_ent_adv = keras.losses.categorical_crossentropy(y, preds_adv) - - return 0.5 * cross_ent + 0.5 * cross_ent_adv - - return adv_loss - - -def main(argv=None): - from cleverhans_tutorials import check_installation - check_installation(__file__) - - mnist_tutorial(nb_epochs=FLAGS.nb_epochs, - batch_size=FLAGS.batch_size, - learning_rate=FLAGS.learning_rate) - - -if __name__ == '__main__': - flags.DEFINE_integer('nb_epochs', NB_EPOCHS, - 'Number of epochs to train model') - flags.DEFINE_integer('batch_size', BATCH_SIZE, 'Size of training batches') - flags.DEFINE_float('learning_rate', LEARNING_RATE, - 'Learning rate for training') - tf.app.run() diff --git a/cleverhans_tutorials/mnist_tutorial_keras_tf.py b/cleverhans_tutorials/mnist_tutorial_keras_tf.py deleted file mode 100644 index be1de2642..000000000 --- a/cleverhans_tutorials/mnist_tutorial_keras_tf.py +++ /dev/null @@ -1,235 +0,0 @@ -""" -This tutorial shows how to generate adversarial examples using FGSM -and train a model using adversarial training with Keras. -It is very similar to mnist_tutorial_tf.py, which does the same -thing but without a dependence on keras. -The original paper can be found at: -https://arxiv.org/abs/1412.6572 -""" -# pylint: disable=missing-docstring -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - -import os - -import tensorflow as tf -from tensorflow import keras -import numpy as np - -from cleverhans.attacks import FastGradientMethod -from cleverhans.compat import flags -from cleverhans.dataset import MNIST -from cleverhans.loss import CrossEntropy -from cleverhans.train import train -from cleverhans.utils import AccuracyReport -from cleverhans.utils_keras import cnn_model -from cleverhans.utils_keras import KerasModelWrapper -from cleverhans.utils_tf import model_eval - -FLAGS = flags.FLAGS - -NB_EPOCHS = 6 -BATCH_SIZE = 128 -LEARNING_RATE = .001 -TRAIN_DIR = 'train_dir' -FILENAME = 'mnist.ckpt' -LOAD_MODEL = False - - -def mnist_tutorial(train_start=0, train_end=60000, test_start=0, - test_end=10000, nb_epochs=NB_EPOCHS, batch_size=BATCH_SIZE, - learning_rate=LEARNING_RATE, train_dir=TRAIN_DIR, - filename=FILENAME, load_model=LOAD_MODEL, - testing=False, label_smoothing=0.1): - """ - MNIST CleverHans tutorial - :param train_start: index of first training set example - :param train_end: index of last training set example - :param test_start: index of first test set example - :param test_end: index of last test set example - :param nb_epochs: number of epochs to train model - :param batch_size: size of training batches - :param learning_rate: learning rate for training - :param train_dir: Directory storing the saved model - :param filename: Filename to save model under - :param load_model: True for load, False for not load - :param testing: if true, test error is calculated - :param label_smoothing: float, amount of label smoothing for cross entropy - :return: an AccuracyReport object - """ - tf.keras.backend.set_learning_phase(0) - - # Object used to keep track of (and return) key accuracies - report = AccuracyReport() - - # Set TF random seed to improve reproducibility - tf.set_random_seed(1234) - - if keras.backend.image_data_format() != 'channels_last': - raise NotImplementedError("this tutorial requires keras to be configured to channels_last format") - - # Create TF session and set as Keras backend session - sess = tf.Session() - keras.backend.set_session(sess) - - # Get MNIST test data - mnist = MNIST(train_start=train_start, train_end=train_end, - test_start=test_start, test_end=test_end) - x_train, y_train = mnist.get_set('train') - x_test, y_test = mnist.get_set('test') - - # Obtain Image Parameters - img_rows, img_cols, nchannels = x_train.shape[1:4] - nb_classes = y_train.shape[1] - - # Define input TF placeholder - x = tf.placeholder(tf.float32, shape=(None, img_rows, img_cols, - nchannels)) - y = tf.placeholder(tf.float32, shape=(None, nb_classes)) - - # Define TF model graph - model = cnn_model(img_rows=img_rows, img_cols=img_cols, - channels=nchannels, nb_filters=64, - nb_classes=nb_classes) - preds = model(x) - print("Defined TensorFlow model graph.") - - def evaluate(): - # Evaluate the accuracy of the MNIST model on legitimate test examples - eval_params = {'batch_size': batch_size} - acc = model_eval(sess, x, y, preds, x_test, y_test, args=eval_params) - report.clean_train_clean_eval = acc -# assert X_test.shape[0] == test_end - test_start, X_test.shape - print('Test accuracy on legitimate examples: %0.4f' % acc) - - # Train an MNIST model - train_params = { - 'nb_epochs': nb_epochs, - 'batch_size': batch_size, - 'learning_rate': learning_rate, - 'train_dir': train_dir, - 'filename': filename - } - - rng = np.random.RandomState([2017, 8, 30]) - if not os.path.exists(train_dir): - os.mkdir(train_dir) - - ckpt = tf.train.get_checkpoint_state(train_dir) - print(train_dir, ckpt) - ckpt_path = False if ckpt is None else ckpt.model_checkpoint_path - wrap = KerasModelWrapper(model) - - if load_model and ckpt_path: - saver = tf.train.Saver() - print(ckpt_path) - saver.restore(sess, ckpt_path) - print("Model loaded from: {}".format(ckpt_path)) - evaluate() - else: - print("Model was not loaded, training from scratch.") - loss = CrossEntropy(wrap, smoothing=label_smoothing) - train(sess, loss, x_train, y_train, evaluate=evaluate, - args=train_params, rng=rng) - - # Calculate training error - if testing: - eval_params = {'batch_size': batch_size} - acc = model_eval(sess, x, y, preds, x_train, y_train, args=eval_params) - report.train_clean_train_clean_eval = acc - - # Initialize the Fast Gradient Sign Method (FGSM) attack object and graph - fgsm = FastGradientMethod(wrap, sess=sess) - fgsm_params = {'eps': 0.3, - 'clip_min': 0., - 'clip_max': 1.} - adv_x = fgsm.generate(x, **fgsm_params) - # Consider the attack to be constant - adv_x = tf.stop_gradient(adv_x) - preds_adv = model(adv_x) - - # Evaluate the accuracy of the MNIST model on adversarial examples - eval_par = {'batch_size': batch_size} - acc = model_eval(sess, x, y, preds_adv, x_test, y_test, args=eval_par) - print('Test accuracy on adversarial examples: %0.4f\n' % acc) - report.clean_train_adv_eval = acc - - # Calculating train error - if testing: - eval_par = {'batch_size': batch_size} - acc = model_eval(sess, x, y, preds_adv, x_train, - y_train, args=eval_par) - report.train_clean_train_adv_eval = acc - - print("Repeating the process, using adversarial training") - # Redefine TF model graph - model_2 = cnn_model(img_rows=img_rows, img_cols=img_cols, - channels=nchannels, nb_filters=64, - nb_classes=nb_classes) - wrap_2 = KerasModelWrapper(model_2) - preds_2 = model_2(x) - fgsm2 = FastGradientMethod(wrap_2, sess=sess) - - def attack(x): - return fgsm2.generate(x, **fgsm_params) - - preds_2_adv = model_2(attack(x)) - loss_2 = CrossEntropy(wrap_2, smoothing=label_smoothing, attack=attack) - - def evaluate_2(): - # Accuracy of adversarially trained model on legitimate test inputs - eval_params = {'batch_size': batch_size} - accuracy = model_eval(sess, x, y, preds_2, x_test, y_test, - args=eval_params) - print('Test accuracy on legitimate examples: %0.4f' % accuracy) - report.adv_train_clean_eval = accuracy - - # Accuracy of the adversarially trained model on adversarial examples - accuracy = model_eval(sess, x, y, preds_2_adv, x_test, - y_test, args=eval_params) - print('Test accuracy on adversarial examples: %0.4f' % accuracy) - report.adv_train_adv_eval = accuracy - - # Perform and evaluate adversarial training - train(sess, loss_2, x_train, y_train, evaluate=evaluate_2, - args=train_params, rng=rng) - - # Calculate training errors - if testing: - eval_params = {'batch_size': batch_size} - accuracy = model_eval(sess, x, y, preds_2, x_train, y_train, - args=eval_params) - report.train_adv_train_clean_eval = accuracy - accuracy = model_eval(sess, x, y, preds_2_adv, x_train, - y_train, args=eval_params) - report.train_adv_train_adv_eval = accuracy - - return report - - -def main(argv=None): - from cleverhans_tutorials import check_installation - check_installation(__file__) - - mnist_tutorial(nb_epochs=FLAGS.nb_epochs, - batch_size=FLAGS.batch_size, - learning_rate=FLAGS.learning_rate, - train_dir=FLAGS.train_dir, - filename=FLAGS.filename, - load_model=FLAGS.load_model) - - -if __name__ == '__main__': - flags.DEFINE_integer('nb_epochs', NB_EPOCHS, - 'Number of epochs to train model') - flags.DEFINE_integer('batch_size', BATCH_SIZE, 'Size of training batches') - flags.DEFINE_float('learning_rate', LEARNING_RATE, - 'Learning rate for training') - flags.DEFINE_string('train_dir', TRAIN_DIR, - 'Directory where to save model.') - flags.DEFINE_string('filename', FILENAME, 'Checkpoint filename.') - flags.DEFINE_boolean('load_model', LOAD_MODEL, - 'Load saved model or train.') - tf.app.run() diff --git a/cleverhans_tutorials/mnist_tutorial_picklable.py b/cleverhans_tutorials/mnist_tutorial_picklable.py deleted file mode 100644 index 9e368fee1..000000000 --- a/cleverhans_tutorials/mnist_tutorial_picklable.py +++ /dev/null @@ -1,255 +0,0 @@ -""" -This tutorial shows how to use cleverhans.picklable_model -to create models that can be saved for evaluation later. -""" -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - -import logging -import numpy as np -import tensorflow as tf - -from cleverhans.attacks import FastGradientMethod -from cleverhans.compat import flags -from cleverhans.dataset import MNIST -from cleverhans.loss import CrossEntropy -from cleverhans.serial import save -from cleverhans.utils_tf import model_eval, silence -from cleverhans.train import train -from cleverhans.utils import AccuracyReport, set_log_level -from cleverhans_tutorials.tutorial_models import make_basic_picklable_cnn - -silence() - -FLAGS = flags.FLAGS - -NB_EPOCHS = 6 -BATCH_SIZE = 128 -LEARNING_RATE = .001 -NB_FILTERS = 64 -CLEAN_TRAIN = True -BACKPROP_THROUGH_ATTACK = False - - -def mnist_tutorial(train_start=0, train_end=60000, test_start=0, - test_end=10000, nb_epochs=NB_EPOCHS, batch_size=BATCH_SIZE, - learning_rate=LEARNING_RATE, - clean_train=CLEAN_TRAIN, - testing=False, - backprop_through_attack=BACKPROP_THROUGH_ATTACK, - nb_filters=NB_FILTERS, num_threads=None, - label_smoothing=0.1): - """ - MNIST cleverhans tutorial - :param train_start: index of first training set example - :param train_end: index of last training set example - :param test_start: index of first test set example - :param test_end: index of last test set example - :param nb_epochs: number of epochs to train model - :param batch_size: size of training batches - :param learning_rate: learning rate for training - :param clean_train: perform normal training on clean examples only - before performing adversarial training. - :param testing: if true, complete an AccuracyReport for unit tests - to verify that performance is adequate - :param backprop_through_attack: If True, backprop through adversarial - example construction process during - adversarial training. - :param label_smoothing: float, amount of label smoothing for cross entropy - :return: an AccuracyReport object - """ - - # Object used to keep track of (and return) key accuracies - report = AccuracyReport() - - # Set TF random seed to improve reproducibility - tf.set_random_seed(1234) - - # Set logging level to see debug information - set_log_level(logging.DEBUG) - - # Create TF session - if num_threads: - config_args = dict(intra_op_parallelism_threads=1) - else: - config_args = {} - sess = tf.Session(config=tf.ConfigProto(**config_args)) - - # Get MNIST test data - mnist = MNIST(train_start=train_start, train_end=train_end, - test_start=test_start, test_end=test_end) - x_train, y_train = mnist.get_set('train') - x_test, y_test = mnist.get_set('test') - - # Use Image Parameters - img_rows, img_cols, nchannels = x_train.shape[1:4] - nb_classes = y_train.shape[1] - - # Define input TF placeholder - x = tf.placeholder(tf.float32, shape=(None, img_rows, img_cols, - nchannels)) - y = tf.placeholder(tf.float32, shape=(None, nb_classes)) - - # Train an MNIST model - train_params = { - 'nb_epochs': nb_epochs, - 'batch_size': batch_size, - 'learning_rate': learning_rate - } - eval_params = {'batch_size': batch_size} - fgsm_params = { - 'eps': 0.3, - 'clip_min': 0., - 'clip_max': 1. - } - rng = np.random.RandomState([2017, 8, 30]) - - def do_eval(preds, x_set, y_set, report_key, is_adv=None): - """ - Run the evaluation and print the results. - """ - acc = model_eval(sess, x, y, preds, x_set, y_set, args=eval_params) - setattr(report, report_key, acc) - if is_adv is None: - report_text = None - elif is_adv: - report_text = 'adversarial' - else: - report_text = 'legitimate' - if report_text: - print('Test accuracy on %s examples: %0.4f' % (report_text, acc)) - - if clean_train: - model = make_basic_picklable_cnn() - # Tag the model so that when it is saved to disk, future scripts will - # be able to tell what data it was trained on - model.dataset_factory = mnist.get_factory() - preds = model.get_logits(x) - assert len(model.get_params()) > 0 - loss = CrossEntropy(model, smoothing=label_smoothing) - - def evaluate(): - """ - Run evaluation for the naively trained model on clean examples. - """ - do_eval(preds, x_test, y_test, 'clean_train_clean_eval', False) - - train(sess, loss, x_train, y_train, evaluate=evaluate, - args=train_params, rng=rng, var_list=model.get_params()) - - with sess.as_default(): - save("clean_model.joblib", model) - - print("Now that the model has been saved, you can evaluate it in a" - " separate process using `evaluate_pickled_model.py`. " - "You should get exactly the same result for both clean and " - "adversarial accuracy as you get within this program.") - - # Calculate training error - if testing: - do_eval(preds, x_train, y_train, 'train_clean_train_clean_eval') - - # Initialize the Fast Gradient Sign Method (FGSM) attack object and - # graph - fgsm = FastGradientMethod(model, sess=sess) - adv_x = fgsm.generate(x, **fgsm_params) - preds_adv = model.get_logits(adv_x) - - # Evaluate the accuracy of the MNIST model on adversarial examples - do_eval(preds_adv, x_test, y_test, 'clean_train_adv_eval', True) - - # Calculate training error - if testing: - do_eval(preds_adv, x_train, y_train, 'train_clean_train_adv_eval') - - print('Repeating the process, using adversarial training') - - # Create a new model and train it to be robust to FastGradientMethod - model2 = make_basic_picklable_cnn() - # Tag the model so that when it is saved to disk, future scripts will - # be able to tell what data it was trained on - model2.dataset_factory = mnist.get_factory() - fgsm2 = FastGradientMethod(model2, sess=sess) - - def attack(x): - """Return an adversarial example near clean example `x`""" - return fgsm2.generate(x, **fgsm_params) - - loss2 = CrossEntropy(model2, smoothing=label_smoothing, attack=attack) - preds2 = model2.get_logits(x) - adv_x2 = attack(x) - - if not backprop_through_attack: - # For the fgsm attack used in this tutorial, the attack has zero - # gradient so enabling this flag does not change the gradient. - # For some other attacks, enabling this flag increases the cost of - # training, but gives the defender the ability to anticipate how - # the atacker will change their strategy in response to updates to - # the defender's parameters. - adv_x2 = tf.stop_gradient(adv_x2) - preds2_adv = model2.get_logits(adv_x2) - - def evaluate_adv(): - """ - Evaluate the adversarially trained model. - """ - # Accuracy of adversarially trained model on legitimate test inputs - do_eval(preds2, x_test, y_test, 'adv_train_clean_eval', False) - # Accuracy of the adversarially trained model on adversarial examples - do_eval(preds2_adv, x_test, y_test, 'adv_train_adv_eval', True) - - # Perform and evaluate adversarial training - train(sess, loss2, x_train, y_train, evaluate=evaluate_adv, - args=train_params, rng=rng, var_list=model2.get_params()) - - - with sess.as_default(): - save("adv_model.joblib", model2) - print("Now that the model has been saved, you can evaluate it in a " - "separate process using " - "`python evaluate_pickled_model.py adv_model.joblib`. " - "You should get exactly the same result for both clean and " - "adversarial accuracy as you get within this program." - " You can also move beyond the tutorials directory and run the " - " real `compute_accuracy.py` script (make sure cleverhans/scripts " - "is in your PATH) to see that this FGSM-trained " - "model is actually not very robust---it's just a model that trains " - " quickly so the tutorial does not take a long time") - - # Calculate training errors - if testing: - do_eval(preds2, x_train, y_train, 'train_adv_train_clean_eval') - do_eval(preds2_adv, x_train, y_train, 'train_adv_train_adv_eval') - - return report - - -def main(argv=None): - """ - Run the tutorial using command line flags - """ - from cleverhans_tutorials import check_installation - check_installation(__file__) - - mnist_tutorial(nb_epochs=FLAGS.nb_epochs, batch_size=FLAGS.batch_size, - learning_rate=FLAGS.learning_rate, - clean_train=FLAGS.clean_train, - backprop_through_attack=FLAGS.backprop_through_attack, - nb_filters=FLAGS.nb_filters) - - -if __name__ == '__main__': - flags.DEFINE_integer('nb_filters', NB_FILTERS, 'Model size multiplier') - flags.DEFINE_integer('nb_epochs', NB_EPOCHS, - 'Number of epochs to train model') - flags.DEFINE_integer('batch_size', BATCH_SIZE, 'Size of training batches') - flags.DEFINE_float('learning_rate', LEARNING_RATE, - 'Learning rate for training') - flags.DEFINE_bool('clean_train', CLEAN_TRAIN, 'Train on clean examples') - flags.DEFINE_bool('backprop_through_attack', BACKPROP_THROUGH_ATTACK, - ('If True, backprop through adversarial example ' - 'construction process during adversarial training')) - - tf.app.run() diff --git a/cleverhans_tutorials/mnist_tutorial_pytorch.py b/cleverhans_tutorials/mnist_tutorial_pytorch.py deleted file mode 100644 index 1fe601a8c..000000000 --- a/cleverhans_tutorials/mnist_tutorial_pytorch.py +++ /dev/null @@ -1,190 +0,0 @@ -""" -This tutorial shows how to generate adversarial examples using FGSM -and train a model using adversarial training with TensorFlow. -It is very similar to mnist_tutorial_keras_tf.py, which does the same -thing but with a dependence on keras. -The original paper can be found at: -https://arxiv.org/abs/1412.6572 -""" -# pylint: disable=missing-docstring -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - -import warnings -import numpy as np -import tensorflow as tf -import torch -from torch import nn -import torch.nn.functional as F -from torch import optim -from torch.autograd import Variable -from torchvision import datasets, transforms - -from cleverhans.attacks import FastGradientMethod -from cleverhans.compat import flags -from cleverhans.model import CallableModelWrapper -from cleverhans.utils import AccuracyReport -from cleverhans.utils_pytorch import convert_pytorch_model_to_tf - -FLAGS = flags.FLAGS - -NB_EPOCHS = 6 -BATCH_SIZE = 128 -LEARNING_RATE = .001 - - -warnings.warn("convert_pytorch_model_to_tf is deprecated, switch to" - + " dedicated PyTorch support provided by CleverHans v4.") - - -class PytorchMnistModel(nn.Module): - """ Basic MNIST model from github - https://github.com/rickiepark/pytorch-examples/blob/master/mnist.ipynb - """ - - def __init__(self): - super(PytorchMnistModel, self).__init__() - # input is 28x28 - # padding=2 for same padding - self.conv1 = nn.Conv2d(1, 32, 5, padding=2) - # feature map size is 14*14 by pooling - # padding=2 for same padding - self.conv2 = nn.Conv2d(32, 64, 5, padding=2) - # feature map size is 7*7 by pooling - self.fc1 = nn.Linear(64 * 7 * 7, 1024) - self.fc2 = nn.Linear(1024, 10) - - def forward(self, x): - x = F.max_pool2d(F.relu(self.conv1(x)), 2) - x = F.max_pool2d(F.relu(self.conv2(x)), 2) - x = x.view(-1, 64 * 7 * 7) # reshape Variable - x = F.relu(self.fc1(x)) - x = self.fc2(x) - return F.log_softmax(x, dim=-1) - - -def mnist_tutorial(nb_epochs=NB_EPOCHS, batch_size=BATCH_SIZE, - train_end=-1, test_end=-1, learning_rate=LEARNING_RATE): - """ - MNIST cleverhans tutorial - :param nb_epochs: number of epochs to train model - :param batch_size: size of training batches - :param learning_rate: learning rate for training - :return: an AccuracyReport object - """ - # Train a pytorch MNIST model - torch_model = PytorchMnistModel() - if torch.cuda.is_available(): - torch_model = torch_model.cuda() - report = AccuracyReport() - - train_loader = torch.utils.data.DataLoader( - datasets.MNIST('data', train=True, download=True, - transform=transforms.ToTensor()), - batch_size=batch_size, shuffle=True) - test_loader = torch.utils.data.DataLoader( - datasets.MNIST('data', train=False, transform=transforms.ToTensor()), - batch_size=batch_size) - - # Truncate the datasets so that our test run more quickly - train_loader.dataset.train_data = train_loader.dataset.train_data[ - :train_end] - test_loader.dataset.test_data = test_loader.dataset.test_data[:test_end] - - # Train our model - optimizer = optim.Adam(torch_model.parameters(), lr=learning_rate) - train_loss = [] - - total = 0 - correct = 0 - step = 0 - for _epoch in range(nb_epochs): - for xs, ys in train_loader: - xs, ys = Variable(xs), Variable(ys) - if torch.cuda.is_available(): - xs, ys = xs.cuda(), ys.cuda() - optimizer.zero_grad() - preds = torch_model(xs) - loss = F.nll_loss(preds, ys) - loss.backward() # calc gradients - train_loss.append(loss.data.item()) - optimizer.step() # update gradients - - preds_np = preds.cpu().detach().numpy() - correct += (np.argmax(preds_np, axis=1) == ys.cpu().detach().numpy()).sum() - total += train_loader.batch_size - step += 1 - if total % 1000 == 0: - acc = float(correct) / total - print('[%s] Training accuracy: %.2f%%' % (step, acc * 100)) - total = 0 - correct = 0 - - # Evaluate on clean data - total = 0 - correct = 0 - for xs, ys in test_loader: - xs, ys = Variable(xs), Variable(ys) - if torch.cuda.is_available(): - xs, ys = xs.cuda(), ys.cuda() - - preds = torch_model(xs) - preds_np = preds.cpu().detach().numpy() - - correct += (np.argmax(preds_np, axis=1) == ys.cpu().detach().numpy()).sum() - total += len(xs) - - acc = float(correct) / total - report.clean_train_clean_eval = acc - print('[%s] Clean accuracy: %.2f%%' % (step, acc * 100)) - - # We use tf for evaluation on adversarial data - sess = tf.Session() - x_op = tf.placeholder(tf.float32, shape=(None, 1, 28, 28,)) - - # Convert pytorch model to a tf_model and wrap it in cleverhans - tf_model_fn = convert_pytorch_model_to_tf(torch_model) - cleverhans_model = CallableModelWrapper(tf_model_fn, output_layer='logits') - - # Create an FGSM attack - fgsm_op = FastGradientMethod(cleverhans_model, sess=sess) - fgsm_params = {'eps': 0.3, - 'clip_min': 0., - 'clip_max': 1.} - adv_x_op = fgsm_op.generate(x_op, **fgsm_params) - adv_preds_op = tf_model_fn(adv_x_op) - - # Run an evaluation of our model against fgsm - total = 0 - correct = 0 - for xs, ys in test_loader: - adv_preds = sess.run(adv_preds_op, feed_dict={x_op: xs}) - correct += (np.argmax(adv_preds, axis=1) == ys.cpu().detach().numpy()).sum() - total += test_loader.batch_size - - acc = float(correct) / total - print('Adv accuracy: {:.3f}'.format(acc * 100)) - report.clean_train_adv_eval = acc - return report - - -def main(_=None): - from cleverhans_tutorials import check_installation - check_installation(__file__) - - mnist_tutorial(nb_epochs=FLAGS.nb_epochs, - batch_size=FLAGS.batch_size, - learning_rate=FLAGS.learning_rate) - - -if __name__ == '__main__': - flags.DEFINE_integer('nb_epochs', NB_EPOCHS, - 'Number of epochs to train model') - flags.DEFINE_integer('batch_size', BATCH_SIZE, - 'Size of training batches') - flags.DEFINE_float('learning_rate', LEARNING_RATE, - 'Learning rate for training') - - tf.app.run() diff --git a/cleverhans_tutorials/mnist_tutorial_tf.py b/cleverhans_tutorials/mnist_tutorial_tf.py deleted file mode 100644 index d97ae6840..000000000 --- a/cleverhans_tutorials/mnist_tutorial_tf.py +++ /dev/null @@ -1,220 +0,0 @@ -""" -This tutorial shows how to generate adversarial examples using FGSM -and train a model using adversarial training with TensorFlow. -It is very similar to mnist_tutorial_keras_tf.py, which does the same -thing but with a dependence on keras. -The original paper can be found at: -https://arxiv.org/abs/1412.6572 -""" -# pylint: disable=missing-docstring -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - -import logging -import numpy as np -import tensorflow as tf - -from cleverhans.compat import flags -from cleverhans.loss import CrossEntropy -from cleverhans.dataset import MNIST -from cleverhans.utils_tf import model_eval -from cleverhans.train import train -from cleverhans.attacks import FastGradientMethod -from cleverhans.utils import AccuracyReport, set_log_level -from cleverhans.model_zoo.basic_cnn import ModelBasicCNN - -FLAGS = flags.FLAGS - -NB_EPOCHS = 6 -BATCH_SIZE = 128 -LEARNING_RATE = 0.001 -CLEAN_TRAIN = True -BACKPROP_THROUGH_ATTACK = False -NB_FILTERS = 64 - - -def mnist_tutorial(train_start=0, train_end=60000, test_start=0, - test_end=10000, nb_epochs=NB_EPOCHS, batch_size=BATCH_SIZE, - learning_rate=LEARNING_RATE, - clean_train=CLEAN_TRAIN, - testing=False, - backprop_through_attack=BACKPROP_THROUGH_ATTACK, - nb_filters=NB_FILTERS, num_threads=None, - label_smoothing=0.1): - """ - MNIST cleverhans tutorial - :param train_start: index of first training set example - :param train_end: index of last training set example - :param test_start: index of first test set example - :param test_end: index of last test set example - :param nb_epochs: number of epochs to train model - :param batch_size: size of training batches - :param learning_rate: learning rate for training - :param clean_train: perform normal training on clean examples only - before performing adversarial training. - :param testing: if true, complete an AccuracyReport for unit tests - to verify that performance is adequate - :param backprop_through_attack: If True, backprop through adversarial - example construction process during - adversarial training. - :param label_smoothing: float, amount of label smoothing for cross entropy - :return: an AccuracyReport object - """ - - # Object used to keep track of (and return) key accuracies - report = AccuracyReport() - - # Set TF random seed to improve reproducibility - tf.set_random_seed(1234) - - # Set logging level to see debug information - set_log_level(logging.DEBUG) - - # Create TF session - if num_threads: - config_args = dict(intra_op_parallelism_threads=1) - else: - config_args = {} - sess = tf.Session(config=tf.ConfigProto(**config_args)) - - # Get MNIST data - mnist = MNIST(train_start=train_start, train_end=train_end, - test_start=test_start, test_end=test_end) - x_train, y_train = mnist.get_set('train') - x_test, y_test = mnist.get_set('test') - - # Use Image Parameters - img_rows, img_cols, nchannels = x_train.shape[1:4] - nb_classes = y_train.shape[1] - - # Define input TF placeholder - x = tf.placeholder(tf.float32, shape=(None, img_rows, img_cols, - nchannels)) - y = tf.placeholder(tf.float32, shape=(None, nb_classes)) - - # Train an MNIST model - train_params = { - 'nb_epochs': nb_epochs, - 'batch_size': batch_size, - 'learning_rate': learning_rate - } - eval_params = {'batch_size': batch_size} - fgsm_params = { - 'eps': 0.3, - 'clip_min': 0., - 'clip_max': 1. - } - rng = np.random.RandomState([2017, 8, 30]) - - def do_eval(preds, x_set, y_set, report_key, is_adv=None): - acc = model_eval(sess, x, y, preds, x_set, y_set, args=eval_params) - setattr(report, report_key, acc) - if is_adv is None: - report_text = None - elif is_adv: - report_text = 'adversarial' - else: - report_text = 'legitimate' - if report_text: - print('Test accuracy on %s examples: %0.4f' % (report_text, acc)) - - if clean_train: - model = ModelBasicCNN('model1', nb_classes, nb_filters) - preds = model.get_logits(x) - loss = CrossEntropy(model, smoothing=label_smoothing) - - def evaluate(): - do_eval(preds, x_test, y_test, 'clean_train_clean_eval', False) - - train(sess, loss, x_train, y_train, evaluate=evaluate, - args=train_params, rng=rng, var_list=model.get_params()) - - # Calculate training error - if testing: - do_eval(preds, x_train, y_train, 'train_clean_train_clean_eval') - - # Initialize the Fast Gradient Sign Method (FGSM) attack object and - # graph - fgsm = FastGradientMethod(model, sess=sess) - adv_x = fgsm.generate(x, **fgsm_params) - preds_adv = model.get_logits(adv_x) - - # Evaluate the accuracy of the MNIST model on adversarial examples - do_eval(preds_adv, x_test, y_test, 'clean_train_adv_eval', True) - - # Calculate training error - if testing: - do_eval(preds_adv, x_train, y_train, 'train_clean_train_adv_eval') - - print('Repeating the process, using adversarial training') - - # Create a new model and train it to be robust to FastGradientMethod - model2 = ModelBasicCNN('model2', nb_classes, nb_filters) - fgsm2 = FastGradientMethod(model2, sess=sess) - - def attack(x): - return fgsm2.generate(x, **fgsm_params) - - loss2 = CrossEntropy(model2, smoothing=label_smoothing, attack=attack) - preds2 = model2.get_logits(x) - adv_x2 = attack(x) - - if not backprop_through_attack: - # For the fgsm attack used in this tutorial, the attack has zero - # gradient so enabling this flag does not change the gradient. - # For some other attacks, enabling this flag increases the cost of - # training, but gives the defender the ability to anticipate how - # the atacker will change their strategy in response to updates to - # the defender's parameters. - adv_x2 = tf.stop_gradient(adv_x2) - preds2_adv = model2.get_logits(adv_x2) - - def evaluate2(): - # Accuracy of adversarially trained model on legitimate test inputs - do_eval(preds2, x_test, y_test, 'adv_train_clean_eval', False) - # Accuracy of the adversarially trained model on adversarial examples - do_eval(preds2_adv, x_test, y_test, 'adv_train_adv_eval', True) - - # Perform and evaluate adversarial training - train(sess, loss2, x_train, y_train, evaluate=evaluate2, - args=train_params, rng=rng, var_list=model2.get_params()) - - # Calculate training errors - if testing: - do_eval(preds2, x_train, y_train, 'train_adv_train_clean_eval') - do_eval(preds2_adv, x_train, y_train, 'train_adv_train_adv_eval') - - return report - - -def main(argv=None): - """ - Run the tutorial using command line flags. - """ - from cleverhans_tutorials import check_installation - check_installation(__file__) - - mnist_tutorial(nb_epochs=FLAGS.nb_epochs, batch_size=FLAGS.batch_size, - learning_rate=FLAGS.learning_rate, - clean_train=FLAGS.clean_train, - backprop_through_attack=FLAGS.backprop_through_attack, - nb_filters=FLAGS.nb_filters) - - -if __name__ == '__main__': - flags.DEFINE_integer('nb_filters', NB_FILTERS, - 'Model size multiplier') - flags.DEFINE_integer('nb_epochs', NB_EPOCHS, - 'Number of epochs to train model') - flags.DEFINE_integer('batch_size', BATCH_SIZE, - 'Size of training batches') - flags.DEFINE_float('learning_rate', LEARNING_RATE, - 'Learning rate for training') - flags.DEFINE_bool('clean_train', CLEAN_TRAIN, 'Train on clean examples') - flags.DEFINE_bool('backprop_through_attack', BACKPROP_THROUGH_ATTACK, - ('If True, backprop through adversarial example ' - 'construction process during adversarial training')) - - tf.app.run() diff --git a/cleverhans_tutorials/mnist_tutorial_tfe.py b/cleverhans_tutorials/mnist_tutorial_tfe.py deleted file mode 100644 index 805da227b..000000000 --- a/cleverhans_tutorials/mnist_tutorial_tfe.py +++ /dev/null @@ -1,247 +0,0 @@ -""" -This tutorial shows how to generate adversarial examples using FGSM -and train a model using adversarial training with TensorFlow Eager. - -It is similar to mnist_tutorial_tf.py. -mnist_tutorial_tf - dynaminc eager execution. -mnist_tutorial_tf - graph based execution. -The original paper can be found at: -https://arxiv.org/abs/1412.6572 -""" -# pylint: disable=missing-docstring -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - -import logging -import numpy as np -import tensorflow as tf - -from cleverhans.compat import flags -from cleverhans.utils import AccuracyReport -from cleverhans.utils_tfe import train, model_eval -from cleverhans.attacks_tfe import BasicIterativeMethod -from cleverhans.attacks_tfe import FastGradientMethod -from cleverhans.dataset import MNIST -from cleverhans.utils import set_log_level -from cleverhans_tutorials.tutorial_models_tfe import ModelBasicCNNTFE - -if tf.executing_eagerly() is True: - print('TF Eager Activated.') -else: - raise Exception("Error Enabling Eager Execution.") -tfe = tf.contrib.eager - -FLAGS = flags.FLAGS - -NB_EPOCHS = 6 -BATCH_SIZE = 128 -LEARNING_RATE = .001 -NB_FILTERS = 64 - -# Keeps track of implemented attacks. -# Maps attack string taken from bash to attack class -# -- 'fgsm' : FastGradientMethod -# -- 'bim': BasicIterativeMethod - -AVAILABLE_ATTACKS = { - 'fgsm': FastGradientMethod, - 'bim': BasicIterativeMethod -} - - -def attack_selection(attack_string): - """ - Selects the Attack Class using string input. - :param attack_string: adversarial attack name in string format - :return: attack class defined in cleverhans.attacks_eager - """ - - # List of Implemented attacks - attacks_list = AVAILABLE_ATTACKS.keys() - - # Checking for requested attack in list of available attacks. - if attack_string is None: - raise AttributeError("Attack type is not specified, " - "list of available attacks\t".join(attacks_list)) - if attack_string not in attacks_list: - raise AttributeError("Attack not available " - "list of available attacks\t".join(attacks_list)) - # Mapping attack from string to class. - attack_class = AVAILABLE_ATTACKS[attack_string] - return attack_class - - -def mnist_tutorial(train_start=0, train_end=60000, test_start=0, - test_end=10000, nb_epochs=NB_EPOCHS, batch_size=BATCH_SIZE, - learning_rate=LEARNING_RATE, - clean_train=True, - testing=False, - backprop_through_attack=False, - nb_filters=NB_FILTERS, num_threads=None, - attack_string=None): - """ - MNIST cleverhans tutorial - :param train_start: index of first training set example. - :param train_end: index of last training set example. - :param test_start: index of first test set example. - :param test_end: index of last test set example. - :param nb_epochs: number of epochs to train model. - :param batch_size: size of training batches. - :param learning_rate: learning rate for training. - :param clean_train: perform normal training on clean examples only - before performing adversarial training. - :param testing: if true, complete an AccuracyReport for unit tests - to verify that performance is adequate. - :param backprop_through_attack: If True, backprop through adversarial - example construction process during - adversarial training. - :param nb_filters: number of filters in the CNN used for training. - :param num_threads: number of threads used for running the process. - :param attack_string: attack name for crafting adversarial attacks and - adversarial training, in string format. - :return: an AccuracyReport object - """ - - # Object used to keep track of (and return) key accuracies - report = AccuracyReport() - - # Set TF random seed to improve reproducibility - tf.set_random_seed(1234) - - # Set logging level to see debug information - set_log_level(logging.DEBUG) - - # Get MNIST test data - mnist = MNIST(train_start=train_start, train_end=train_end, - test_start=test_start, test_end=test_end) - X_train, Y_train = mnist.get_set('train') - X_test, Y_test = mnist.get_set('test') - - # Use label smoothing - assert Y_train.shape[1] == 10 - label_smooth = .1 - Y_train = Y_train.clip(label_smooth / 9., 1. - label_smooth) - - # Train an MNIST model - train_params = { - 'nb_epochs': nb_epochs, - 'batch_size': batch_size, - 'learning_rate': learning_rate - } - - # Initialize the attack object - attack_class = attack_selection(attack_string) - attack_params = {'eps': 0.3, 'clip_min': 0., - 'clip_max': 1.} - - rng = np.random.RandomState([2018, 6, 18]) - if clean_train: - model = ModelBasicCNNTFE(nb_filters=nb_filters) - - def evaluate_clean(): - """Evaluate the accuracy of the MNIST model on legitimate test - examples - """ - eval_params = {'batch_size': batch_size} - acc = model_eval(model, X_test, Y_test, args=eval_params) - report.clean_train_clean_eval = acc - assert X_test.shape[0] == test_end - test_start, X_test.shape - print('Test accuracy on legitimate examples: %0.4f' % acc) - - train(model, X_train, Y_train, evaluate=evaluate_clean, - args=train_params, rng=rng, var_list=model.get_params()) - - if testing: - # Calculate training error - eval_params = {'batch_size': batch_size} - acc = model_eval(model, X_train, Y_train, args=eval_params) - report.train_clean_train_clean_eval = acc - - # Evaluate the accuracy of the MNIST model on adversarial examples - eval_par = {'batch_size': batch_size} - attack = attack_class(model) - acc = model_eval( - model, X_test, Y_test, args=eval_par, - attack=attack, attack_args=attack_params) - print('Test accuracy on adversarial examples: %0.4f\n' % acc) - report.clean_train_adv_eval = acc - - # Calculate training error - if testing: - eval_par = {'batch_size': batch_size} - acc = model_eval( - model, X_train, Y_train, args=eval_par, - attack=attack, attack_args=attack_params) - print('Train accuracy on adversarial examples: %0.4f\n' % acc) - report.train_clean_train_adv_eval = acc - - attack = None - print("Repeating the process, using adversarial training") - - model_adv_train = ModelBasicCNNTFE(nb_filters=nb_filters) - attack = attack_class(model_adv_train) - - def evaluate_adv(): - # Accuracy of adversarially trained model on legitimate test inputs - eval_params = {'batch_size': batch_size} - accuracy = model_eval( - model_adv_train, X_test, Y_test, - args=eval_params) - print('Test accuracy on legitimate examples: %0.4f' % accuracy) - report.adv_train_clean_eval = accuracy - # Accuracy of the adversarially trained model on adversarial examples - accuracy = model_eval( - model_adv_train, X_test, Y_test, - args=eval_params, attack=attack, - attack_args=attack_params) - print('Test accuracy on adversarial examples: %0.4f' % accuracy) - report.adv_train_adv_eval = accuracy - - # Perform and evaluate adversarial training - train(model_adv_train, X_train, Y_train, evaluate=evaluate_adv, - args=train_params, rng=rng, - var_list=model_adv_train.get_params(), - attack=attack, attack_args=attack_params) - - # Calculate training errors - if testing: - eval_params = {'batch_size': batch_size} - accuracy = model_eval( - model_adv_train, X_train, Y_train, args=eval_params, - attack=None, attack_args=None) - report.train_adv_train_clean_eval = accuracy - accuracy = model_eval( - model_adv_train, X_train, Y_train, args=eval_params, - attack=attack, attack_args=attack_params) - report.train_adv_train_adv_eval = accuracy - return report - - -def main(argv=None): - from cleverhans_tutorials import check_installation - check_installation(__file__) - - mnist_tutorial( - nb_epochs=FLAGS.nb_epochs, batch_size=FLAGS.batch_size, - learning_rate=FLAGS.learning_rate, clean_train=FLAGS.clean_train, - backprop_through_attack=FLAGS.backprop_through_attack, - nb_filters=FLAGS.nb_filters, attack_string=FLAGS.attack) - - -if __name__ == '__main__': - flags.DEFINE_integer('nb_filters', NB_FILTERS, - 'Model size multiplier') - flags.DEFINE_integer('nb_epochs', NB_EPOCHS, - 'Number of epochs to train model') - flags.DEFINE_integer('batch_size', BATCH_SIZE, 'Size of training batches') - flags.DEFINE_float('learning_rate', LEARNING_RATE, - 'Learning rate for training') - flags.DEFINE_bool('clean_train', True, 'Train on clean examples') - flags.DEFINE_bool('backprop_through_attack', False, - ('If True, backprop through adversarial example ' - 'construction process during adversarial training')) - flags.DEFINE_string('attack', 'fgsm', - 'Adversarial attack crafted and used for training') - tf.app.run() diff --git a/cleverhans_tutorials/tutorial_models.py b/cleverhans_tutorials/tutorial_models.py deleted file mode 100644 index 2a960351d..000000000 --- a/cleverhans_tutorials/tutorial_models.py +++ /dev/null @@ -1,28 +0,0 @@ -""" -A pure TensorFlow implementation of a neural network. This can be -used as a drop-in replacement for a Keras model. -""" -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - -from cleverhans.picklable_model import MLP, Conv2D, ReLU, Flatten, Linear -from cleverhans.picklable_model import Softmax - - -def make_basic_picklable_cnn(nb_filters=64, nb_classes=10, - input_shape=(None, 28, 28, 1)): - """The model for the picklable models tutorial. - """ - layers = [Conv2D(nb_filters, (8, 8), (2, 2), "SAME"), - ReLU(), - Conv2D(nb_filters * 2, (6, 6), (2, 2), "VALID"), - ReLU(), - Conv2D(nb_filters * 2, (5, 5), (1, 1), "VALID"), - ReLU(), - Flatten(), - Linear(nb_classes), - Softmax()] - model = MLP(layers, input_shape) - return model diff --git a/cleverhans_tutorials/tutorial_models_tfe.py b/cleverhans_tutorials/tutorial_models_tfe.py deleted file mode 100644 index b79e49f16..000000000 --- a/cleverhans_tutorials/tutorial_models_tfe.py +++ /dev/null @@ -1,115 +0,0 @@ -""" -A TensorFlow Eager implementation of a neural network. -""" -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - -import tensorflow as tf -from cleverhans.model import Model - - -class ModelBasicCNNTFE(Model): - """ - Basic CNN model for tensorflow eager execution. - """ - - def __init__(self, nb_classes=10, - nb_filters=64, dummy_input=tf.zeros((32, 28, 28, 1))): - Model.__init__(self, nb_classes=nb_classes) - - # Parametes - # number of filters, number of classes. - self.nb_filters = nb_filters - self.nb_classes = nb_classes - - # Lists for layers attributes. - # layer names , layers, layer activations - self.layer_names = ['input', 'conv_1', 'conv_2', 'conv_3', 'flatten', - 'logits'] - self.layers = {} - self.layer_acts = {} - - # layer definitions - self.layers['conv_1'] = tf.layers.Conv2D(filters=self.nb_filters, - kernel_size=8, strides=2, - padding='same', - activation=tf.nn.relu) - self.layers['conv_2'] = tf.layers.Conv2D(filters=self.nb_filters * 2, - kernel_size=6, strides=2, - padding='valid', - activation=tf.nn.relu) - self.layers['conv_3'] = tf.layers.Conv2D(filters=self.nb_filters * 2, - kernel_size=5, strides=1, - padding='valid', - activation=tf.nn.relu) - self.layers['flatten'] = tf.layers.Flatten() - self.layers['logits'] = tf.layers.Dense(self.nb_classes, - activation=None) - - # Dummy fprop to activate the network. - self.fprop(dummy_input) - - def fprop(self, x): - """ - Forward propagation throught the network - :return: dictionary with layer names mapping to activation values. - """ - - # Feed forward through the network layers - for layer_name in self.layer_names: - if layer_name == 'input': - prev_layer_act = x - continue - else: - self.layer_acts[layer_name] = self.layers[layer_name]( - prev_layer_act) - prev_layer_act = self.layer_acts[layer_name] - - # Adding softmax values to list of activations. - self.layer_acts['probs'] = tf.nn.softmax( - logits=self.layer_acts['logits']) - return self.layer_acts - - def get_layer_params(self, layer_name): - """ - Provides access to the parameters of the given layer. - Works arounds the non-availability of graph collections in - eager mode. - :layer_name: name of the layer for which parameters are - required, must be one of the string in the - list layer_names - :return: list of parameters corresponding to the given - layer. - """ - assert layer_name in self.layer_names - - out = [] - layer = self.layers[layer_name] - layer_variables = layer.variables - - # For each parameter in a layer. - for param in layer_variables: - if param not in out: - out.append(param) - return out - - def get_params(self): - """ - Provides access to the model's parameters. - Works arounds the non-availability of graph collections in - eager mode. - :return: A list of all Variables defining the model parameters. - """ - assert tf.executing_eagerly() - out = [] - - # Collecting params from each layer. - for layer_name in self.layers: - out += self.get_layer_params(layer_name) - return out - - def get_layer_names(self): - """:return: the list of exposed layers for this model.""" - return self.layer_names diff --git a/cleverhans_v3.1.0/CODE_OF_CONDUCT.rst b/cleverhans_v3.1.0/CODE_OF_CONDUCT.rst new file mode 100644 index 000000000..34a40fd0e --- /dev/null +++ b/cleverhans_v3.1.0/CODE_OF_CONDUCT.rst @@ -0,0 +1,13 @@ +CleverHans is dedicated to providing a harassment-free experience for +everyone, regardless of gender, gender identity and expression, sexual +orientation, disability, physical appearance, body size, age, race, or +religion. We do not tolerate harassment of participants in any form. + +This code of conduct applies to all CleverHans spaces (including Gist +comments) both online and off. Anyone who violates this code of +conduct may be sanctioned or expelled from these spaces at the +discretion of the OpenAI / Pennsylvania State University team. + +We may add additional rules over time, which will be made clearly +available to participants. Participants are responsible for knowing +and abiding by these rules. diff --git a/cleverhans_v3.1.0/CONTRIBUTING.md b/cleverhans_v3.1.0/CONTRIBUTING.md new file mode 100644 index 000000000..df5530c70 --- /dev/null +++ b/cleverhans_v3.1.0/CONTRIBUTING.md @@ -0,0 +1,27 @@ +# Contributing to CleverHans + +First off, thank you for considering contributing to CleverHans. +Following these guidelines helps to communicate that you respect +the time of the researchers and developers managing and developing this open +source project. In return, they should reciprocate that respect in +addressing your issue, assessing changes, and helping you finalize +your pull requests. + +Adding new features, improving documentation, bug triaging, or +writing tutorials are all +examples of helpful contributions. +Furthermore, if you are publishing a new attack or defense, +we strongly encourage you to add it to CleverHans so that others +may evaluate it fairly in their own work. + +To speed the code review process, we ask that: + +* New efforts and features be coordinated +on the mailing list for CleverHans development: [cleverhans-dev@googlegroups.com](https://groups.google.com/forum/#!forum/cleverhans-dev). +* When making code contributions to CleverHans, you should follow the +[`Black`](https://black.readthedocs.io/en/stable/index.html) coding style in your pull requests. +* We do not accept pull requests that add git submodules because of [the + problems that arise when maintaining git + submodules](https://medium.com/@porteneuve/mastering-git-submodules-34c65e940407) + +Bug fixes can be initiated through Github pull requests. diff --git a/cleverhans_v3.1.0/Dockerfile b/cleverhans_v3.1.0/Dockerfile new file mode 100644 index 000000000..548ccd53a --- /dev/null +++ b/cleverhans_v3.1.0/Dockerfile @@ -0,0 +1,8 @@ +FROM ubuntu:14.04 +RUN apt-get update +RUN apt-get install -y python +RUN apt-get install -y python-pip +RUN pip install --upgrade pip +COPY .setup_vm_and_run_tests.sh / +RUN chmod +x /.setup_vm_and_run_tests.sh +CMD ["/.setup_vm_and_run_tests.sh"] diff --git a/cleverhans_v3.1.0/LICENSE b/cleverhans_v3.1.0/LICENSE new file mode 100644 index 000000000..39ee91ec2 --- /dev/null +++ b/cleverhans_v3.1.0/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Google Inc., OpenAI and Pennsylvania State University + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/cleverhans_v3.1.0/README.md b/cleverhans_v3.1.0/README.md new file mode 100644 index 000000000..80fe2201b --- /dev/null +++ b/cleverhans_v3.1.0/README.md @@ -0,0 +1,333 @@ +# CleverHans (latest release: v3.1.0) + +Note: This is the final version of CleverHans v3, which supports TensorFlow 1, Python 3 and Python 2. In our version 4 update, we’re dropping support for TF1 and Python 2, and transitioning to JAX, PyTorch and TF2 on Python 3. + +This version was last tested with Python 3.5 and Tensorflow {1.8, 1.12} on Ubuntu 14.04.5 LTS (Trusty Tahr). You’re welcome to continue using it, but it’s no longer maintained. + +cleverhans logo + +[![Build Status](https://travis-ci.org/tensorflow/cleverhans.svg?branch=master)](https://travis-ci.org/tensorflow/cleverhans) +[![Documentation Status](https://readthedocs.org/projects/cleverhans/badge/?version=latest)](https://cleverhans.readthedocs.io/en/latest/?badge=latest) + +This repository contains the source code for CleverHans, a Python library to +benchmark machine learning systems' vulnerability to +[adversarial examples](http://karpathy.github.io/2015/03/30/breaking-convnets/). +You can learn more about such vulnerabilities on the accompanying [blog](http://cleverhans.io). + +The CleverHans library is under continual development, always welcoming +[contributions](https://github.com/tensorflow/cleverhans#contributing) +of the latest attacks and defenses. +In particular, we always welcome help towards resolving the [issues](https://github.com/tensorflow/cleverhans/issues) +currently open. + +## Major updates coming to CleverHans + +CleverHans will soon support 3 frameworks: JAX, PyTorch, and TF2. The package +itself will focus on its initial principle: reference implementation of attacks +against machine learning models to help with benchmarking models against +adversarial examples. This repository will also contain two folders: +`tutorials/` for scripts demonstrating the features of CleverHans and +`defenses/` for scripts that contain authoritative implementations of defenses +in one of the 3 supported frameworks. The structure of the future repository +will look like this: + +``` +cleverhans/ + jax/ + attacks/ + ... + tests/ + ... + tf2/ + attacks/ + ... + tests/ + ... + torch/ + attacks/ + ... + tests/ + ... +defenses/ + jax/ + ... + tf2/ + ... + torch/ + ... +tutorials/ + jax/ + ... + tf2/ + ... + torch/ + ... +``` + +In the meanwhile, all of these folders can be found in the correspond `future/` +subdirectory (e.g., `cleverhans/future/jax/attacks`, `cleverhans/future/jax/tests` or `defenses/future/jax/`). + +A public milestone has been created to track the changes that are to be +implemented before the library version is incremented to v4. + +## Setting up CleverHans + +### Dependencies + +This library uses [TensorFlow](https://www.tensorflow.org/) to accelerate graph +computations performed by many machine learning models. +Therefore, installing TensorFlow is a pre-requisite. + +You can find instructions +[here](https://www.tensorflow.org/install/). +For better performance, it is also recommended to install TensorFlow +with GPU support (detailed instructions on how to do this are available +in the TensorFlow installation documentation). + +Installing TensorFlow will +take care of all other dependencies like `numpy` and `scipy`. + +### Installation + +Once dependencies have been taken care of, you can install CleverHans using +`pip` or by cloning this Github repository. + +#### `pip` installation + +If you are installing CleverHans using `pip`, run the following command +after installing TensorFlow: + +``` +pip install cleverhans +``` + +This will install the last version uploaded to +[Pypi](https://pypi.org/project/cleverhans). +If you'd instead like to install the bleeding edge version, use: + +``` +pip install git+https://github.com/tensorflow/cleverhans.git#egg=cleverhans +``` + +#### Installation for development + +If you want to make an editable installation of CleverHans so that you can +develop the library and contribute changes back, first fork the repository +on GitHub and then clone your fork into a directory of your choice: + +``` +git clone https://github.com/tensorflow/cleverhans +``` + +You can then install the local package in "editable" mode in order to add it to +your `PYTHONPATH`: + +``` +cd cleverhans +pip install -e . +``` + +### Currently supported setups + +Although CleverHans is likely to work on many other machine configurations, we +currently [test it](https://travis-ci.org/tensorflow/cleverhans) it with Python +3.5 and TensorFlow {1.8, 1.12} on Ubuntu 14.04.5 LTS (Trusty Tahr). +Support for Python 2.7 is deprecated. +CleverHans 3.0.1 supports Python 2.7 and the master branch is likely to +continue to work in Python 2.7 for some time, but we no longer run the tests +in Python 2.7 and we do not plan to fix bugs affecting only Python 2.7 after +2019-07-04. +Support for TensorFlow prior to 1.12 is deprecated. +Backwards compatibility wrappers for these versions may be removed after 2019-07-07, +and we will not fix bugs for those versions after that date. +Support for TensorFlow 1.7 and earlier is already deprecated: we do not fix +bugs for those versions and any remaining wrapper code for those versions +may be removed without further notice. + +## Getting support + +If you have a request for support, please ask a question +on [StackOverflow](https://stackoverflow.com/questions/tagged/cleverhans) +rather than opening an issue in the GitHub tracker. The GitHub +issue tracker should *only* be used to report bugs or make feature requests. + +## Contributing + +Contributions are welcomed! To speed the code review process, we ask that: +* New efforts and features be coordinated +on the mailing list for CleverHans development: [cleverhans-dev@googlegroups.com](https://groups.google.com/forum/#!forum/cleverhans-dev). +* When making code contributions to CleverHans, you follow the +`PEP8 with two spaces` coding style (the same as the one used +by TensorFlow) in your pull requests. +In most cases this can be done by running `autopep8 -i --indent-size 2 ` +on the files you have edited. +You can check your code by running `nosetests cleverhans/devtools/tests/test_format.py` or check an individual file by running `pylint ` from inside the cleverhans repository root directory. +* When making your first pull request, you [sign the Google CLA](https://cla.developers.google.com/clas) +* We do not accept pull requests that add git submodules because of [the + problems that arise when maintaining git + submodules](https://medium.com/@porteneuve/mastering-git-submodules-34c65e940407) + +Bug fixes can be initiated through Github pull requests. + +## Scripts: `scripts` directory + +The `scripts` directory contains command line utilities. +In many cases you can use these to run CleverHans functionality on your +saved models without needing to write any of your own Python code. + +You may want to set your `.bashrc` / `.bash_profile` file to add the +CleverHans `scripts` directory to your `PATH` environment variable +so that these scripts will be conveniently executable from any directory. + +## Tutorials: `cleverhans_tutorials` directory + +To help you get started with the functionalities provided by this library, the +`cleverhans_tutorials/` folder comes with the following tutorials: +* **MNIST with FGSM** ([code](cleverhans_tutorials/mnist_tutorial_tf.py)): this +tutorial covers how to train a MNIST model using TensorFlow, +craft adversarial examples using the [fast gradient sign method](https://arxiv.org/abs/1412.6572), +and make the model more robust to adversarial examples using adversarial training. +* **MNIST with FGSM using Keras** ([code](cleverhans_tutorials/mnist_tutorial_keras_tf.py)): this +tutorial covers how to define a MNIST model with Keras and train it using TensorFlow, +craft adversarial examples using the [fast gradient sign method](https://arxiv.org/abs/1412.6572), +and make the model more robust to adversarial +examples using adversarial training. +* **MNIST with JSMA** ([code](cleverhans_tutorials/mnist_tutorial_jsma.py)): this second +tutorial covers how to define a MNIST model with Keras and train it using TensorFlow and +craft adversarial examples using the [Jacobian-based saliency map approach](https://arxiv.org/abs/1511.07528). +* **MNIST using a black-box attack** ([code](cleverhans_tutorials/mnist_blackbox.py)): +this tutorial implements the black-box +attack described in this [paper](https://arxiv.org/abs/1602.02697). +The adversary train a substitute model: a copy that imitates the black-box +model by observing the labels that the black-box model assigns to inputs chosen +carefully by the adversary. The adversary then uses the substitute +model’s gradients to find adversarial examples that are misclassified by the +black-box model as well. + +NOTE: the tutorials are maintained carefully, in the sense that we use +continuous integration to make sure they continue working. They are not +considered part of the API and they can change at any time without warning. +You should not write 3rd party code that imports the tutorials and expect +that the interface will not break. Only the main library is subject to +our six month interface deprecation warning rule. + +NOTE: please write to cleverhans-dev@googlegroups.com before writing a new +tutorial. Because each new tutorial involves a large amount of duplicated +code relative to the existing tutorials, and because every line of code +requires ongoing testing and maintenance indefinitely, we generally prefer +not to add new tutorials. Each tutorial should showcase an extremely different +way of using the library. Just calling a different attack, model, or dataset +is not enough to justify maintaining a parallel tutorial. + +## Examples : `examples` directory + +The `examples/` folder contains additional scripts to showcase different uses +of the CleverHans library or get you started competing in different adversarial +example contests. We do not offer nearly as much ongoing maintenance or support +for this directory as the rest of the library, and if code in here gets broken +we may just delete it without warning. + +## Reporting benchmarks + +When reporting benchmarks, please: +* Use a versioned release of CleverHans. You can find a list of released versions [here](https://github.com/tensorflow/cleverhans/releases). +* Either use the latest version, or, if comparing to an earlier publication, use the same version as the earlier publication. +* Report which attack method was used. +* Report any configuration variables used to determine the behavior of the attack. + +For example, you might report "We benchmarked the robustness of our method to +adversarial attack using v3.0.1 of CleverHans. On a test set modified by the +`FastGradientMethod` with a max-norm `eps` of 0.3, we obtained a test set accuracy of 71.3%." + +## Citing this work + +If you use CleverHans for academic research, you are highly encouraged +(though not required) to cite the following [paper](https://arxiv.org/abs/1610.00768): + +``` +@article{papernot2018cleverhans, + title={Technical Report on the CleverHans v2.1.0 Adversarial Examples Library}, + author={Nicolas Papernot and Fartash Faghri and Nicholas Carlini and + Ian Goodfellow and Reuben Feinman and Alexey Kurakin and Cihang Xie and + Yash Sharma and Tom Brown and Aurko Roy and Alexander Matyasko and + Vahid Behzadan and Karen Hambardzumyan and Zhishuai Zhang and + Yi-Lin Juang and Zhi Li and Ryan Sheatsley and Abhibhav Garg and + Jonathan Uesato and Willi Gierke and Yinpeng Dong and David Berthelot and + Paul Hendricks and Jonas Rauber and Rujun Long}, + journal={arXiv preprint arXiv:1610.00768}, + year={2018} +} +``` + +## About the name + +The name CleverHans is a reference to a presentation by Bob Sturm titled +“Clever Hans, Clever Algorithms: Are Your Machine Learnings Learning What You +Think?" and the corresponding publication, ["A Simple Method to Determine if a +Music Information Retrieval System is a +'Horse'."](http://ieeexplore.ieee.org/document/6847693/) Clever Hans was a +horse that appeared to have learned to answer arithmetic questions, but had in +fact only learned to read social cues that enabled him to give the correct +answer. In controlled settings where he could not see people's faces or receive +other feedback, he was unable to answer the same questions. The story of Clever +Hans is a metaphor for machine learning systems that may achieve very high +accuracy on a test set drawn from the same distribution as the training data, +but that do not actually understand the underlying task and perform poorly on +other inputs. + +## Authors + +This library is managed and maintained by Ian Goodfellow (Google Brain) and +Nicolas Papernot (Google Brain). + +The following authors contributed 100 lines or more (ordered according to the GitHub contributors page): +* Ian Goodfellow (Google Brain) +* Nicolas Papernot (Google Brain) +* Nicholas Carlini (Google Brain) +* Fartash Faghri (University of Toronto) +* Tzu-Wei Sung (National Taiwan University) +* Alexey Kurakin (Google Brain) +* Reuben Feinman (New York University) +* Shiyu Duan (University of Florida) +* Phani Krishna (Video Analytics Lab) +* David Berthelot (Google Brain) +* Tom Brown (Google Brain) +* Cihang Xie (Johns Hopkins) +* Yash Sharma (The Cooper Union) +* Aashish Kumar (HARMAN X) +* Aurko Roy (Google Brain) +* Alexander Matyasko (Nanyang Technological University) +* Anshuman Suri (University of Virginia) +* Yen-Chen Lin (MIT) +* Vahid Behzadan (Kansas State) +* Jonathan Uesato (DeepMind) +* Florian Tramèr (Stanford University) +* Haojie Yuan (University of Science & Technology of China) +* Zhishuai Zhang (Johns Hopkins) +* Karen Hambardzumyan (YerevaNN) +* Jianbo Chen (UC Berkeley) +* Catherine Olsson (Google Brain) +* Aidan Gomez (University of Oxford) +* Zhi Li (University of Toronto) +* Yi-Lin Juang (NTUEE) +* Pratyush Sahay (formerly HARMAN X) +* Abhibhav Garg (IIT Delhi) +* Aditi Raghunathan (Stanford University) +* Yang Song (Stanford University) +* Riccardo Volpi (Italian Institute of Technology) +* Angus Galloway (University of Guelph) +* Yinpeng Dong (Tsinghua University) +* Willi Gierke (Hasso Plattner Institute) +* Bruno López +* Jonas Rauber (IMPRS) +* Paul Hendricks (NVIDIA) +* Ryan Sheatsley (Pennsylvania State University) +* Rujun Long (0101.AI) +* Bogdan Kulynych (EPFL) +* Erfan Noury (UMBC) +* Robert Wagner (Case Western Reserve University) +* Erh-Chung Chen (National Tsing Hua University) + +## Copyright + +Copyright 2019 - Google Inc., OpenAI and Pennsylvania State University. diff --git a/cleverhans_v3.1.0/assets/logo.png b/cleverhans_v3.1.0/assets/logo.png new file mode 100644 index 000000000..1ac28b28f Binary files /dev/null and b/cleverhans_v3.1.0/assets/logo.png differ diff --git a/cleverhans_v3.1.0/assets/logo.psd b/cleverhans_v3.1.0/assets/logo.psd new file mode 100644 index 000000000..8b66eda62 Binary files /dev/null and b/cleverhans_v3.1.0/assets/logo.psd differ diff --git a/cleverhans_v3.1.0/cleverhans/__init__.py b/cleverhans_v3.1.0/cleverhans/__init__.py new file mode 100644 index 000000000..fc721e327 --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans/__init__.py @@ -0,0 +1,6 @@ +"""The CleverHans adversarial example library""" +from cleverhans.devtools.version import append_dev_version + +# If possible attach a hex digest to the version string to keep track of +# changes in the development branch +__version__ = append_dev_version("3.0.1") diff --git a/cleverhans_v3.1.0/cleverhans/attack_bundling.py b/cleverhans_v3.1.0/cleverhans/attack_bundling.py new file mode 100644 index 000000000..64d5bd5cc --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans/attack_bundling.py @@ -0,0 +1,1319 @@ +""" +Runs multiple attacks against each example. + +References: https://openreview.net/forum?id=H1g0piA9tQ + https://arxiv.org/abs/1811.03685 +""" +# pylint: disable=missing-docstring +import copy +import logging +import time + +import numpy as np +import six +from six.moves import range +import tensorflow as tf + +from cleverhans.attacks import Noise +from cleverhans.attacks import ProjectedGradientDescent +from cleverhans.attacks import SPSA +from cleverhans.evaluation import correctness_and_confidence +from cleverhans.evaluation import batch_eval_multi_worker, run_attack +from cleverhans.model import Model +from cleverhans import serial +from cleverhans.utils import create_logger, deep_copy, safe_zip +from cleverhans.utils_tf import infer_devices +from cleverhans.confidence_report import ConfidenceReport +from cleverhans.confidence_report import ConfidenceReportEntry +from cleverhans.confidence_report import print_stats + +_logger = create_logger("attack_bundling") +_logger.setLevel(logging.INFO) + +devices = infer_devices() +num_devices = len(devices) +DEFAULT_EXAMPLES_PER_DEVICE = 128 +BATCH_SIZE = DEFAULT_EXAMPLES_PER_DEVICE * num_devices +REPORT_TIME_INTERVAL = 60 + +# TODO: lower priority: make it possible to initialize one attack with +# the output of an earlier attack + + +def single_run_max_confidence_recipe( + sess, + model, + x, + y, + nb_classes, + eps, + clip_min, + clip_max, + eps_iter, + nb_iter, + report_path, + batch_size=BATCH_SIZE, + eps_iter_small=None, +): + """A reasonable attack bundling recipe for a max norm threat model and + a defender that uses confidence thresholding. This recipe uses both + uniform noise and randomly-initialized PGD targeted attacks. + + References: + https://openreview.net/forum?id=H1g0piA9tQ + + This version runs each attack (noise, targeted PGD for each class with + nb_iter iterations, target PGD for each class with 25X more iterations) + just once and then stops. See `basic_max_confidence_recipe` for a version + that runs indefinitely. + + :param sess: tf.Session + :param model: cleverhans.model.Model + :param x: numpy array containing clean example inputs to attack + :param y: numpy array containing true labels + :param nb_classes: int, number of classes + :param eps: float, maximum size of perturbation (measured by max norm) + :param eps_iter: float, step size for one version of PGD attacks + (will also run another version with eps_iter_small step size) + :param nb_iter: int, number of iterations for the cheaper PGD attacks + (will also run another version with 25X more iterations) + :param report_path: str, the path that the report will be saved to. + :param batch_size: int, the total number of examples to run simultaneously + :param eps_iter_small: optional, float. + The second version of the PGD attack is run with 25 * nb_iter iterations + and eps_iter_small step size. If eps_iter_small is not specified it is + set to eps_iter / 25. + """ + noise_attack = Noise(model, sess) + pgd_attack = ProjectedGradientDescent(model, sess) + threat_params = {"eps": eps, "clip_min": clip_min, "clip_max": clip_max} + noise_attack_config = AttackConfig(noise_attack, threat_params, "noise") + attack_configs = [noise_attack_config] + pgd_attack_configs = [] + pgd_params = copy.copy(threat_params) + pgd_params["eps_iter"] = eps_iter + pgd_params["nb_iter"] = nb_iter + assert batch_size % num_devices == 0 + dev_batch_size = batch_size // num_devices + ones = tf.ones(dev_batch_size, tf.int32) + expensive_pgd = [] + if eps_iter_small is None: + eps_iter_small = eps_iter / 25.0 + for cls in range(nb_classes): + cls_params = copy.copy(pgd_params) + cls_params["y_target"] = tf.to_float(tf.one_hot(ones * cls, nb_classes)) + cls_attack_config = AttackConfig(pgd_attack, cls_params, "pgd_" + str(cls)) + pgd_attack_configs.append(cls_attack_config) + expensive_params = copy.copy(cls_params) + expensive_params["eps_iter"] = eps_iter_small + expensive_params["nb_iter"] *= 25.0 + expensive_config = AttackConfig( + pgd_attack, expensive_params, "expensive_pgd_" + str(cls) + ) + expensive_pgd.append(expensive_config) + attack_configs = [noise_attack_config] + pgd_attack_configs + expensive_pgd + new_work_goal = {config: 1 for config in attack_configs} + goals = [MaxConfidence(t=1.0, new_work_goal=new_work_goal)] + bundle_attacks( + sess, + model, + x, + y, + attack_configs, + goals, + report_path, + attack_batch_size=batch_size, + eval_batch_size=batch_size, + ) + + +def basic_max_confidence_recipe( + sess, + model, + x, + y, + nb_classes, + eps, + clip_min, + clip_max, + eps_iter, + nb_iter, + report_path, + batch_size=BATCH_SIZE, + eps_iter_small=None, +): + """A reasonable attack bundling recipe for a max norm threat model and + a defender that uses confidence thresholding. + + References: + https://openreview.net/forum?id=H1g0piA9tQ + + This version runs indefinitely, updating the report on disk continuously. + + :param sess: tf.Session + :param model: cleverhans.model.Model + :param x: numpy array containing clean example inputs to attack + :param y: numpy array containing true labels + :param nb_classes: int, number of classes + :param eps: float, maximum size of perturbation (measured by max norm) + :param eps_iter: float, step size for one version of PGD attacks + (will also run another version with eps_iter_small) + :param nb_iter: int, number of iterations for one version of PGD attacks + (will also run another version with 25X more iterations) + :param report_path: str, the path that the report will be saved to. + :batch_size: int, the total number of examples to run simultaneously + :param eps_iter_small: optional, float. + The second version of the PGD attack is run with 25 * nb_iter iterations + and eps_iter_small step size. If eps_iter_small is not specified it is + set to eps_iter / 25. + """ + noise_attack = Noise(model, sess) + pgd_attack = ProjectedGradientDescent(model, sess) + threat_params = {"eps": eps, "clip_min": clip_min, "clip_max": clip_max} + noise_attack_config = AttackConfig(noise_attack, threat_params) + attack_configs = [noise_attack_config] + pgd_attack_configs = [] + pgd_params = copy.copy(threat_params) + pgd_params["eps_iter"] = eps_iter + pgd_params["nb_iter"] = nb_iter + assert batch_size % num_devices == 0 + dev_batch_size = batch_size // num_devices + ones = tf.ones(dev_batch_size, tf.int32) + expensive_pgd = [] + if eps_iter_small is None: + eps_iter_small = eps_iter / 25.0 + for cls in range(nb_classes): + cls_params = copy.copy(pgd_params) + cls_params["y_target"] = tf.to_float(tf.one_hot(ones * cls, nb_classes)) + cls_attack_config = AttackConfig(pgd_attack, cls_params, "pgd_" + str(cls)) + pgd_attack_configs.append(cls_attack_config) + expensive_params = copy.copy(cls_params) + expensive_params["eps_iter"] = eps_iter_small + expensive_params["nb_iter"] *= 25.0 + expensive_config = AttackConfig( + pgd_attack, expensive_params, "expensive_pgd_" + str(cls) + ) + expensive_pgd.append(expensive_config) + attack_configs = [noise_attack_config] + pgd_attack_configs + expensive_pgd + new_work_goal = {config: 5 for config in attack_configs} + pgd_work_goal = {config: 5 for config in pgd_attack_configs} + goals = [ + Misclassify(new_work_goal={noise_attack_config: 50}), + Misclassify(new_work_goal=pgd_work_goal), + MaxConfidence(t=0.5, new_work_goal=new_work_goal), + MaxConfidence(t=0.75, new_work_goal=new_work_goal), + MaxConfidence(t=0.875, new_work_goal=new_work_goal), + MaxConfidence(t=0.9375, new_work_goal=new_work_goal), + MaxConfidence(t=0.96875, new_work_goal=new_work_goal), + MaxConfidence(t=0.984375, new_work_goal=new_work_goal), + MaxConfidence(t=1.0), + ] + bundle_attacks(sess, model, x, y, attack_configs, goals, report_path) + # This runs forever + + +def fixed_max_confidence_recipe( + sess, + model, + x, + y, + nb_classes, + eps, + clip_min, + clip_max, + eps_iter, + nb_iter, + report_path, + batch_size=BATCH_SIZE, + eps_iter_small=None, +): + """A reasonable attack bundling recipe for a max norm threat model and + a defender that uses confidence thresholding. + + References: + https://openreview.net/forum?id=H1g0piA9tQ + + This version runs each attack a fixed number of times. + It is more exhaustive than `single_run_max_confidence_recipe` but because + it uses a fixed budget rather than running indefinitely it is more + appropriate for making fair comparisons between two models. + + :param sess: tf.Session + :param model: cleverhans.model.Model + :param x: numpy array containing clean example inputs to attack + :param y: numpy array containing true labels + :param nb_classes: int, number of classes + :param eps: float, maximum size of perturbation (measured by max norm) + :param eps_iter: float, step size for one version of PGD attacks + (will also run another version with smaller step size) + :param nb_iter: int, number of iterations for one version of PGD attacks + (will also run another version with 25X more iterations) + :param report_path: str, the path that the report will be saved to. + :batch_size: int, the total number of examples to run simultaneously + :param eps_iter_small: float, the step size to use for more expensive version of the attack. + If not specified, usess eps_iter / 25 + """ + noise_attack = Noise(model, sess) + pgd_attack = ProjectedGradientDescent(model, sess) + threat_params = {"eps": eps, "clip_min": clip_min, "clip_max": clip_max} + noise_attack_config = AttackConfig(noise_attack, threat_params) + attack_configs = [noise_attack_config] + pgd_attack_configs = [] + pgd_params = copy.copy(threat_params) + pgd_params["eps_iter"] = eps_iter + pgd_params["nb_iter"] = nb_iter + assert batch_size % num_devices == 0 + dev_batch_size = batch_size // num_devices + ones = tf.ones(dev_batch_size, tf.int32) + if eps_iter_small is None: + eps_iter_small = eps_iter / 25.0 + expensive_pgd = [] + for cls in range(nb_classes): + cls_params = copy.copy(pgd_params) + cls_params["y_target"] = tf.to_float(tf.one_hot(ones * cls, nb_classes)) + cls_attack_config = AttackConfig(pgd_attack, cls_params, "pgd_" + str(cls)) + pgd_attack_configs.append(cls_attack_config) + expensive_params = copy.copy(cls_params) + expensive_params["eps_iter"] = eps_iter_small + expensive_params["nb_iter"] *= 25.0 + expensive_config = AttackConfig( + pgd_attack, expensive_params, "expensive_pgd_" + str(cls) + ) + expensive_pgd.append(expensive_config) + attack_configs = [noise_attack_config] + pgd_attack_configs + expensive_pgd + new_work_goal = {config: 5 for config in attack_configs} + pgd_work_goal = {config: 5 for config in pgd_attack_configs} + # TODO: lower priority: make sure bundler won't waste time running targeted + # attacks on examples where the target class is the true class. + goals = [ + Misclassify(new_work_goal={noise_attack_config: 50}), + Misclassify(new_work_goal=pgd_work_goal), + MaxConfidence(t=0.5, new_work_goal=new_work_goal), + MaxConfidence(t=0.75, new_work_goal=new_work_goal), + MaxConfidence(t=0.875, new_work_goal=new_work_goal), + MaxConfidence(t=0.9375, new_work_goal=new_work_goal), + MaxConfidence(t=0.96875, new_work_goal=new_work_goal), + MaxConfidence(t=0.984375, new_work_goal=new_work_goal), + MaxConfidence(t=1.0, new_work_goal=new_work_goal), + ] + bundle_attacks(sess, model, x, y, attack_configs, goals, report_path) + + +def random_search_max_confidence_recipe( + sess, + model, + x, + y, + eps, + clip_min, + clip_max, + report_path, + batch_size=BATCH_SIZE, + num_noise_points=10000, +): + """Max confidence using random search. + + References: + https://openreview.net/forum?id=H1g0piA9tQ + Describes the max_confidence procedure used for the bundling in this recipe + https://arxiv.org/abs/1802.00420 + Describes using random search with 1e5 or more random points to avoid + gradient masking. + + :param sess: tf.Session + :param model: cleverhans.model.Model + :param x: numpy array containing clean example inputs to attack + :param y: numpy array containing true labels + :param nb_classes: int, number of classes + :param eps: float, maximum size of perturbation (measured by max norm) + :param eps_iter: float, step size for one version of PGD attacks + (will also run another version with 25X smaller step size) + :param nb_iter: int, number of iterations for one version of PGD attacks + (will also run another version with 25X more iterations) + :param report_path: str, the path that the report will be saved to. + :batch_size: int, the total number of examples to run simultaneously + """ + noise_attack = Noise(model, sess) + threat_params = {"eps": eps, "clip_min": clip_min, "clip_max": clip_max} + noise_attack_config = AttackConfig(noise_attack, threat_params) + attack_configs = [noise_attack_config] + assert batch_size % num_devices == 0 + new_work_goal = {noise_attack_config: num_noise_points} + goals = [MaxConfidence(t=1.0, new_work_goal=new_work_goal)] + bundle_attacks(sess, model, x, y, attack_configs, goals, report_path) + + +class AttackConfig(object): + """ + An attack and associated parameters. + :param attack: cleverhans.attacks.Attack + :param params: dict of keyword arguments to pass to attack.generate + :param name: str, name to be returned by __str__ / __repr__ + :param pass_y: bool, whether to pass y to `attack.generate` + """ + + def __init__(self, attack, params=None, name=None, pass_y=False): + self.attack = attack + self.params = params + self.name = name + if params is not None: + assert isinstance(params, dict) + for key in params: + assert isinstance(key, six.string_types), type(key) + self.pass_y = pass_y + + def __str__(self): + if self.name is not None: + return self.name + return "AttackConfig(" + str(self.attack) + ", " + str(self.params) + ")" + + def __repr__(self): + return self.__str__() + + +def bundle_attacks( + sess, + model, + x, + y, + attack_configs, + goals, + report_path, + attack_batch_size=BATCH_SIZE, + eval_batch_size=BATCH_SIZE, +): + """ + Runs attack bundling. + Users of cleverhans may call this function but are more likely to call + one of the recipes above. + + Reference: https://openreview.net/forum?id=H1g0piA9tQ + + :param sess: tf.session.Session + :param model: cleverhans.model.Model + :param x: numpy array containing clean example inputs to attack + :param y: numpy array containing true labels + :param attack_configs: list of AttackConfigs to run + :param goals: list of AttackGoals to run + The bundler works through the goals in order, until each is satisfied. + Some goals may never be satisfied, in which case the bundler will run + forever, updating the report on disk as it goes. + :param report_path: str, the path the report will be saved to + :param attack_batch_size: int, batch size for generating adversarial examples + :param eval_batch_size: int, batch size for evaluating the model on clean / adversarial examples + :returns: + adv_x: The adversarial examples, in the same format as `x` + run_counts: dict mapping each AttackConfig to a numpy array reporting + how many times that AttackConfig was run on each example + """ + assert isinstance(sess, tf.Session) + assert isinstance(model, Model) + assert all( + isinstance(attack_config, AttackConfig) for attack_config in attack_configs + ) + assert all(isinstance(goal, AttackGoal) for goal in goals) + assert isinstance(report_path, six.string_types) + if x.shape[0] != y.shape[0]: + raise ValueError("Number of input examples does not match number of labels") + + # Note: no need to precompile attacks, correctness_and_confidence + # caches them + + run_counts = {} + for attack_config in attack_configs: + run_counts[attack_config] = np.zeros(x.shape[0], dtype=np.int64) + + # TODO: make an interface to pass this in if it has already been computed + # elsewhere + _logger.info("Running on clean data to initialize the report...") + packed = correctness_and_confidence( + sess, model, x, y, batch_size=eval_batch_size, devices=devices + ) + _logger.info("...done") + correctness, confidence = packed + _logger.info("Accuracy: " + str(correctness.mean())) + report = ConfidenceReport() + report["clean"] = ConfidenceReportEntry(correctness, confidence) + + adv_x = x.copy() + + for goal in goals: + bundle_attacks_with_goal( + sess, + model, + x, + y, + adv_x, + attack_configs, + run_counts, + goal, + report, + report_path, + attack_batch_size=attack_batch_size, + eval_batch_size=eval_batch_size, + ) + + # Many users will set `goals` to make this run forever, so the return + # statement is not the primary way to get information out. + return adv_x, run_counts + + +def bundle_attacks_with_goal( + sess, + model, + x, + y, + adv_x, + attack_configs, + run_counts, + goal, + report, + report_path, + attack_batch_size=BATCH_SIZE, + eval_batch_size=BATCH_SIZE, +): + """ + Runs attack bundling, working on one specific AttackGoal. + This function is mostly intended to be called by `bundle_attacks`. + + Reference: https://openreview.net/forum?id=H1g0piA9tQ + + :param sess: tf.session.Session + :param model: cleverhans.model.Model + :param x: numpy array containing clean example inputs to attack + :param y: numpy array containing true labels + :param adv_x: numpy array containing the adversarial examples made so far + by earlier work in the bundling process + :param attack_configs: list of AttackConfigs to run + :param run_counts: dict mapping AttackConfigs to numpy arrays specifying + how many times they have been run on each example + :param goal: AttackGoal to run + :param report: ConfidenceReport + :param report_path: str, the path the report will be saved to + :param attack_batch_size: int, batch size for generating adversarial examples + :param eval_batch_size: int, batch size for evaluating the model on adversarial examples + """ + goal.start(run_counts) + _logger.info("Running criteria for new goal...") + criteria = goal.get_criteria(sess, model, adv_x, y, batch_size=eval_batch_size) + assert "correctness" in criteria + _logger.info("Accuracy: " + str(criteria["correctness"].mean())) + assert "confidence" in criteria + while not goal.is_satisfied(criteria, run_counts): + run_batch_with_goal( + sess, + model, + x, + y, + adv_x, + criteria, + attack_configs, + run_counts, + goal, + report, + report_path, + attack_batch_size=attack_batch_size, + ) + # Save after finishing all goals. + # The incremental saves run on a timer. This save is needed so that the last + # few attacks after the timer don't get discarded + report.completed = True + save(criteria, report, report_path, adv_x) + + +def run_batch_with_goal( + sess, + model, + x, + y, + adv_x_val, + criteria, + attack_configs, + run_counts, + goal, + report, + report_path, + attack_batch_size=BATCH_SIZE, +): + """ + Runs attack bundling on one batch of data. + This function is mostly intended to be called by + `bundle_attacks_with_goal`. + + :param sess: tf.session.Session + :param model: cleverhans.model.Model + :param x: numpy array containing clean example inputs to attack + :param y: numpy array containing true labels + :param adv_x_val: numpy array containing the adversarial examples made so far + by earlier work in the bundling process + :param criteria: dict mapping string names of criteria to numpy arrays with + their values for each example + (Different AttackGoals track different criteria) + :param run_counts: dict mapping AttackConfigs to numpy arrays reporting how + many times they have been run on each example + :param goal: the AttackGoal to work on + :param report: dict, see `bundle_attacks_with_goal` + :param report_path: str, path to save the report to + """ + attack_config = goal.get_attack_config(attack_configs, run_counts, criteria) + idxs = goal.request_examples(attack_config, criteria, run_counts, attack_batch_size) + x_batch = x[idxs] + assert x_batch.shape[0] == attack_batch_size + y_batch = y[idxs] + assert y_batch.shape[0] == attack_batch_size + adv_x_batch = run_attack( + sess, + model, + x_batch, + y_batch, + attack_config.attack, + attack_config.params, + attack_batch_size, + devices, + pass_y=attack_config.pass_y, + ) + criteria_batch = goal.get_criteria( + sess, model, adv_x_batch, y_batch, batch_size=min(attack_batch_size, BATCH_SIZE) + ) + # This can't be parallelized because some orig examples are copied more + # than once into the batch + cur_run_counts = run_counts[attack_config] + for batch_idx, orig_idx in enumerate(idxs): + cur_run_counts[orig_idx] += 1 + should_copy = goal.new_wins(criteria, orig_idx, criteria_batch, batch_idx) + if should_copy: + adv_x_val[orig_idx] = adv_x_batch[batch_idx] + for key in criteria: + criteria[key][orig_idx] = criteria_batch[key][batch_idx] + assert np.allclose(y[orig_idx], y_batch[batch_idx]) + report["bundled"] = ConfidenceReportEntry( + criteria["correctness"], criteria["confidence"] + ) + + should_save = False + new_time = time.time() + if hasattr(report, "time"): + if new_time - report.time > REPORT_TIME_INTERVAL: + should_save = True + else: + should_save = True + if should_save: + report.time = new_time + goal.print_progress(criteria, run_counts) + save(criteria, report, report_path, adv_x_val) + + +def save(criteria, report, report_path, adv_x_val): + """ + Saves the report and adversarial examples. + :param criteria: dict, of the form returned by AttackGoal.get_criteria + :param report: dict containing a confidence report + :param report_path: string, filepath + :param adv_x_val: numpy array containing dataset of adversarial examples + """ + print_stats(criteria["correctness"], criteria["confidence"], "bundled") + + print("Saving to " + report_path) + serial.save(report_path, report) + + assert report_path.endswith(".joblib") + adv_x_path = report_path[: -len(".joblib")] + "_adv.npy" + np.save(adv_x_path, adv_x_val) + + +class AttackGoal(object): + """Specifies goals for attack bundling. + Different bundling recipes can have different priorities. + - When choosing which examples to attack in the next batch, do we want + to focus on examples that are not yet misclassified? Among the + still correctly classified ones, do we want to focus on the ones that + have not been attacked many times yet? Do we want to focus on the ones + that have low loss / low confidence so far? + - After an attack has been run, do we prefer the new adversarial example + or the old one? Is the new one better if it causes higher confidence + in the wrong prediction? If it succeeds with a smaller perturbation? + For different use cases, the answers to these questions is different. + Implement different AttackGoal subclasses to represent the priorities + for your use case. + """ + + def start(self, run_counts): + """ + Called by the bundler when it starts working on the goal. + + :param run_counts: dict mapping AttackConfigs to numpy arrays reporting + how many times they have been run on each example. + """ + + def get_criteria(self, sess, model, advx, y, batch_size=BATCH_SIZE): + """ + Returns a dictionary mapping the name of each criterion to a NumPy + array containing the value of that criterion for each adversarial + example. + Subclasses can add extra criteria by implementing the `extra_criteria` + method. + + :param sess: tf.session.Session + :param model: cleverhans.model.Model + :param adv_x: numpy array containing the adversarial examples made so far + by earlier work in the bundling process + :param y: numpy array containing true labels + :param batch_size: int, batch size + """ + + names, factory = self.extra_criteria() + factory = _CriteriaFactory(model, factory) + results = batch_eval_multi_worker( + sess, factory, [advx, y], batch_size=batch_size, devices=devices + ) + names = ["correctness", "confidence"] + names + out = dict(safe_zip(names, results)) + return out + + def extra_criteria(self): + """ + Subclasses implement this to specify any extra criteria they need to track. + : returns: list of criterion names, _ExtraCriteriaFactory implementing them + """ + return [], None + + def request_examples(self, attack_config, criteria, run_counts, batch_size): + """ + Returns a numpy array of integer example indices to run in the next batch. + """ + raise NotImplementedError( + str(type(self)) + "needs to implement request_examples" + ) + + def is_satisfied(self, criteria, run_counts): + """ + Returns a bool indicating whether the goal has been satisfied. + """ + raise NotImplementedError(str(type(self)) + " needs to implement is_satisfied.") + + def print_progress(self, criteria, run_counts): + """ + Prints a progress message about how much has been done toward the goal. + :param criteria: dict, of the format returned by get_criteria + :param run_counts: dict mapping each AttackConfig to a numpy array + specifying how many times it has been run for each example + """ + print("Working on a " + self.__class__.__name__ + " goal.") + + def get_attack_config(self, attack_configs, run_counts, criteria): + """ + Returns an AttackConfig to run on the next batch. + """ + raise NotImplementedError( + str(type(self)) + " needs to implement get_attack_config" + ) + + def new_wins(self, orig_criteria, orig_idx, new_criteria, new_idx): + """ + Returns a bool indicating whether a new adversarial example is better + than the pre-existing one for the same clean example. + :param orig_criteria: dict mapping names of criteria to their value + for each example in the whole dataset + :param orig_idx: The position of the pre-existing example within the + whole dataset. + :param new_criteria: dict, like orig_criteria, but with values only + on the latest batch of adversarial examples + :param new_idx: The position of the new adversarial example within + the batch + """ + raise NotImplementedError(str(type(self)) + " needs to implement new_wins.") + + +class Misclassify(AttackGoal): + """An AttackGoal that prioritizes misclassifying all examples. + + Times out when each attack has been run the requested number of times. + Some examples may be attacked more than the goal number because we + always run a full batch of attacks and sometimes the batch size is + larger than the number of examples with attacks left to do. + :param new_work_goal: dict + Maps AttackConfigs to ints specifying how many times they should be + run before timing out. + If this dict is not specified, all attacks will be run, repeatedly, + until all examples are misclassified (or forever if some cannot + be changed into misclassified examples). + If this dict is specfied, only attacks in the dict will be run. + :param break_ties: string name of tie-breaking scheme for `new_wins` + When two examples are misclassified, how do we choose between them? + Currently the only scheme is 'wrong_confidence', where we prefer the + one with higher confidence assigned to a single wrong class. + In the future we may support other schemes like smaller perturbation + size, higher loss, etc. + """ + + def __init__(self, new_work_goal=None, break_ties="wrong_confidence"): + super(Misclassify, self).__init__() + self.new_work_goal = new_work_goal + assert all(isinstance(key, AttackConfig) for key in new_work_goal.keys()) + assert all(isinstance(value, int) for value in new_work_goal.values()) + self.rng = np.random.RandomState([2018, 10, 5, 9]) + self.break_ties = break_ties + + def start(self, run_counts): + for key in run_counts: + value = run_counts[key] + assert value.ndim == 1 + _logger.info("Started working on a Misclassify goal") + self.work_before = deep_copy(run_counts) + + def is_satisfied(self, criteria, run_counts): + correctness = criteria["correctness"] + assert correctness.dtype == np.bool + assert correctness.ndim == 1 + if correctness.max() == 0: + _logger.info("Everything is misclassified! Done with Misclassify goal") + return True + if self.new_work_goal is None: + return False + correct_run_counts = self.filter(run_counts, criteria) + correct_work_before = self.filter(self.work_before, criteria) + unfinished = unfinished_attack_configs( + self.new_work_goal, correct_work_before, correct_run_counts + ) + finished = len(unfinished) == 0 + if finished: + _logger.info("Misclassify timed out after running all requested attacks") + else: + pass + # _logger.info("Miclassify goal still has attacks to run") + return finished + + def print_progress(self, criteria, run_counts): + print("Working on a " + self.__class__.__name__ + " goal.") + num_below = criteria["correctness"].sum() + print(str(num_below) + " examples are still correctly classified.") + if self.new_work_goal is None: + print("No work goal: running all attacks indefinitely") + else: + print("Working until all attacks have been run enough times") + filtered_run_counts = self.filter(run_counts, criteria) + filtered_work_before = self.filter(self.work_before, criteria) + for ac in self.new_work_goal: + goal = self.new_work_goal[ac] + new = filtered_run_counts[ac] - filtered_work_before[ac] + if new.size > 0: + min_new = new.min() + if min_new < goal: + num_min = (new == min_new).sum() + print( + "\t" + + str(ac) + + ": goal of " + + str(goal) + + " runs, but " + + str(num_min) + + " examples have been run only " + + str(min_new) + + " times" + ) + + def filter(self, run_counts, criteria): + """ + Return run counts only for examples that are still correctly classified + """ + correctness = criteria["correctness"] + assert correctness.dtype == np.bool + filtered_counts = deep_copy(run_counts) + for key in filtered_counts: + filtered_counts[key] = filtered_counts[key][correctness] + return filtered_counts + + def get_attack_config(self, attack_configs, run_counts, criteria): + if self.new_work_goal is not None: + correct_work_before = self.filter(self.work_before, criteria) + correct_run_counts = self.filter(run_counts, criteria) + attack_configs = unfinished_attack_configs( + self.new_work_goal, correct_work_before, correct_run_counts + ) + attack_config = attack_configs[self.rng.randint(len(attack_configs))] + return attack_config + + def extra_criteria(self): + if self.break_ties == "wrong_confidence": + return ["wrong_confidence"], _WrongConfidenceFactory() + else: + raise NotImplementedError() + + def request_examples(self, attack_config, criteria, run_counts, batch_size): + correctness = criteria["correctness"] + assert correctness.dtype == np.bool + total = correctness.size + total_correct = correctness.sum() + all_idxs = np.arange(total) + run_counts = run_counts[attack_config] + if total_correct > 0: + correct_idxs = all_idxs[correctness] + assert correct_idxs.size == total_correct + run_counts = run_counts[correctness] + pairs = safe_zip(correct_idxs, run_counts) + else: + pairs = safe_zip(all_idxs, run_counts) + # In PY3, pairs is now an iterator. + # To support sorting, we need to make it a list. + pairs = list(pairs) + + def key(pair): + return pair[1] + + pairs.sort(key=key) + idxs = [pair[0] for pair in pairs] + while len(idxs) < batch_size: + needed = batch_size - len(idxs) + idxs = idxs + idxs[:needed] + if len(idxs) > batch_size: + idxs = idxs[:batch_size] + idxs = np.array(idxs) + return idxs + + def new_wins(self, orig_criteria, orig_idx, new_criteria, new_idx): + orig_correct = orig_criteria["correctness"][orig_idx] + new_correct = new_criteria["correctness"][new_idx] + if orig_correct and not new_correct: + return True + if (not orig_correct) and new_correct: + return False + assert orig_correct == new_correct + if self.break_ties == "wrong_confidence": + new = new_criteria["wrong_confidence"][new_idx] + orig = orig_criteria["wrong_confidence"][orig_idx] + return new > orig + else: + raise NotImplementedError(self.break_ties) + + +class MaxConfidence(AttackGoal): + """ + The AttackGoal corresponding the MaxConfidence procedure. + + Reference: https://openreview.net/forum?id=H1g0piA9tQ + + This should be used with a recipe that includes AttackConfigs + that target all of the classes, plus an any additional AttackConfigs + that may help to avoid gradient masking. + + This AttackGoal prioritizes getting all examples above a specified + threshold. (If the threshold is set to 1, then no examples are above + the threshold, so all are attacked equally often). The MaxConfidence + attack procedure against *a single example* is optimal regardless of + threshold, so long as t >= 0.5, but when attacking a population of + examples with finite computation time, knowledge of the threshold is + necessary to determine which examples to prioritize attacking. + + :param t: Prioritize pushing examples above this threshold. + :param new_work_goal: Optional dict mapping AttackConfigs to ints. + The int specifies the number of times to run each AttackConfig on each + below-threshold example before giving up. + If not specified, this goal runs all available attacks and never gives + up. + """ + + def __init__(self, t=1.0, new_work_goal=None): + super(MaxConfidence, self).__init__() + self.t = t + self.new_work_goal = new_work_goal + if new_work_goal is not None: + for key in new_work_goal: + assert isinstance(key, AttackConfig) + assert isinstance(new_work_goal[key], int) + self.rng = np.random.RandomState([2018, 10, 7, 12]) + + def filter(self, run_counts, criteria): + """ + Return the counts for only those examples that are below the threshold + """ + wrong_confidence = criteria["wrong_confidence"] + below_t = wrong_confidence <= self.t + filtered_counts = deep_copy(run_counts) + for key in filtered_counts: + filtered_counts[key] = filtered_counts[key][below_t] + return filtered_counts + + def extra_criteria(self): + return ["wrong_confidence"], _WrongConfidenceFactory() + + def is_satisfied(self, criteria, run_counts): + wrong_confidence = criteria["wrong_confidence"] + if wrong_confidence.min() > self.t: + _logger.info("Everything is above threshold " + str(self.t)) + _logger.info("Done with MaxConfidence goal") + return True + if self.new_work_goal is None: + return False + filtered_run_counts = self.filter(run_counts, criteria) + filtered_work_before = self.filter(self.work_before, criteria) + unfinished = unfinished_attack_configs( + self.new_work_goal, filtered_work_before, filtered_run_counts, log=False + ) + finished = len(unfinished) == 0 + if finished: + _logger.info("MaxConfidence timed out after running all requested attacks") + else: + pass + return finished + + def print_progress(self, criteria, run_counts): + print("Working on a " + self.__class__.__name__ + " goal.") + if self.t == 1.0: + print("Threshold of 1, so just driving up confidence of all examples.") + else: + print("Target threshold of " + str(self.t)) + num_below = (criteria["wrong_confidence"] <= self.t).sum() + print(str(num_below) + " examples are below the target threshold.") + if self.new_work_goal is None: + print("No work goal: running all attacks indefinitely") + else: + print("Working until all attacks have been run enough times") + filtered_run_counts = self.filter(run_counts, criteria) + filtered_work_before = self.filter(self.work_before, criteria) + for ac in self.new_work_goal: + goal = self.new_work_goal[ac] + new = filtered_run_counts[ac] - filtered_work_before[ac] + min_new = new.min() + if min_new < goal: + num_min = (new == min_new).sum() + print( + "\t" + + str(ac) + + ": goal of " + + str(goal) + + " runs, but " + + str(num_min) + + " examples have been run only " + + str(min_new) + + " times" + ) + + def get_attack_config(self, attack_configs, run_counts, criteria): + # TODO: refactor to avoid this duplicated method + if self.new_work_goal is not None: + correct_work_before = self.filter(self.work_before, criteria) + correct_run_counts = self.filter(run_counts, criteria) + attack_configs = unfinished_attack_configs( + self.new_work_goal, correct_work_before, correct_run_counts + ) + attack_config = attack_configs[self.rng.randint(len(attack_configs))] + return attack_config + + def start(self, run_counts): + _logger.info("Started working on a MaxConfidence goal") + _logger.info("Threshold: " + str(self.t)) + if self.new_work_goal is None: + if self.t >= 1.0: + _logger.info("This goal will run forever") + else: + _logger.info( + "This goal will run until all examples have confidence" + + " greater than " + + str(self.t) + + ", which may never" + + " happen." + ) + self.work_before = deep_copy(run_counts) + + def request_examples(self, attack_config, criteria, run_counts, batch_size): + wrong_confidence = criteria["wrong_confidence"] + below_t = wrong_confidence <= self.t + assert below_t.dtype == np.bool + total = below_t.size + total_below = below_t.sum() + all_idxs = np.arange(total) + run_counts = run_counts[attack_config] + if total_below > 0: + correct_idxs = all_idxs[below_t] + assert correct_idxs.size == total_below + run_counts = run_counts[below_t] + pairs = safe_zip(correct_idxs, run_counts) + else: + pairs = safe_zip(all_idxs, run_counts) + + def key(pair): + return pair[1] + + pairs.sort(key=key) + idxs = [pair[0] for pair in pairs] + while len(idxs) < batch_size: + needed = batch_size - len(idxs) + idxs = idxs + idxs[:needed] + if len(idxs) > batch_size: + idxs = idxs[:batch_size] + idxs = np.array(idxs) + return idxs + + def new_wins(self, orig_criteria, orig_idx, new_criteria, new_idx): + new_wrong_confidence = new_criteria["wrong_confidence"][new_idx] + orig_wrong_confidence = orig_criteria["wrong_confidence"][orig_idx] + return new_wrong_confidence > orig_wrong_confidence + + +def unfinished_attack_configs(new_work_goal, work_before, run_counts, log=False): + """ + Returns a list of attack configs that have not yet been run the desired + number of times. + :param new_work_goal: dict mapping attacks to desired number of times to run + :param work_before: dict mapping attacks to number of times they were run + before starting this new goal. Should be prefiltered to include only + examples that don't already meet the primary goal + :param run_counts: dict mapping attacks to total number of times they have + ever been run. Should be prefiltered to include only examples that don't + already meet the primary goal + """ + + assert isinstance(work_before, dict), work_before + + for key in work_before: + value = work_before[key] + assert value.ndim == 1, value.shape + if key in run_counts: + assert run_counts[key].shape == value.shape + + attack_configs = [] + for attack_config in new_work_goal: + done_now = run_counts[attack_config] + if log: + _logger.info(str(attack_config) + " ave run count: " + str(done_now.mean())) + _logger.info(str(attack_config) + " min run count: " + str(done_now.min())) + done_before = work_before[attack_config] + if log: + _logger.info( + str(attack_config) + " mean work before: " + str(done_before.mean()) + ) + # This is the vector for all examples + new = done_now - done_before + # The work is only done when it has been done for every example + new = new.min() + assert isinstance(new, (int, np.int64)), type(new) + new_goal = new_work_goal[attack_config] + assert isinstance(new_goal, int), type(new_goal) + if new < new_goal: + if log: + _logger.info( + str(attack_config) + " has run " + str(new) + " of " + str(new_goal) + ) + attack_configs.append(attack_config) + return attack_configs + + +class _CriteriaFactory(object): + """ + A factory that builds the expression to evaluate all criteria. + """ + + def __init__(self, model, extra_criteria_factory=None): + self.model = model + self.extra_criteria_factory = extra_criteria_factory + properties_to_hash = (model,) + if extra_criteria_factory is not None: + if extra_criteria_factory.properties_to_hash is not None: + extra_properties = extra_criteria_factory.properties_to_hash + properties_to_hash = properties_to_hash + extra_properties + self.properties_to_hash = properties_to_hash + + def __hash__(self): + # Make factory hashable so that no two factories for the + # same model will be used to build redundant tf graphs + return self.properties_to_hash.__hash__() + + def __eq__(self, other): + # Make factory hashable so that no two factories for the + # same model will be used to build redundant tf graphs + if not isinstance(other, _CriteriaFactory): + return False + if type(self.extra_criteria_factory) is not type(other.extra_criteria_factory): + return False + return self.properties_to_hash == other.properties_to_hash + + def __call__(self): + x_batch = self.model.make_input_placeholder() + y_batch = self.model.make_label_placeholder() + + predictions = self.model.get_probs(x_batch) + correct = tf.equal(tf.argmax(y_batch, axis=-1), tf.argmax(predictions, axis=-1)) + max_probs = tf.reduce_max(predictions, axis=1) + + if self.extra_criteria_factory is not None: + extra_criteria = self.extra_criteria_factory( + x_batch, y_batch, predictions, correct, max_probs + ) + else: + extra_criteria = tuple([]) + + return (x_batch, y_batch), (correct, max_probs) + extra_criteria + + +class _ExtraCriteriaFactory(object): + """ + A factory that builds extra criteria + """ + + def __init__(self, properties_to_hash=None): + self.properties_to_hash = properties_to_hash + + def __hash__(self): + # Make factory hashable so that no two factories for the + # same model will be used to build redundant tf graphs + return self.properties_to_hash.__hash__() + + def __eq__(self, other): + # Make factory hashable so that no two factories for the + # same model will be used to build redundant tf graphs + if not isinstance(other, _ExtraCriteriaFactory): + return False + return self.properties_to_hash == other.properties_to_hash + + def __call__(self, x_batch, y_batch, predictions, correct, max_probs): + raise NotImplementedError() + + +class _WrongConfidenceFactory(_ExtraCriteriaFactory): + def __call__(self, x_batch, y_batch, predictions, correct, max_probs): + max_wrong_probs = tf.reduce_max(predictions * (1.0 - y_batch), axis=1) + return tuple([max_wrong_probs]) + + +def bundle_examples_with_goal( + sess, model, adv_x_list, y, goal, report_path, batch_size=BATCH_SIZE +): + """ + A post-processor version of attack bundling, that chooses the strongest + example from the output of multiple earlier bundling strategies. + + :param sess: tf.session.Session + :param model: cleverhans.model.Model + :param adv_x_list: list of numpy arrays + Each entry in the list is the output of a previous bundler; it is an + adversarial version of the whole dataset. + :param y: numpy array containing true labels + :param goal: AttackGoal to use to choose the best version of each adversarial + example + :param report_path: str, the path the report will be saved to + :param batch_size: int, batch size + """ + + # Check the input + num_attacks = len(adv_x_list) + assert num_attacks > 0 + adv_x_0 = adv_x_list[0] + assert isinstance(adv_x_0, np.ndarray) + assert all(adv_x.shape == adv_x_0.shape for adv_x in adv_x_list) + + # Allocate the output + out = np.zeros_like(adv_x_0) + m = adv_x_0.shape[0] + # Initialize with negative sentinel values to make sure everything is + # written to + correctness = -np.ones(m, dtype="int32") + confidence = -np.ones(m, dtype="float32") + + # Gather criteria + criteria = [ + goal.get_criteria(sess, model, adv_x, y, batch_size=batch_size) + for adv_x in adv_x_list + ] + assert all("correctness" in c for c in criteria) + assert all("confidence" in c for c in criteria) + _logger.info("Accuracy on each advx dataset: ") + for c in criteria: + _logger.info("\t" + str(c["correctness"].mean())) + + for example_idx in range(m): + # Index of the best attack for this example + attack_idx = 0 + # Find the winner + for candidate_idx in range(1, num_attacks): + if goal.new_wins( + criteria[attack_idx], example_idx, criteria[candidate_idx], example_idx + ): + attack_idx = candidate_idx + # Copy the winner into the output + out[example_idx] = adv_x_list[attack_idx][example_idx] + correctness[example_idx] = criteria[attack_idx]["correctness"][example_idx] + confidence[example_idx] = criteria[attack_idx]["confidence"][example_idx] + + assert correctness.min() >= 0 + assert correctness.max() <= 1 + assert confidence.min() >= 0.0 + assert confidence.max() <= 1.0 + correctness = correctness.astype("bool") + _logger.info("Accuracy on bundled examples: " + str(correctness.mean())) + + report = ConfidenceReport() + report["bundled"] = ConfidenceReportEntry(correctness, confidence) + serial.save(report_path, report) + assert report_path.endswith(".joblib") + adv_x_path = report_path[: -len(".joblib")] + "_adv_x.npy" + np.save(adv_x_path, out) + + +def spsa_max_confidence_recipe( + sess, + model, + x, + y, + nb_classes, + eps, + clip_min, + clip_max, + nb_iter, + report_path, + spsa_samples=SPSA.DEFAULT_SPSA_SAMPLES, + spsa_iters=SPSA.DEFAULT_SPSA_ITERS, + eval_batch_size=BATCH_SIZE, +): + """Runs the MaxConfidence attack using SPSA as the underlying optimizer. + + Even though this runs only one attack, it must be implemented as a bundler + because SPSA supports only batch_size=1. The cleverhans.attacks.MaxConfidence + attack internally multiplies the batch size by nb_classes, so it can't take + SPSA as a base attacker. Insteader, we must bundle batch_size=1 calls using + cleverhans.attack_bundling.MaxConfidence. + + References: + https://openreview.net/forum?id=H1g0piA9tQ + + :param sess: tf.Session + :param model: cleverhans.model.Model + :param x: numpy array containing clean example inputs to attack + :param y: numpy array containing true labels + :param nb_classes: int, number of classes + :param eps: float, maximum size of perturbation (measured by max norm) + :param nb_iter: int, number of iterations for one version of PGD attacks + (will also run another version with 25X more iterations) + :param report_path: str, the path that the report will be saved to. + :param eval_batch_size: int, batch size for evaluation (as opposed to making attacks) + """ + spsa = SPSA(model, sess) + spsa_params = { + "eps": eps, + "clip_min": clip_min, + "clip_max": clip_max, + "nb_iter": nb_iter, + "spsa_samples": spsa_samples, + "spsa_iters": spsa_iters, + } + attack_configs = [] + dev_batch_size = 1 # The only batch size supported by SPSA + batch_size = num_devices + ones = tf.ones(dev_batch_size, tf.int32) + for cls in range(nb_classes): + cls_params = copy.copy(spsa_params) + cls_params["y_target"] = tf.to_float(tf.one_hot(ones * cls, nb_classes)) + cls_attack_config = AttackConfig(spsa, cls_params, "spsa_" + str(cls)) + attack_configs.append(cls_attack_config) + new_work_goal = {config: 1 for config in attack_configs} + goals = [MaxConfidence(t=1.0, new_work_goal=new_work_goal)] + bundle_attacks( + sess, + model, + x, + y, + attack_configs, + goals, + report_path, + attack_batch_size=batch_size, + eval_batch_size=eval_batch_size, + ) diff --git a/cleverhans/attacks/__init__.py b/cleverhans_v3.1.0/cleverhans/attacks/__init__.py similarity index 88% rename from cleverhans/attacks/__init__.py rename to cleverhans_v3.1.0/cleverhans/attacks/__init__.py index 7fbdc939a..3c6b13741 100644 --- a/cleverhans/attacks/__init__.py +++ b/cleverhans_v3.1.0/cleverhans/attacks/__init__.py @@ -15,7 +15,11 @@ from cleverhans.attacks.deep_fool import DeepFool from cleverhans.attacks.elastic_net_method import ElasticNetMethod from cleverhans.attacks.fast_feature_adversaries import FastFeatureAdversaries -from cleverhans.attacks.fast_gradient_method import FastGradientMethod, fgm, optimize_linear +from cleverhans.attacks.fast_gradient_method import ( + FastGradientMethod, + fgm, + optimize_linear, +) from cleverhans.attacks.lbfgs import LBFGS from cleverhans.attacks.madry_et_al import MadryEtAl from cleverhans.attacks.max_confidence import MaxConfidence @@ -27,7 +31,10 @@ from cleverhans.attacks.spsa import SPSA, projected_optimization from cleverhans.attacks.spatial_transformation_method import SpatialTransformationMethod from cleverhans.attacks.virtual_adversarial_method import VirtualAdversarialMethod, vatm -from cleverhans.attacks.hop_skip_jump_attack import HopSkipJumpAttack, BoundaryAttackPlusPlus +from cleverhans.attacks.hop_skip_jump_attack import ( + HopSkipJumpAttack, + BoundaryAttackPlusPlus, +) from cleverhans.attacks.sparse_l1_descent import SparseL1Descent from cleverhans.model import Model, CallableModelWrapper from cleverhans.model import wrapper_warning, wrapper_warning_logits @@ -38,4 +45,4 @@ from cleverhans import utils_tf _logger = utils.create_logger("cleverhans.attacks") -tf_dtype = tf.as_dtype('float32') +tf_dtype = tf.as_dtype("float32") diff --git a/cleverhans_v3.1.0/cleverhans/attacks/attack.py b/cleverhans_v3.1.0/cleverhans/attacks/attack.py new file mode 100644 index 000000000..e3e09efc3 --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans/attacks/attack.py @@ -0,0 +1,363 @@ +""" +The Attack interface. +""" + +from abc import ABCMeta +import collections +import warnings + +import numpy as np +import tensorflow as tf + +from cleverhans.compat import reduce_max +from cleverhans.model import Model +from cleverhans import utils + +_logger = utils.create_logger("cleverhans.attacks.attack") + + +class Attack(object): + """ + Abstract base class for all attack classes. + """ + + __metaclass__ = ABCMeta + + def __init__(self, model, sess=None, dtypestr="float32", **kwargs): + """ + :param model: An instance of the cleverhans.model.Model class. + :param sess: The (possibly optional) tf.Session to run graphs in. + :param dtypestr: Floating point precision to use (change to float64 + to avoid numerical instabilities). + :param back: (deprecated and will be removed on or after 2019-03-26). + The backend to use. Currently 'tf' is the only option. + """ + if "back" in kwargs: + if kwargs["back"] == "tf": + warnings.warn( + "Argument back to attack constructors is not needed" + " anymore and will be removed on or after 2019-03-26." + " All attacks are implemented using TensorFlow." + ) + else: + raise ValueError( + "Backend argument must be 'tf' and is now deprecated" + "It will be removed on or after 2019-03-26." + ) + + self.tf_dtype = tf.as_dtype(dtypestr) + self.np_dtype = np.dtype(dtypestr) + + if sess is not None and not isinstance(sess, tf.Session): + raise TypeError("sess is not an instance of tf.Session") + + from cleverhans import attacks_tf + + attacks_tf.np_dtype = self.np_dtype + attacks_tf.tf_dtype = self.tf_dtype + + if not isinstance(model, Model): + raise TypeError( + "The model argument should be an instance of" + " the cleverhans.model.Model class." + ) + + # Prepare attributes + self.model = model + self.sess = sess + self.dtypestr = dtypestr + + # We are going to keep track of old graphs and cache them. + self.graphs = {} + + # When calling generate_np, arguments in the following set should be + # fed into the graph, as they are not structural items that require + # generating a new graph. + # This dict should map names of arguments to the types they should + # have. + # (Usually, the target class will be a feedable keyword argument.) + self.feedable_kwargs = tuple() + + # When calling generate_np, arguments in the following set should NOT + # be fed into the graph, as they ARE structural items that require + # generating a new graph. + # This list should contain the names of the structural arguments. + self.structural_kwargs = [] + + def generate(self, x, **kwargs): + """ + Generate the attack's symbolic graph for adversarial examples. This + method should be overriden in any child class that implements an + attack that is expressable symbolically. Otherwise, it will wrap the + numerical implementation as a symbolic operator. + + :param x: The model's symbolic inputs. + :param **kwargs: optional parameters used by child classes. + Each child class defines additional parameters as needed. + Child classes that use the following concepts should use the following + names: + clip_min: minimum feature value + clip_max: maximum feature value + eps: size of norm constraint on adversarial perturbation + ord: order of norm constraint + nb_iter: number of iterations + eps_iter: size of norm constraint on iteration + y_target: if specified, the attack is targeted. + y: Do not specify if y_target is specified. + If specified, the attack is untargeted, aims to make the output + class not be y. + If neither y_target nor y is specified, y is inferred by having + the model classify the input. + For other concepts, it's generally a good idea to read other classes + and check for name consistency. + :return: A symbolic representation of the adversarial examples. + """ + + error = "Sub-classes must implement generate." + raise NotImplementedError(error) + # Include an unused return so pylint understands the method signature + return x + + def construct_graph(self, fixed, feedable, x_val, hash_key): + """ + Construct the graph required to run the attack through generate_np. + + :param fixed: Structural elements that require defining a new graph. + :param feedable: Arguments that can be fed to the same graph when + they take different values. + :param x_val: symbolic adversarial example + :param hash_key: the key used to store this graph in our cache + """ + # try our very best to create a TF placeholder for each of the + # feedable keyword arguments, and check the types are one of + # the allowed types + class_name = str(self.__class__).split(".")[-1][:-2] + _logger.info("Constructing new graph for attack " + class_name) + + # remove the None arguments, they are just left blank + for k in list(feedable.keys()): + if feedable[k] is None: + del feedable[k] + + # process all of the rest and create placeholders for them + new_kwargs = dict(x for x in fixed.items()) + for name, value in feedable.items(): + given_type = value.dtype + if isinstance(value, np.ndarray): + if value.ndim == 0: + # This is pretty clearly not a batch of data + new_kwargs[name] = tf.placeholder(given_type, shape=[], name=name) + else: + # Assume that this is a batch of data, make the first axis variable + # in size + new_shape = [None] + list(value.shape[1:]) + new_kwargs[name] = tf.placeholder(given_type, new_shape, name=name) + elif isinstance(value, utils.known_number_types): + new_kwargs[name] = tf.placeholder(given_type, shape=[], name=name) + else: + raise ValueError( + "Could not identify type of argument " + name + ": " + str(value) + ) + + # x is a special placeholder we always want to have + x_shape = [None] + list(x_val.shape)[1:] + x = tf.placeholder(self.tf_dtype, shape=x_shape) + + # now we generate the graph that we want + x_adv = self.generate(x, **new_kwargs) + + self.graphs[hash_key] = (x, new_kwargs, x_adv) + + if len(self.graphs) >= 10: + warnings.warn( + "Calling generate_np() with multiple different " + "structural parameters is inefficient and should" + " be avoided. Calling generate() is preferred." + ) + + def generate_np(self, x_val, **kwargs): + """ + Generate adversarial examples and return them as a NumPy array. + Sub-classes *should not* implement this method unless they must + perform special handling of arguments. + + :param x_val: A NumPy array with the original inputs. + :param **kwargs: optional parameters used by child classes. + :return: A NumPy array holding the adversarial examples. + """ + + if self.sess is None: + raise ValueError("Cannot use `generate_np` when no `sess` was" " provided") + + packed = self.construct_variables(kwargs) + fixed, feedable, _, hash_key = packed + + if hash_key not in self.graphs: + self.construct_graph(fixed, feedable, x_val, hash_key) + else: + # remove the None arguments, they are just left blank + for k in list(feedable.keys()): + if feedable[k] is None: + del feedable[k] + + x, new_kwargs, x_adv = self.graphs[hash_key] + + feed_dict = {x: x_val} + + for name in feedable: + feed_dict[new_kwargs[name]] = feedable[name] + + return self.sess.run(x_adv, feed_dict) + + def construct_variables(self, kwargs): + """ + Construct the inputs to the attack graph to be used by generate_np. + + :param kwargs: Keyword arguments to generate_np. + :return: + Structural arguments + Feedable arguments + Output of `arg_type` describing feedable arguments + A unique key + """ + if isinstance(self.feedable_kwargs, dict): + warnings.warn( + "Using a dict for `feedable_kwargs is deprecated." + "Switch to using a tuple." + "It is not longer necessary to specify the types " + "of the arguments---we build a different graph " + "for each received type." + "Using a dict may become an error on or after " + "2019-04-18." + ) + feedable_names = tuple(sorted(self.feedable_kwargs.keys())) + else: + feedable_names = self.feedable_kwargs + if not isinstance(feedable_names, tuple): + raise TypeError( + "Attack.feedable_kwargs should be a tuple, but " + "for subclass " + + str(type(self)) + + " it is " + + str(self.feedable_kwargs) + + " of type " + + str(type(self.feedable_kwargs)) + ) + + # the set of arguments that are structural properties of the attack + # if these arguments are different, we must construct a new graph + fixed = dict((k, v) for k, v in kwargs.items() if k in self.structural_kwargs) + + # the set of arguments that are passed as placeholders to the graph + # on each call, and can change without constructing a new graph + feedable = {k: v for k, v in kwargs.items() if k in feedable_names} + for k in feedable: + if isinstance(feedable[k], (float, int)): + feedable[k] = np.array(feedable[k]) + + for key in kwargs: + if key not in fixed and key not in feedable: + raise ValueError(str(type(self)) + ": Undeclared argument: " + key) + + feed_arg_type = arg_type(feedable_names, feedable) + + if not all(isinstance(value, collections.Hashable) for value in fixed.values()): + # we have received a fixed value that isn't hashable + # this means we can't cache this graph for later use, + # and it will have to be discarded later + hash_key = None + else: + # create a unique key for this set of fixed paramaters + hash_key = tuple(sorted(fixed.items())) + tuple([feed_arg_type]) + + return fixed, feedable, feed_arg_type, hash_key + + def get_or_guess_labels(self, x, kwargs): + """ + Get the label to use in generating an adversarial example for x. + The kwargs are fed directly from the kwargs of the attack. + If 'y' is in kwargs, then assume it's an untargeted attack and + use that as the label. + If 'y_target' is in kwargs and is not none, then assume it's a + targeted attack and use that as the label. + Otherwise, use the model's prediction as the label and perform an + untargeted attack. + """ + if "y" in kwargs and "y_target" in kwargs: + raise ValueError("Can not set both 'y' and 'y_target'.") + elif "y" in kwargs: + labels = kwargs["y"] + elif "y_target" in kwargs and kwargs["y_target"] is not None: + labels = kwargs["y_target"] + else: + preds = self.model.get_probs(x) + preds_max = reduce_max(preds, 1, keepdims=True) + original_predictions = tf.to_float(tf.equal(preds, preds_max)) + labels = tf.stop_gradient(original_predictions) + del preds + if isinstance(labels, np.ndarray): + nb_classes = labels.shape[1] + else: + nb_classes = labels.get_shape().as_list()[1] + return labels, nb_classes + + def parse_params(self, params=None): + """ + Take in a dictionary of parameters and applies attack-specific checks + before saving them as attributes. + + :param params: a dictionary of attack-specific parameters + :return: True when parsing was successful + """ + + if params is not None: + warnings.warn( + "`params` is unused and will be removed " " on or after 2019-04-26." + ) + return True + + +def arg_type(arg_names, kwargs): + """ + Returns a hashable summary of the types of arg_names within kwargs. + :param arg_names: tuple containing names of relevant arguments + :param kwargs: dict mapping string argument names to values. + These must be values for which we can create a tf placeholder. + Currently supported: numpy darray or something that can ducktype it + returns: + API contract is to return a hashable object describing all + structural consequences of argument values that can otherwise + be fed into a graph of fixed structure. + Currently this is implemented as a tuple of tuples that track: + - whether each argument was passed + - whether each argument was passed and not None + - the dtype of each argument + Callers shouldn't rely on the exact structure of this object, + just its hashability and one-to-one mapping between graph structures. + """ + assert isinstance(arg_names, tuple) + passed = tuple(name in kwargs for name in arg_names) + passed_and_not_none = [] + for name in arg_names: + if name in kwargs: + passed_and_not_none.append(kwargs[name] is not None) + else: + passed_and_not_none.append(False) + passed_and_not_none = tuple(passed_and_not_none) + dtypes = [] + for name in arg_names: + if name not in kwargs: + dtypes.append(None) + continue + value = kwargs[name] + if value is None: + dtypes.append(None) + continue + assert hasattr(value, "dtype"), type(value) + dtype = value.dtype + if not isinstance(dtype, np.dtype): + dtype = dtype.as_np_dtype + assert isinstance(dtype, np.dtype) + dtypes.append(dtype) + dtypes = tuple(dtypes) + return (passed, passed_and_not_none, dtypes) diff --git a/cleverhans_v3.1.0/cleverhans/attacks/basic_iterative_method.py b/cleverhans_v3.1.0/cleverhans/attacks/basic_iterative_method.py new file mode 100644 index 000000000..145dd0980 --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans/attacks/basic_iterative_method.py @@ -0,0 +1,16 @@ +""" +The BasicIterativeMethod attack. +""" + +from cleverhans.attacks.projected_gradient_descent import ProjectedGradientDescent + + +class BasicIterativeMethod(ProjectedGradientDescent): + """ + The BasicIterativeMethod attack. + """ + + def __init__(self, model, sess=None, dtypestr="float32", **kwargs): + super(BasicIterativeMethod, self).__init__( + model, sess=sess, dtypestr=dtypestr, default_rand_init=False, **kwargs + ) diff --git a/cleverhans_v3.1.0/cleverhans/attacks/carlini_wagner_l2.py b/cleverhans_v3.1.0/cleverhans/attacks/carlini_wagner_l2.py new file mode 100644 index 000000000..e010a046f --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans/attacks/carlini_wagner_l2.py @@ -0,0 +1,460 @@ +"""The CarliniWagnerL2 attack +""" +# pylint: disable=missing-docstring +import logging + +import numpy as np +import tensorflow as tf + +from cleverhans.attacks.attack import Attack +from cleverhans.compat import reduce_sum, reduce_max +from cleverhans.model import CallableModelWrapper, Model, wrapper_warning_logits +from cleverhans import utils + +np_dtype = np.dtype("float32") +tf_dtype = tf.as_dtype("float32") + +_logger = utils.create_logger("cleverhans.attacks.carlini_wagner_l2") +_logger.setLevel(logging.INFO) + + +class CarliniWagnerL2(Attack): + """ + This attack was originally proposed by Carlini and Wagner. It is an + iterative attack that finds adversarial examples on many defenses that + are robust to other attacks. + Paper link: https://arxiv.org/abs/1608.04644 + + At a high level, this attack is an iterative attack using Adam and + a specially-chosen loss function to find adversarial examples with + lower distortion than other attacks. This comes at the cost of speed, + as this attack is often much slower than others. + + :param model: cleverhans.model.Model + :param sess: tf.Session + :param dtypestr: dtype of the data + :param kwargs: passed through to super constructor + """ + + def __init__(self, model, sess, dtypestr="float32", **kwargs): + """ + Note: the model parameter should be an instance of the + cleverhans.model.Model abstraction provided by CleverHans. + """ + if not isinstance(model, Model): + wrapper_warning_logits() + model = CallableModelWrapper(model, "logits") + + super(CarliniWagnerL2, self).__init__(model, sess, dtypestr, **kwargs) + + self.feedable_kwargs = ("y", "y_target") + + self.structural_kwargs = [ + "batch_size", + "confidence", + "targeted", + "learning_rate", + "binary_search_steps", + "max_iterations", + "abort_early", + "initial_const", + "clip_min", + "clip_max", + ] + + def generate(self, x, **kwargs): + """ + Return a tensor that constructs adversarial examples for the given + input. Generate uses tf.py_func in order to operate over tensors. + + :param x: A tensor with the inputs. + :param kwargs: See `parse_params` + """ + assert ( + self.sess is not None + ), "Cannot use `generate` when no `sess` was provided" + self.parse_params(**kwargs) + + labels, nb_classes = self.get_or_guess_labels(x, kwargs) + + attack = CWL2( + self.sess, + self.model, + self.batch_size, + self.confidence, + "y_target" in kwargs, + self.learning_rate, + self.binary_search_steps, + self.max_iterations, + self.abort_early, + self.initial_const, + self.clip_min, + self.clip_max, + nb_classes, + x.get_shape().as_list()[1:], + ) + + def cw_wrap(x_val, y_val): + return np.array(attack.attack(x_val, y_val), dtype=self.np_dtype) + + wrap = tf.py_func(cw_wrap, [x, labels], self.tf_dtype) + wrap.set_shape(x.get_shape()) + + return wrap + + def parse_params( + self, + y=None, + y_target=None, + batch_size=1, + confidence=0, + learning_rate=5e-3, + binary_search_steps=5, + max_iterations=1000, + abort_early=True, + initial_const=1e-2, + clip_min=0, + clip_max=1, + ): + """ + :param y: (optional) A tensor with the true labels for an untargeted + attack. If None (and y_target is None) then use the + original labels the classifier assigns. + :param y_target: (optional) A tensor with the target labels for a + targeted attack. + :param confidence: Confidence of adversarial examples: higher produces + examples with larger l2 distortion, but more + strongly classified as adversarial. + :param batch_size: Number of attacks to run simultaneously. + :param learning_rate: The learning rate for the attack algorithm. + Smaller values produce better results but are + slower to converge. + :param binary_search_steps: The number of times we perform binary + search to find the optimal tradeoff- + constant between norm of the purturbation + and confidence of the classification. + :param max_iterations: The maximum number of iterations. Setting this + to a larger value will produce lower distortion + results. Using only a few iterations requires + a larger learning rate, and will produce larger + distortion results. + :param abort_early: If true, allows early aborts if gradient descent + is unable to make progress (i.e., gets stuck in + a local minimum). + :param initial_const: The initial tradeoff-constant to use to tune the + relative importance of size of the perturbation + and confidence of classification. + If binary_search_steps is large, the initial + constant is not important. A smaller value of + this constant gives lower distortion results. + :param clip_min: (optional float) Minimum input component value + :param clip_max: (optional float) Maximum input component value + """ + + # ignore the y and y_target argument + self.batch_size = batch_size + self.confidence = confidence + self.learning_rate = learning_rate + self.binary_search_steps = binary_search_steps + self.max_iterations = max_iterations + self.abort_early = abort_early + self.initial_const = initial_const + self.clip_min = clip_min + self.clip_max = clip_max + + +def ZERO(): + return np.asarray(0.0, dtype=np_dtype) + + +class CWL2(object): + def __init__( + self, + sess, + model, + batch_size, + confidence, + targeted, + learning_rate, + binary_search_steps, + max_iterations, + abort_early, + initial_const, + clip_min, + clip_max, + num_labels, + shape, + ): + """ + Return a tensor that constructs adversarial examples for the given + input. Generate uses tf.py_func in order to operate over tensors. + + :param sess: a TF session. + :param model: a cleverhans.model.Model object. + :param batch_size: Number of attacks to run simultaneously. + :param confidence: Confidence of adversarial examples: higher produces + examples with larger l2 distortion, but more + strongly classified as adversarial. + :param targeted: boolean controlling the behavior of the adversarial + examples produced. If set to False, they will be + misclassified in any wrong class. If set to True, + they will be misclassified in a chosen target class. + :param learning_rate: The learning rate for the attack algorithm. + Smaller values produce better results but are + slower to converge. + :param binary_search_steps: The number of times we perform binary + search to find the optimal tradeoff- + constant between norm of the purturbation + and confidence of the classification. + :param max_iterations: The maximum number of iterations. Setting this + to a larger value will produce lower distortion + results. Using only a few iterations requires + a larger learning rate, and will produce larger + distortion results. + :param abort_early: If true, allows early aborts if gradient descent + is unable to make progress (i.e., gets stuck in + a local minimum). + :param initial_const: The initial tradeoff-constant to use to tune the + relative importance of size of the pururbation + and confidence of classification. + If binary_search_steps is large, the initial + constant is not important. A smaller value of + this constant gives lower distortion results. + :param clip_min: (optional float) Minimum input component value. + :param clip_max: (optional float) Maximum input component value. + :param num_labels: the number of classes in the model's output. + :param shape: the shape of the model's input tensor. + """ + + self.sess = sess + self.TARGETED = targeted + self.LEARNING_RATE = learning_rate + self.MAX_ITERATIONS = max_iterations + self.BINARY_SEARCH_STEPS = binary_search_steps + self.ABORT_EARLY = abort_early + self.CONFIDENCE = confidence + self.initial_const = initial_const + self.batch_size = batch_size + self.clip_min = clip_min + self.clip_max = clip_max + self.model = model + + self.repeat = binary_search_steps >= 10 + + self.shape = shape = tuple([batch_size] + list(shape)) + + # the variable we're going to optimize over + modifier = tf.Variable(np.zeros(shape, dtype=np_dtype)) + + # these are variables to be more efficient in sending data to tf + self.timg = tf.Variable(np.zeros(shape), dtype=tf_dtype, name="timg") + self.tlab = tf.Variable( + np.zeros((batch_size, num_labels)), dtype=tf_dtype, name="tlab" + ) + self.const = tf.Variable(np.zeros(batch_size), dtype=tf_dtype, name="const") + + # and here's what we use to assign them + self.assign_timg = tf.placeholder(tf_dtype, shape, name="assign_timg") + self.assign_tlab = tf.placeholder( + tf_dtype, (batch_size, num_labels), name="assign_tlab" + ) + self.assign_const = tf.placeholder(tf_dtype, [batch_size], name="assign_const") + + # the resulting instance, tanh'd to keep bounded from clip_min + # to clip_max + self.newimg = (tf.tanh(modifier + self.timg) + 1) / 2 + self.newimg = self.newimg * (clip_max - clip_min) + clip_min + + # prediction BEFORE-SOFTMAX of the model + self.output = model.get_logits(self.newimg) + + # distance to the input data + self.other = (tf.tanh(self.timg) + 1) / 2 * (clip_max - clip_min) + clip_min + self.l2dist = reduce_sum( + tf.square(self.newimg - self.other), list(range(1, len(shape))) + ) + + # compute the probability of the label class versus the maximum other + real = reduce_sum((self.tlab) * self.output, 1) + other = reduce_max((1 - self.tlab) * self.output - self.tlab * 10000, 1) + + if self.TARGETED: + # if targeted, optimize for making the other class most likely + loss1 = tf.maximum(ZERO(), other - real + self.CONFIDENCE) + else: + # if untargeted, optimize for making this class least likely. + loss1 = tf.maximum(ZERO(), real - other + self.CONFIDENCE) + + # sum up the losses + self.loss2 = reduce_sum(self.l2dist) + self.loss1 = reduce_sum(self.const * loss1) + self.loss = self.loss1 + self.loss2 + + # Setup the adam optimizer and keep track of variables we're creating + start_vars = set(x.name for x in tf.global_variables()) + optimizer = tf.train.AdamOptimizer(self.LEARNING_RATE) + self.train = optimizer.minimize(self.loss, var_list=[modifier]) + end_vars = tf.global_variables() + new_vars = [x for x in end_vars if x.name not in start_vars] + + # these are the variables to initialize when we run + self.setup = [] + self.setup.append(self.timg.assign(self.assign_timg)) + self.setup.append(self.tlab.assign(self.assign_tlab)) + self.setup.append(self.const.assign(self.assign_const)) + + self.init = tf.variables_initializer(var_list=[modifier] + new_vars) + + def attack(self, imgs, targets): + """ + Perform the L_2 attack on the given instance for the given targets. + + If self.targeted is true, then the targets represents the target labels + If self.targeted is false, then targets are the original class labels + """ + + r = [] + for i in range(0, len(imgs), self.batch_size): + _logger.debug(("Running CWL2 attack on instance %s of %s", i, len(imgs))) + r.extend( + self.attack_batch( + imgs[i : i + self.batch_size], targets[i : i + self.batch_size] + ) + ) + return np.array(r) + + def attack_batch(self, imgs, labs): + """ + Run the attack on a batch of instance and labels. + """ + + def compare(x, y): + if not isinstance(x, (float, int, np.int64)): + x = np.copy(x) + if self.TARGETED: + x[y] -= self.CONFIDENCE + else: + x[y] += self.CONFIDENCE + x = np.argmax(x) + if self.TARGETED: + return x == y + else: + return x != y + + batch_size = self.batch_size + + oimgs = np.clip(imgs, self.clip_min, self.clip_max) + + # re-scale instances to be within range [0, 1] + imgs = (imgs - self.clip_min) / (self.clip_max - self.clip_min) + imgs = np.clip(imgs, 0, 1) + # now convert to [-1, 1] + imgs = (imgs * 2) - 1 + # convert to tanh-space + imgs = np.arctanh(imgs * 0.999999) + + # set the lower and upper bounds accordingly + lower_bound = np.zeros(batch_size) + CONST = np.ones(batch_size) * self.initial_const + upper_bound = np.ones(batch_size) * 1e10 + + # placeholders for the best l2, score, and instance attack found so far + o_bestl2 = [1e10] * batch_size + o_bestscore = [-1] * batch_size + o_bestattack = np.copy(oimgs) + + for outer_step in range(self.BINARY_SEARCH_STEPS): + # completely reset adam's internal state. + self.sess.run(self.init) + batch = imgs[:batch_size] + batchlab = labs[:batch_size] + + bestl2 = [1e10] * batch_size + bestscore = [-1] * batch_size + _logger.debug( + " Binary search step %s of %s", outer_step, self.BINARY_SEARCH_STEPS + ) + + # The last iteration (if we run many steps) repeat the search once. + if self.repeat and outer_step == self.BINARY_SEARCH_STEPS - 1: + CONST = upper_bound + + # set the variables so that we don't have to send them over again + self.sess.run( + self.setup, + { + self.assign_timg: batch, + self.assign_tlab: batchlab, + self.assign_const: CONST, + }, + ) + + prev = 1e6 + for iteration in range(self.MAX_ITERATIONS): + # perform the attack + _, l, l2s, scores, nimg = self.sess.run( + [self.train, self.loss, self.l2dist, self.output, self.newimg] + ) + + if iteration % ((self.MAX_ITERATIONS // 10) or 1) == 0: + _logger.debug( + ( + " Iteration {} of {}: loss={:.3g} " + + "l2={:.3g} f={:.3g}" + ).format( + iteration, + self.MAX_ITERATIONS, + l, + np.mean(l2s), + np.mean(scores), + ) + ) + + # check if we should abort search if we're getting nowhere. + if ( + self.ABORT_EARLY + and iteration % ((self.MAX_ITERATIONS // 10) or 1) == 0 + ): + if l > prev * 0.9999: + msg = " Failed to make progress; stop early" + _logger.debug(msg) + break + prev = l + + # adjust the best result found so far + for e, (l2, sc, ii) in enumerate(zip(l2s, scores, nimg)): + lab = np.argmax(batchlab[e]) + if l2 < bestl2[e] and compare(sc, lab): + bestl2[e] = l2 + bestscore[e] = np.argmax(sc) + if l2 < o_bestl2[e] and compare(sc, lab): + o_bestl2[e] = l2 + o_bestscore[e] = np.argmax(sc) + o_bestattack[e] = ii + + # adjust the constant as needed + for e in range(batch_size): + if compare(bestscore[e], np.argmax(batchlab[e])) and bestscore[e] != -1: + # success, divide const by two + upper_bound[e] = min(upper_bound[e], CONST[e]) + if upper_bound[e] < 1e9: + CONST[e] = (lower_bound[e] + upper_bound[e]) / 2 + else: + # failure, either multiply by 10 if no solution found yet + # or do binary search with the known upper bound + lower_bound[e] = max(lower_bound[e], CONST[e]) + if upper_bound[e] < 1e9: + CONST[e] = (lower_bound[e] + upper_bound[e]) / 2 + else: + CONST[e] *= 10 + _logger.debug( + " Successfully generated adversarial examples " + + "on {} of {} instances.".format(sum(upper_bound < 1e9), batch_size) + ) + o_bestl2 = np.array(o_bestl2) + mean = np.mean(np.sqrt(o_bestl2[o_bestl2 < 1e9])) + _logger.debug(" Mean successful distortion: {:.4g}".format(mean)) + + # return the best solution found + o_bestl2 = np.array(o_bestl2) + return o_bestattack diff --git a/cleverhans_v3.1.0/cleverhans/attacks/deep_fool.py b/cleverhans_v3.1.0/cleverhans/attacks/deep_fool.py new file mode 100644 index 000000000..427f26227 --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans/attacks/deep_fool.py @@ -0,0 +1,278 @@ +"""The DeepFool attack + +""" +import copy +import logging +import warnings + +import numpy as np +import tensorflow as tf + +from cleverhans.attacks.attack import Attack +from cleverhans.model import Model, wrapper_warning_logits, CallableModelWrapper +from cleverhans import utils +from cleverhans import utils_tf + +np_dtype = np.dtype("float32") + +_logger = utils.create_logger("cleverhans.attacks.deep_fool") +_logger.setLevel(logging.INFO) + + +class DeepFool(Attack): + """ + DeepFool is an untargeted & iterative attack which is based on an + iterative linearization of the classifier. The implementation here + is w.r.t. the L2 norm. + Paper link: "https://arxiv.org/pdf/1511.04599.pdf" + + :param model: cleverhans.model.Model + :param sess: tf.Session + :param dtypestr: dtype of the data + :param kwargs: passed through to super constructor + """ + + def __init__(self, model, sess, dtypestr="float32", **kwargs): + """ + Create a DeepFool instance. + """ + if not isinstance(model, Model): + wrapper_warning_logits() + model = CallableModelWrapper(model, "logits") + + super(DeepFool, self).__init__(model, sess, dtypestr, **kwargs) + + self.structural_kwargs = [ + "overshoot", + "max_iter", + "clip_max", + "clip_min", + "nb_candidate", + ] + + def generate(self, x, **kwargs): + """ + Generate symbolic graph for adversarial examples and return. + + :param x: The model's symbolic inputs. + :param kwargs: See `parse_params` + """ + assert ( + self.sess is not None + ), "Cannot use `generate` when no `sess` was provided" + from cleverhans.utils_tf import jacobian_graph + + # Parse and save attack-specific parameters + assert self.parse_params(**kwargs) + + # Define graph wrt to this input placeholder + logits = self.model.get_logits(x) + self.nb_classes = logits.get_shape().as_list()[-1] + assert ( + self.nb_candidate <= self.nb_classes + ), "nb_candidate should not be greater than nb_classes" + preds = tf.reshape( + tf.nn.top_k(logits, k=self.nb_candidate)[0], [-1, self.nb_candidate] + ) + # grads will be the shape [batch_size, nb_candidate, image_size] + grads = tf.stack(jacobian_graph(preds, x, self.nb_candidate), axis=1) + + # Define graph + def deepfool_wrap(x_val): + """deepfool function for py_func""" + return deepfool_batch( + self.sess, + x, + preds, + logits, + grads, + x_val, + self.nb_candidate, + self.overshoot, + self.max_iter, + self.clip_min, + self.clip_max, + self.nb_classes, + ) + + wrap = tf.py_func(deepfool_wrap, [x], self.tf_dtype) + wrap.set_shape(x.get_shape()) + return wrap + + def parse_params( + self, + nb_candidate=10, + overshoot=0.02, + max_iter=50, + clip_min=0.0, + clip_max=1.0, + **kwargs + ): + """ + :param nb_candidate: The number of classes to test against, i.e., + deepfool only consider nb_candidate classes when + attacking(thus accelerate speed). The nb_candidate + classes are chosen according to the prediction + confidence during implementation. + :param overshoot: A termination criterion to prevent vanishing updates + :param max_iter: Maximum number of iteration for deepfool + :param clip_min: Minimum component value for clipping + :param clip_max: Maximum component value for clipping + """ + self.nb_candidate = nb_candidate + self.overshoot = overshoot + self.max_iter = max_iter + self.clip_min = clip_min + self.clip_max = clip_max + if len(kwargs.keys()) > 0: + warnings.warn( + "kwargs is unused and will be removed on or after " "2019-04-26." + ) + + return True + + +def deepfool_batch( + sess, + x, + pred, + logits, + grads, + X, + nb_candidate, + overshoot, + max_iter, + clip_min, + clip_max, + nb_classes, + feed=None, +): + """ + Applies DeepFool to a batch of inputs + :param sess: TF session + :param x: The input placeholder + :param pred: The model's sorted symbolic output of logits, only the top + nb_candidate classes are contained + :param logits: The model's unnormalized output tensor (the input to + the softmax layer) + :param grads: Symbolic gradients of the top nb_candidate classes, procuded + from gradient_graph + :param X: Numpy array with sample inputs + :param nb_candidate: The number of classes to test against, i.e., + deepfool only consider nb_candidate classes when + attacking(thus accelerate speed). The nb_candidate + classes are chosen according to the prediction + confidence during implementation. + :param overshoot: A termination criterion to prevent vanishing updates + :param max_iter: Maximum number of iteration for DeepFool + :param clip_min: Minimum value for components of the example returned + :param clip_max: Maximum value for components of the example returned + :param nb_classes: Number of model output classes + :return: Adversarial examples + """ + X_adv = deepfool_attack( + sess, + x, + pred, + logits, + grads, + X, + nb_candidate, + overshoot, + max_iter, + clip_min, + clip_max, + feed=feed, + ) + + return np.asarray(X_adv, dtype=np_dtype) + + +def deepfool_attack( + sess, + x, + predictions, + logits, + grads, + sample, + nb_candidate, + overshoot, + max_iter, + clip_min, + clip_max, + feed=None, +): + """ + TensorFlow implementation of DeepFool. + Paper link: see https://arxiv.org/pdf/1511.04599.pdf + :param sess: TF session + :param x: The input placeholder + :param predictions: The model's sorted symbolic output of logits, only the + top nb_candidate classes are contained + :param logits: The model's unnormalized output tensor (the input to + the softmax layer) + :param grads: Symbolic gradients of the top nb_candidate classes, procuded + from gradient_graph + :param sample: Numpy array with sample input + :param nb_candidate: The number of classes to test against, i.e., + deepfool only consider nb_candidate classes when + attacking(thus accelerate speed). The nb_candidate + classes are chosen according to the prediction + confidence during implementation. + :param overshoot: A termination criterion to prevent vanishing updates + :param max_iter: Maximum number of iteration for DeepFool + :param clip_min: Minimum value for components of the example returned + :param clip_max: Maximum value for components of the example returned + :return: Adversarial examples + """ + adv_x = copy.copy(sample) + # Initialize the loop variables + iteration = 0 + current = utils_tf.model_argmax(sess, x, logits, adv_x, feed=feed) + if current.shape == (): + current = np.array([current]) + w = np.squeeze(np.zeros(sample.shape[1:])) # same shape as original image + r_tot = np.zeros(sample.shape) + original = current # use original label as the reference + + _logger.debug("Starting DeepFool attack up to %s iterations", max_iter) + # Repeat this main loop until we have achieved misclassification + while np.any(current == original) and iteration < max_iter: + + if iteration % 5 == 0 and iteration > 0: + _logger.info("Attack result at iteration %s is %s", iteration, current) + gradients = sess.run(grads, feed_dict={x: adv_x}) + predictions_val = sess.run(predictions, feed_dict={x: adv_x}) + for idx in range(sample.shape[0]): + pert = np.inf + if current[idx] != original[idx]: + continue + for k in range(1, nb_candidate): + w_k = gradients[idx, k, ...] - gradients[idx, 0, ...] + f_k = predictions_val[idx, k] - predictions_val[idx, 0] + # adding value 0.00001 to prevent f_k = 0 + pert_k = (abs(f_k) + 0.00001) / np.linalg.norm(w_k.flatten()) + if pert_k < pert: + pert = pert_k + w = w_k + r_i = pert * w / np.linalg.norm(w) + r_tot[idx, ...] = r_tot[idx, ...] + r_i + + adv_x = np.clip(r_tot + sample, clip_min, clip_max) + current = utils_tf.model_argmax(sess, x, logits, adv_x, feed=feed) + if current.shape == (): + current = np.array([current]) + # Update loop variables + iteration = iteration + 1 + + # need more revision, including info like how many succeed + _logger.info("Attack result at iteration %s is %s", iteration, current) + _logger.info( + "%s out of %s become adversarial examples at iteration %s", + sum(current != original), + sample.shape[0], + iteration, + ) + # need to clip this image into the given range + adv_x = np.clip((1 + overshoot) * r_tot + sample, clip_min, clip_max) + return adv_x diff --git a/cleverhans_v3.1.0/cleverhans/attacks/elastic_net_method.py b/cleverhans_v3.1.0/cleverhans/attacks/elastic_net_method.py new file mode 100644 index 000000000..e0945ed5b --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans/attacks/elastic_net_method.py @@ -0,0 +1,581 @@ +"""The ElasticNetMethod attack. +""" +# pylint: disable=missing-docstring +import logging + +import numpy as np +import tensorflow as tf + +from cleverhans.attacks.attack import Attack +from cleverhans.compat import reduce_sum, reduce_max +from cleverhans.model import Model, CallableModelWrapper, wrapper_warning_logits +from cleverhans import utils + +np_dtype = np.dtype("float32") +tf_dtype = tf.as_dtype("float32") + +_logger = utils.create_logger("cleverhans.attacks.elastic_net_method") +_logger.setLevel(logging.INFO) + + +def ZERO(): + return np.asarray(0.0, dtype=np_dtype) + + +class ElasticNetMethod(Attack): + """ + This attack features L1-oriented adversarial examples and includes + the C&W L2 attack as a special case (when beta is set to 0). + Adversarial examples attain similar performance to those + generated by the C&W L2 attack in the white-box case, + and more importantly, have improved transferability properties + and complement adversarial training. + Paper link: https://arxiv.org/abs/1709.04114 + + :param model: cleverhans.model.Model + :param sess: tf.Session + :param dtypestr: dtype of the data + :param kwargs: passed through to super constructor + """ + + def __init__(self, model, sess, dtypestr="float32", **kwargs): + """ + Note: the model parameter should be an instance of the + cleverhans.model.Model abstraction provided by CleverHans. + """ + if not isinstance(model, Model): + wrapper_warning_logits() + model = CallableModelWrapper(model, "logits") + + super(ElasticNetMethod, self).__init__(model, sess, dtypestr, **kwargs) + + self.feedable_kwargs = ("y", "y_target") + + self.structural_kwargs = [ + "beta", + "decision_rule", + "batch_size", + "confidence", + "targeted", + "learning_rate", + "binary_search_steps", + "max_iterations", + "abort_early", + "initial_const", + "clip_min", + "clip_max", + ] + + def generate(self, x, **kwargs): + """ + Return a tensor that constructs adversarial examples for the given + input. Generate uses tf.py_func in order to operate over tensors. + + :param x: (required) A tensor with the inputs. + :param kwargs: See `parse_params` + """ + assert ( + self.sess is not None + ), "Cannot use `generate` when no `sess` was provided" + self.parse_params(**kwargs) + + labels, nb_classes = self.get_or_guess_labels(x, kwargs) + + attack = EAD( + self.sess, + self.model, + self.beta, + self.decision_rule, + self.batch_size, + self.confidence, + "y_target" in kwargs, + self.learning_rate, + self.binary_search_steps, + self.max_iterations, + self.abort_early, + self.initial_const, + self.clip_min, + self.clip_max, + nb_classes, + x.get_shape().as_list()[1:], + ) + + def ead_wrap(x_val, y_val): + return np.array(attack.attack(x_val, y_val), dtype=self.np_dtype) + + wrap = tf.py_func(ead_wrap, [x, labels], self.tf_dtype) + wrap.set_shape(x.get_shape()) + + return wrap + + def parse_params( + self, + y=None, + y_target=None, + beta=1e-2, + decision_rule="EN", + batch_size=1, + confidence=0, + learning_rate=1e-2, + binary_search_steps=9, + max_iterations=1000, + abort_early=False, + initial_const=1e-3, + clip_min=0, + clip_max=1, + ): + """ + :param y: (optional) A tensor with the true labels for an untargeted + attack. If None (and y_target is None) then use the + original labels the classifier assigns. + :param y_target: (optional) A tensor with the target labels for a + targeted attack. + :param beta: Trades off L2 distortion with L1 distortion: higher + produces examples with lower L1 distortion, at the + cost of higher L2 (and typically Linf) distortion + :param decision_rule: EN or L1. Select final adversarial example from + all successful examples based on the least + elastic-net or L1 distortion criterion. + :param confidence: Confidence of adversarial examples: higher produces + examples with larger l2 distortion, but more + strongly classified as adversarial. + :param batch_size: Number of attacks to run simultaneously. + :param learning_rate: The learning rate for the attack algorithm. + Smaller values produce better results but are + slower to converge. + :param binary_search_steps: The number of times we perform binary + search to find the optimal tradeoff- + constant between norm of the perturbation + and confidence of the classification. Set + 'initial_const' to a large value and fix + this param to 1 for speed. + :param max_iterations: The maximum number of iterations. Setting this + to a larger value will produce lower distortion + results. Using only a few iterations requires + a larger learning rate, and will produce larger + distortion results. + :param abort_early: If true, allows early abort when the total + loss starts to increase (greatly speeds up attack, + but hurts performance, particularly on ImageNet) + :param initial_const: The initial tradeoff-constant to use to tune the + relative importance of size of the perturbation + and confidence of classification. + If binary_search_steps is large, the initial + constant is not important. A smaller value of + this constant gives lower distortion results. + For computational efficiency, fix + binary_search_steps to 1 and set this param + to a large value. + :param clip_min: (optional float) Minimum input component value + :param clip_max: (optional float) Maximum input component value + """ + + # ignore the y and y_target argument + self.beta = beta + self.decision_rule = decision_rule + self.batch_size = batch_size + self.confidence = confidence + self.learning_rate = learning_rate + self.binary_search_steps = binary_search_steps + self.max_iterations = max_iterations + self.abort_early = abort_early + self.initial_const = initial_const + self.clip_min = clip_min + self.clip_max = clip_max + + +class EAD(object): + def __init__( + self, + sess, + model, + beta, + decision_rule, + batch_size, + confidence, + targeted, + learning_rate, + binary_search_steps, + max_iterations, + abort_early, + initial_const, + clip_min, + clip_max, + num_labels, + shape, + ): + """ + EAD Attack + + Return a tensor that constructs adversarial examples for the given + input. Generate uses tf.py_func in order to operate over tensors. + + :param sess: a TF session. + :param model: a cleverhans.model.Model object. + :param beta: Trades off L2 distortion with L1 distortion: higher + produces examples with lower L1 distortion, at the + cost of higher L2 (and typically Linf) distortion + :param decision_rule: EN or L1. Select final adversarial example from + all successful examples based on the least + elastic-net or L1 distortion criterion. + :param batch_size: Number of attacks to run simultaneously. + :param confidence: Confidence of adversarial examples: higher produces + examples with larger l2 distortion, but more + strongly classified as adversarial. + :param targeted: boolean controlling the behavior of the adversarial + examples produced. If set to False, they will be + misclassified in any wrong class. If set to True, + they will be misclassified in a chosen target class. + :param learning_rate: The learning rate for the attack algorithm. + Smaller values produce better results but are + slower to converge. + :param binary_search_steps: The number of times we perform binary + search to find the optimal tradeoff- + constant between norm of the perturbation + and confidence of the classification. Set + 'initial_const' to a large value and fix + this param to 1 for speed. + :param max_iterations: The maximum number of iterations. Setting this + to a larger value will produce lower distortion + results. Using only a few iterations requires + a larger learning rate, and will produce larger + distortion results. + :param abort_early: If true, allows early abort when the total + loss starts to increase (greatly speeds up attack, + but hurts performance, particularly on ImageNet) + :param initial_const: The initial tradeoff-constant to use to tune the + relative importance of size of the perturbation + and confidence of classification. + If binary_search_steps is large, the initial + constant is not important. A smaller value of + this constant gives lower distortion results. + For computational efficiency, fix + binary_search_steps to 1 and set this param + to a large value. + :param clip_min: (optional float) Minimum input component value. + :param clip_max: (optional float) Maximum input component value. + :param num_labels: the number of classes in the model's output. + :param shape: the shape of the model's input tensor. + """ + + self.sess = sess + self.TARGETED = targeted + self.LEARNING_RATE = learning_rate + self.MAX_ITERATIONS = max_iterations + self.BINARY_SEARCH_STEPS = binary_search_steps + self.ABORT_EARLY = abort_early + self.CONFIDENCE = confidence + self.initial_const = initial_const + self.batch_size = batch_size + self.clip_min = clip_min + self.clip_max = clip_max + self.model = model + self.decision_rule = decision_rule + + self.beta = beta + self.beta_t = tf.cast(self.beta, tf_dtype) + + self.repeat = binary_search_steps >= 10 + + self.shape = shape = tuple([batch_size] + list(shape)) + + # these are variables to be more efficient in sending data to tf + self.timg = tf.Variable(np.zeros(shape), dtype=tf_dtype, name="timg") + self.newimg = tf.Variable(np.zeros(shape), dtype=tf_dtype, name="newimg") + self.slack = tf.Variable(np.zeros(shape), dtype=tf_dtype, name="slack") + self.tlab = tf.Variable( + np.zeros((batch_size, num_labels)), dtype=tf_dtype, name="tlab" + ) + self.const = tf.Variable(np.zeros(batch_size), dtype=tf_dtype, name="const") + + # and here's what we use to assign them + self.assign_timg = tf.placeholder(tf_dtype, shape, name="assign_timg") + self.assign_newimg = tf.placeholder(tf_dtype, shape, name="assign_newimg") + self.assign_slack = tf.placeholder(tf_dtype, shape, name="assign_slack") + self.assign_tlab = tf.placeholder( + tf_dtype, (batch_size, num_labels), name="assign_tlab" + ) + self.assign_const = tf.placeholder(tf_dtype, [batch_size], name="assign_const") + + self.global_step = tf.Variable(0, trainable=False) + self.global_step_t = tf.cast(self.global_step, tf_dtype) + + # Fast Iterative Shrinkage Thresholding + self.zt = tf.divide( + self.global_step_t, self.global_step_t + tf.cast(3, tf_dtype) + ) + cond1 = tf.cast( + tf.greater(tf.subtract(self.slack, self.timg), self.beta_t), tf_dtype + ) + cond2 = tf.cast( + tf.less_equal(tf.abs(tf.subtract(self.slack, self.timg)), self.beta_t), + tf_dtype, + ) + cond3 = tf.cast( + tf.less(tf.subtract(self.slack, self.timg), tf.negative(self.beta_t)), + tf_dtype, + ) + + upper = tf.minimum( + tf.subtract(self.slack, self.beta_t), tf.cast(self.clip_max, tf_dtype) + ) + lower = tf.maximum( + tf.add(self.slack, self.beta_t), tf.cast(self.clip_min, tf_dtype) + ) + + self.assign_newimg = tf.multiply(cond1, upper) + self.assign_newimg += tf.multiply(cond2, self.timg) + self.assign_newimg += tf.multiply(cond3, lower) + + self.assign_slack = self.assign_newimg + self.assign_slack += tf.multiply(self.zt, self.assign_newimg - self.newimg) + + # -------------------------------- + self.setter = tf.assign(self.newimg, self.assign_newimg) + self.setter_y = tf.assign(self.slack, self.assign_slack) + + # prediction BEFORE-SOFTMAX of the model + self.output = model.get_logits(self.newimg) + self.output_y = model.get_logits(self.slack) + + # distance to the input data + self.l2dist = reduce_sum( + tf.square(self.newimg - self.timg), list(range(1, len(shape))) + ) + self.l2dist_y = reduce_sum( + tf.square(self.slack - self.timg), list(range(1, len(shape))) + ) + self.l1dist = reduce_sum( + tf.abs(self.newimg - self.timg), list(range(1, len(shape))) + ) + self.l1dist_y = reduce_sum( + tf.abs(self.slack - self.timg), list(range(1, len(shape))) + ) + self.elasticdist = self.l2dist + tf.multiply(self.l1dist, self.beta_t) + self.elasticdist_y = self.l2dist_y + tf.multiply(self.l1dist_y, self.beta_t) + if self.decision_rule == "EN": + self.crit = self.elasticdist + self.crit_p = "Elastic" + else: + self.crit = self.l1dist + self.crit_p = "L1" + + # compute the probability of the label class versus the maximum other + real = reduce_sum((self.tlab) * self.output, 1) + real_y = reduce_sum((self.tlab) * self.output_y, 1) + other = reduce_max((1 - self.tlab) * self.output - (self.tlab * 10000), 1) + other_y = reduce_max((1 - self.tlab) * self.output_y - (self.tlab * 10000), 1) + + if self.TARGETED: + # if targeted, optimize for making the other class most likely + loss1 = tf.maximum(ZERO(), other - real + self.CONFIDENCE) + loss1_y = tf.maximum(ZERO(), other_y - real_y + self.CONFIDENCE) + else: + # if untargeted, optimize for making this class least likely. + loss1 = tf.maximum(ZERO(), real - other + self.CONFIDENCE) + loss1_y = tf.maximum(ZERO(), real_y - other_y + self.CONFIDENCE) + + # sum up the losses + self.loss21 = reduce_sum(self.l1dist) + self.loss21_y = reduce_sum(self.l1dist_y) + self.loss2 = reduce_sum(self.l2dist) + self.loss2_y = reduce_sum(self.l2dist_y) + self.loss1 = reduce_sum(self.const * loss1) + self.loss1_y = reduce_sum(self.const * loss1_y) + self.loss_opt = self.loss1_y + self.loss2_y + self.loss = self.loss1 + self.loss2 + tf.multiply(self.beta_t, self.loss21) + + self.learning_rate = tf.train.polynomial_decay( + self.LEARNING_RATE, self.global_step, self.MAX_ITERATIONS, 0, power=0.5 + ) + + # Setup the optimizer and keep track of variables we're creating + start_vars = set(x.name for x in tf.global_variables()) + optimizer = tf.train.GradientDescentOptimizer(self.learning_rate) + self.train = optimizer.minimize( + self.loss_opt, var_list=[self.slack], global_step=self.global_step + ) + end_vars = tf.global_variables() + new_vars = [x for x in end_vars if x.name not in start_vars] + + # these are the variables to initialize when we run + self.setup = [] + self.setup.append(self.timg.assign(self.assign_timg)) + self.setup.append(self.tlab.assign(self.assign_tlab)) + self.setup.append(self.const.assign(self.assign_const)) + + var_list = [self.global_step] + [self.slack] + [self.newimg] + new_vars + self.init = tf.variables_initializer(var_list=var_list) + + def attack(self, imgs, targets): + """ + Perform the EAD attack on the given instance for the given targets. + + If self.targeted is true, then the targets represents the target labels + If self.targeted is false, then targets are the original class labels + """ + + batch_size = self.batch_size + r = [] + for i in range(0, len(imgs) // batch_size): + _logger.debug( + ("Running EAD attack on instance %s of %s", i * batch_size, len(imgs)) + ) + r.extend( + self.attack_batch( + imgs[i * batch_size : (i + 1) * batch_size], + targets[i * batch_size : (i + 1) * batch_size], + ) + ) + if len(imgs) % batch_size != 0: + last_elements = len(imgs) - (len(imgs) % batch_size) + _logger.debug( + ("Running EAD attack on instance %s of %s", last_elements, len(imgs)) + ) + temp_imgs = np.zeros((batch_size,) + imgs.shape[2:]) + temp_targets = np.zeros((batch_size,) + targets.shape[2:]) + temp_imgs[: (len(imgs) % batch_size)] = imgs[last_elements:] + temp_targets[: (len(imgs) % batch_size)] = targets[last_elements:] + temp_data = self.attack_batch(temp_imgs, temp_targets) + r.extend(temp_data[: (len(imgs) % batch_size)], targets[last_elements:]) + return np.array(r) + + def attack_batch(self, imgs, labs): + """ + Run the attack on a batch of instance and labels. + """ + + def compare(x, y): + if not isinstance(x, (float, int, np.int64)): + x = np.copy(x) + if self.TARGETED: + x[y] -= self.CONFIDENCE + else: + x[y] += self.CONFIDENCE + x = np.argmax(x) + if self.TARGETED: + return x == y + else: + return x != y + + batch_size = self.batch_size + + imgs = np.clip(imgs, self.clip_min, self.clip_max) + + # set the lower and upper bounds accordingly + lower_bound = np.zeros(batch_size) + CONST = np.ones(batch_size) * self.initial_const + upper_bound = np.ones(batch_size) * 1e10 + + # placeholders for the best en, score, and instance attack found so far + o_bestdst = [1e10] * batch_size + o_bestscore = [-1] * batch_size + o_bestattack = np.copy(imgs) + + for outer_step in range(self.BINARY_SEARCH_STEPS): + # completely reset the optimizer's internal state. + self.sess.run(self.init) + batch = imgs[:batch_size] + batchlab = labs[:batch_size] + + bestdst = [1e10] * batch_size + bestscore = [-1] * batch_size + _logger.debug( + " Binary search step %s of %s", outer_step, self.BINARY_SEARCH_STEPS + ) + + # The last iteration (if we run many steps) repeat the search once. + if self.repeat and outer_step == self.BINARY_SEARCH_STEPS - 1: + CONST = upper_bound + + # set the variables so that we don't have to send them over again + self.sess.run( + self.setup, + { + self.assign_timg: batch, + self.assign_tlab: batchlab, + self.assign_const: CONST, + }, + ) + self.sess.run(self.setter, {self.assign_newimg: batch}) + self.sess.run(self.setter_y, {self.assign_slack: batch}) + prev = 1e6 + for iteration in range(self.MAX_ITERATIONS): + # perform the attack + self.sess.run([self.train]) + self.sess.run([self.setter, self.setter_y]) + l, l2s, l1s, crit, scores, nimg = self.sess.run( + [ + self.loss, + self.l2dist, + self.l1dist, + self.crit, + self.output, + self.newimg, + ] + ) + if iteration % ((self.MAX_ITERATIONS // 10) or 1) == 0: + _logger.debug( + ( + " Iteration {} of {}: loss={:.3g} " + + "l2={:.3g} l1={:.3g} f={:.3g}" + ).format( + iteration, + self.MAX_ITERATIONS, + l, + np.mean(l2s), + np.mean(l1s), + np.mean(scores), + ) + ) + + # check if we should abort search if we're getting nowhere. + if ( + self.ABORT_EARLY + and iteration % ((self.MAX_ITERATIONS // 10) or 1) == 0 + ): + if l > prev * 0.9999: + msg = " Failed to make progress; stop early" + _logger.debug(msg) + break + prev = l + + # adjust the best result found so far + for e, (dst, sc, ii) in enumerate(zip(crit, scores, nimg)): + lab = np.argmax(batchlab[e]) + if dst < bestdst[e] and compare(sc, lab): + bestdst[e] = dst + bestscore[e] = np.argmax(sc) + if dst < o_bestdst[e] and compare(sc, lab): + o_bestdst[e] = dst + o_bestscore[e] = np.argmax(sc) + o_bestattack[e] = ii + + # adjust the constant as needed + for e in range(batch_size): + if compare(bestscore[e], np.argmax(batchlab[e])) and bestscore[e] != -1: + # success, divide const by two + upper_bound[e] = min(upper_bound[e], CONST[e]) + if upper_bound[e] < 1e9: + CONST[e] = (lower_bound[e] + upper_bound[e]) / 2 + else: + # failure, either multiply by 10 if no solution found yet + # or do binary search with the known upper bound + lower_bound[e] = max(lower_bound[e], CONST[e]) + if upper_bound[e] < 1e9: + CONST[e] = (lower_bound[e] + upper_bound[e]) / 2 + else: + CONST[e] *= 10 + _logger.debug( + " Successfully generated adversarial examples " + + "on {} of {} instances.".format(sum(upper_bound < 1e9), batch_size) + ) + o_bestdst = np.array(o_bestdst) + mean = np.mean(np.sqrt(o_bestdst[o_bestdst < 1e9])) + _logger.debug( + self.crit_p + " Mean successful distortion: {:.4g}".format(mean) + ) + + # return the best solution found + o_bestdst = np.array(o_bestdst) + return o_bestattack diff --git a/cleverhans_v3.1.0/cleverhans/attacks/fast_feature_adversaries.py b/cleverhans_v3.1.0/cleverhans/attacks/fast_feature_adversaries.py new file mode 100644 index 000000000..8f814ecac --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans/attacks/fast_feature_adversaries.py @@ -0,0 +1,171 @@ +""" +The FastFeatureAdversaries attack +""" +# pylint: disable=missing-docstring +import warnings + +import numpy as np +import tensorflow as tf + +from cleverhans.attacks.attack import Attack +from cleverhans.compat import reduce_sum +from cleverhans.model import Model +from cleverhans.utils_tf import clip_eta + + +class FastFeatureAdversaries(Attack): + """ + This is a fast implementation of "Feature Adversaries", an attack + against a target internal representation of a model. + "Feature adversaries" were originally introduced in (Sabour et al. 2016), + where the optimization was done using LBFGS. + Paper link: https://arxiv.org/abs/1511.05122 + + This implementation is similar to "Basic Iterative Method" + (Kurakin et al. 2016) but applied to the internal representations. + + :param model: cleverhans.model.Model + :param sess: optional tf.Session + :param dtypestr: dtype of the data + :param kwargs: passed through to super constructor + """ + + def __init__(self, model, sess=None, dtypestr="float32", **kwargs): + """ + Create a FastFeatureAdversaries instance. + """ + super(FastFeatureAdversaries, self).__init__(model, sess, dtypestr, **kwargs) + self.feedable_kwargs = ("eps", "eps_iter", "clip_min", "clip_max") + self.structural_kwargs = ["ord", "nb_iter", "layer"] + + assert isinstance(self.model, Model) + + def parse_params( + self, + layer=None, + eps=0.3, + eps_iter=0.05, + nb_iter=10, + ord=np.inf, + clip_min=None, + clip_max=None, + **kwargs + ): + """ + Take in a dictionary of parameters and applies attack-specific checks + before saving them as attributes. + + Attack-specific parameters: + + :param layer: (required str) name of the layer to target. + :param eps: (optional float) maximum distortion of adversarial example + compared to original input + :param eps_iter: (optional float) step size for each attack iteration + :param nb_iter: (optional int) Number of attack iterations. + :param ord: (optional) Order of the norm (mimics Numpy). + Possible values: np.inf, 1 or 2. + :param clip_min: (optional float) Minimum input component value + :param clip_max: (optional float) Maximum input component value + """ + + # Save attack-specific parameters + self.layer = layer + self.eps = eps + self.eps_iter = eps_iter + self.nb_iter = nb_iter + self.ord = ord + self.clip_min = clip_min + self.clip_max = clip_max + + # Check if order of the norm is acceptable given current implementation + if self.ord not in [np.inf, 1, 2]: + raise ValueError("Norm order must be either np.inf, 1, or 2.") + if len(kwargs.keys()) > 0: + warnings.warn( + "kwargs is unused and will be removed on or after " "2019-04-26." + ) + + return True + + def attack_single_step(self, x, eta, g_feat): + """ + TensorFlow implementation of the Fast Feature Gradient. This is a + single step attack similar to Fast Gradient Method that attacks an + internal representation. + + :param x: the input placeholder + :param eta: A tensor the same shape as x that holds the perturbation. + :param g_feat: model's internal tensor for guide + :return: a tensor for the adversarial example + """ + + adv_x = x + eta + a_feat = self.model.fprop(adv_x)[self.layer] + + # feat.shape = (batch, c) or (batch, w, h, c) + axis = list(range(1, len(a_feat.shape))) + + # Compute loss + # This is a targeted attack, hence the negative sign + loss = -reduce_sum(tf.square(a_feat - g_feat), axis) + + # Define gradient of loss wrt input + (grad,) = tf.gradients(loss, adv_x) + + # Multiply by constant epsilon + scaled_signed_grad = self.eps_iter * tf.sign(grad) + + # Add perturbation to original example to obtain adversarial example + adv_x = adv_x + scaled_signed_grad + + # If clipping is needed, + # reset all values outside of [clip_min, clip_max] + if (self.clip_min is not None) and (self.clip_max is not None): + adv_x = tf.clip_by_value(adv_x, self.clip_min, self.clip_max) + + adv_x = tf.stop_gradient(adv_x) + + eta = adv_x - x + eta = clip_eta(eta, self.ord, self.eps) + + return eta + + def generate(self, x, g, **kwargs): + """ + Generate symbolic graph for adversarial examples and return. + + :param x: The model's symbolic inputs. + :param g: The target value of the symbolic representation + :param kwargs: See `parse_params` + """ + + # Parse and save attack-specific parameters + assert self.parse_params(**kwargs) + + g_feat = self.model.fprop(g)[self.layer] + + # Initialize loop variables + eta = tf.random_uniform(tf.shape(x), -self.eps, self.eps, dtype=self.tf_dtype) + eta = clip_eta(eta, self.ord, self.eps) + + def cond(i, _): + return tf.less(i, self.nb_iter) + + def body(i, e): + new_eta = self.attack_single_step(x, e, g_feat) + return i + 1, new_eta + + _, eta = tf.while_loop( + cond, + body, + (tf.zeros([]), eta), + back_prop=True, + maximum_iterations=self.nb_iter, + ) + + # Define adversarial example (and clip if necessary) + adv_x = x + eta + if self.clip_min is not None and self.clip_max is not None: + adv_x = tf.clip_by_value(adv_x, self.clip_min, self.clip_max) + + return adv_x diff --git a/cleverhans_v3.1.0/cleverhans/attacks/fast_gradient_method.py b/cleverhans_v3.1.0/cleverhans/attacks/fast_gradient_method.py new file mode 100644 index 000000000..8a0f05a88 --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans/attacks/fast_gradient_method.py @@ -0,0 +1,271 @@ +""" +The FastGradientMethod attack. +""" + +import warnings + +import numpy as np +import tensorflow as tf + +from cleverhans.attacks.attack import Attack +from cleverhans.compat import reduce_max, reduce_sum, softmax_cross_entropy_with_logits +from cleverhans import utils_tf + + +class FastGradientMethod(Attack): + """ + This attack was originally implemented by Goodfellow et al. (2014) with the + infinity norm (and is known as the "Fast Gradient Sign Method"). This + implementation extends the attack to other norms, and is therefore called + the Fast Gradient Method. + Paper link: https://arxiv.org/abs/1412.6572 + + :param model: cleverhans.model.Model + :param sess: optional tf.Session + :param dtypestr: dtype of the data + :param kwargs: passed through to super constructor + """ + + def __init__(self, model, sess=None, dtypestr="float32", **kwargs): + """ + Create a FastGradientMethod instance. + Note: the model parameter should be an instance of the + cleverhans.model.Model abstraction provided by CleverHans. + """ + + super(FastGradientMethod, self).__init__(model, sess, dtypestr, **kwargs) + self.feedable_kwargs = ("eps", "y", "y_target", "clip_min", "clip_max") + self.structural_kwargs = ["ord", "sanity_checks", "clip_grad", "loss_fn"] + + def generate(self, x, **kwargs): + """ + Returns the graph for Fast Gradient Method adversarial examples. + + :param x: The model's symbolic inputs. + :param kwargs: See `parse_params` + """ + # Parse and save attack-specific parameters + assert self.parse_params(**kwargs) + + labels, _nb_classes = self.get_or_guess_labels(x, kwargs) + + return fgm( + x, + self.model.get_logits(x), + y=labels, + eps=self.eps, + ord=self.ord, + loss_fn=self.loss_fn, + clip_min=self.clip_min, + clip_max=self.clip_max, + clip_grad=self.clip_grad, + targeted=(self.y_target is not None), + sanity_checks=self.sanity_checks, + ) + + def parse_params( + self, + eps=0.3, + ord=np.inf, + loss_fn=softmax_cross_entropy_with_logits, + y=None, + y_target=None, + clip_min=None, + clip_max=None, + clip_grad=False, + sanity_checks=True, + **kwargs + ): + """ + Take in a dictionary of parameters and applies attack-specific checks + before saving them as attributes. + + Attack-specific parameters: + + :param eps: (optional float) attack step size (input variation) + :param ord: (optional) Order of the norm (mimics NumPy). + Possible values: np.inf, 1 or 2. + :param loss_fn: Loss function that takes (labels, logits) as arguments and returns loss + :param y: (optional) A tensor with the true labels. Only provide + this parameter if you'd like to use true labels when crafting + adversarial samples. Otherwise, model predictions are used as + labels to avoid the "label leaking" effect (explained in this + paper: https://arxiv.org/abs/1611.01236). Default is None. + Labels should be one-hot-encoded. + :param y_target: (optional) A tensor with the labels to target. Leave + y_target=None if y is also set. Labels should be + one-hot-encoded. + :param clip_min: (optional float) Minimum input component value + :param clip_max: (optional float) Maximum input component value + :param clip_grad: (optional bool) Ignore gradient components + at positions where the input is already at the boundary + of the domain, and the update step will get clipped out. + :param sanity_checks: bool, if True, include asserts + (Turn them off to use less runtime / memory or for unit tests that + intentionally pass strange input) + """ + # Save attack-specific parameters + + self.eps = eps + self.ord = ord + self.loss_fn = loss_fn + self.y = y + self.y_target = y_target + self.clip_min = clip_min + self.clip_max = clip_max + self.clip_grad = clip_grad + self.sanity_checks = sanity_checks + + if self.y is not None and self.y_target is not None: + raise ValueError("Must not set both y and y_target") + # Check if order of the norm is acceptable given current implementation + if self.ord not in [np.inf, int(1), int(2)]: + raise ValueError("Norm order must be either np.inf, 1, or 2.") + + if self.clip_grad and (self.clip_min is None or self.clip_max is None): + raise ValueError("Must set clip_min and clip_max if clip_grad is set") + + if len(kwargs.keys()) > 0: + warnings.warn( + "kwargs is unused and will be removed on or after " "2019-04-26." + ) + + return True + + +def fgm( + x, + logits, + y=None, + eps=0.3, + ord=np.inf, + loss_fn=softmax_cross_entropy_with_logits, + clip_min=None, + clip_max=None, + clip_grad=False, + targeted=False, + sanity_checks=True, +): + """ + TensorFlow implementation of the Fast Gradient Method. + :param x: the input placeholder + :param logits: output of model.get_logits + :param y: (optional) A placeholder for the true labels. If targeted + is true, then provide the target label. Otherwise, only provide + this parameter if you'd like to use true labels when crafting + adversarial samples. Otherwise, model predictions are used as + labels to avoid the "label leaking" effect (explained in this + paper: https://arxiv.org/abs/1611.01236). Default is None. + Labels should be one-hot-encoded. + :param eps: the epsilon (input variation parameter) + :param ord: (optional) Order of the norm (mimics NumPy). + Possible values: np.inf, 1 or 2. + :param loss_fn: Loss function that takes (labels, logits) as arguments and returns loss + :param clip_min: Minimum float value for adversarial example components + :param clip_max: Maximum float value for adversarial example components + :param clip_grad: (optional bool) Ignore gradient components + at positions where the input is already at the boundary + of the domain, and the update step will get clipped out. + :param targeted: Is the attack targeted or untargeted? Untargeted, the + default, will try to make the label incorrect. Targeted + will instead try to move in the direction of being more + like y. + :return: a tensor for the adversarial example + """ + + asserts = [] + + # If a data range was specified, check that the input was in that range + if clip_min is not None: + asserts.append(utils_tf.assert_greater_equal(x, tf.cast(clip_min, x.dtype))) + + if clip_max is not None: + asserts.append(utils_tf.assert_less_equal(x, tf.cast(clip_max, x.dtype))) + + # Make sure the caller has not passed probs by accident + assert logits.op.type != "Softmax" + + if y is None: + # Using model predictions as ground truth to avoid label leaking + preds_max = reduce_max(logits, 1, keepdims=True) + y = tf.to_float(tf.equal(logits, preds_max)) + y = tf.stop_gradient(y) + y = y / reduce_sum(y, 1, keepdims=True) + + # Compute loss + loss = loss_fn(labels=y, logits=logits) + if targeted: + loss = -loss + + # Define gradient of loss wrt input + (grad,) = tf.gradients(loss, x) + + if clip_grad: + grad = utils_tf.zero_out_clipped_grads(grad, x, clip_min, clip_max) + + optimal_perturbation = optimize_linear(grad, eps, ord) + + # Add perturbation to original example to obtain adversarial example + adv_x = x + optimal_perturbation + + # If clipping is needed, reset all values outside of [clip_min, clip_max] + if (clip_min is not None) or (clip_max is not None): + # We don't currently support one-sided clipping + assert clip_min is not None and clip_max is not None + adv_x = utils_tf.clip_by_value(adv_x, clip_min, clip_max) + + if sanity_checks: + with tf.control_dependencies(asserts): + adv_x = tf.identity(adv_x) + + return adv_x + + +def optimize_linear(grad, eps, ord=np.inf): + """ + Solves for the optimal input to a linear function under a norm constraint. + + Optimal_perturbation = argmax_{eta, ||eta||_{ord} < eps} dot(eta, grad) + + :param grad: tf tensor containing a batch of gradients + :param eps: float scalar specifying size of constraint region + :param ord: int specifying order of norm + :returns: + tf tensor containing optimal perturbation + """ + + # In Python 2, the `list` call in the following line is redundant / harmless. + # In Python 3, the `list` call is needed to convert the iterator returned by `range` into a list. + red_ind = list(range(1, len(grad.get_shape()))) + avoid_zero_div = 1e-12 + if ord == np.inf: + # Take sign of gradient + optimal_perturbation = tf.sign(grad) + # The following line should not change the numerical results. + # It applies only because `optimal_perturbation` is the output of + # a `sign` op, which has zero derivative anyway. + # It should not be applied for the other norms, where the + # perturbation has a non-zero derivative. + optimal_perturbation = tf.stop_gradient(optimal_perturbation) + elif ord == 1: + abs_grad = tf.abs(grad) + sign = tf.sign(grad) + max_abs_grad = tf.reduce_max(abs_grad, red_ind, keepdims=True) + tied_for_max = tf.to_float(tf.equal(abs_grad, max_abs_grad)) + num_ties = tf.reduce_sum(tied_for_max, red_ind, keepdims=True) + optimal_perturbation = sign * tied_for_max / num_ties + elif ord == 2: + square = tf.maximum( + avoid_zero_div, + reduce_sum(tf.square(grad), reduction_indices=red_ind, keepdims=True), + ) + optimal_perturbation = grad / tf.sqrt(square) + else: + raise NotImplementedError( + "Only L-inf, L1 and L2 norms are " "currently implemented." + ) + + # Scale perturbation to be the solution for the norm=eps rather than + # norm=1 problem + scaled_perturbation = utils_tf.mul(eps, optimal_perturbation) + return scaled_perturbation diff --git a/cleverhans_v3.1.0/cleverhans/attacks/hop_skip_jump_attack.py b/cleverhans_v3.1.0/cleverhans/attacks/hop_skip_jump_attack.py new file mode 100644 index 000000000..260abfd22 --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans/attacks/hop_skip_jump_attack.py @@ -0,0 +1,578 @@ +""" Boundary Attack++ +""" +import logging +import numpy as np +import tensorflow as tf +from warnings import warn +from cleverhans.attacks import Attack +from cleverhans.model import CallableModelWrapper, Model, wrapper_warning_logits +from cleverhans import utils, utils_tf + +np_dtype = np.dtype("float32") +tf_dtype = tf.as_dtype("float32") + +_logger = utils.create_logger("cleverhans.attacks.hop_skip_jump_attack") +_logger.setLevel(logging.INFO) + + +class HopSkipJumpAttack(Attack): + """ + HopSkipJumpAttack was originally proposed by Chen, Jordan and Wainwright. + It is a decision-based attack that requires access to output + labels of a model alone. + Paper link: https://arxiv.org/abs/1904.02144 + At a high level, this attack is an iterative attack composed of three + steps: Binary search to approach the boundary; gradient estimation; + stepsize search. HopSkipJumpAttack requires fewer model queries than + Boundary Attack which was based on rejective sampling. + :param model: cleverhans.model.Model + :param sess: tf.Session + :param dtypestr: dtype of the data + :param kwargs: passed through to super constructor. + see parse_params for details. + """ + + def __init__(self, model, sess, dtypestr="float32", **kwargs): + """ + Note: the model parameter should be an instance of the + cleverhans.model.Model abstraction provided by CleverHans. + """ + if not isinstance(model, Model): + wrapper_warning_logits() + model = CallableModelWrapper(model, "logits") + + super(HopSkipJumpAttack, self).__init__(model, sess, dtypestr, **kwargs) + + self.feedable_kwargs = ("y_target", "image_target") + + self.structural_kwargs = [ + "stepsize_search", + "clip_min", + "clip_max", + "constraint", + "num_iterations", + "initial_num_evals", + "max_num_evals", + "batch_size", + "verbose", + "gamma", + ] + + def generate(self, x, **kwargs): + """ + Return a tensor that constructs adversarial examples for the given + input. Generate uses tf.py_func in order to operate over tensors. + :param x: A tensor with the inputs. + :param kwargs: See `parse_params` + """ + self.parse_params(**kwargs) + shape = [int(i) for i in x.get_shape().as_list()[1:]] + + assert ( + self.sess is not None + ), "Cannot use `generate` when no `sess` was provided" + _check_first_dimension(x, "input") + if self.y_target is not None: + _check_first_dimension(self.y_target, "y_target") + assert ( + self.image_target is not None + ), "Require a target image for targeted attack." + _check_first_dimension(self.image_target, "image_target") + + # Set shape and d. + self.shape = shape + self.d = int(np.prod(shape)) + + # Set binary search threshold. + if self.constraint == "l2": + self.theta = self.gamma / (np.sqrt(self.d) * self.d) + else: + self.theta = self.gamma / (self.d * self.d) + + # Construct input placeholder and output for decision function. + self.input_ph = tf.placeholder( + tf_dtype, [None] + list(self.shape), name="input_image" + ) + self.logits = self.model.get_logits(self.input_ph) + + def hsja_wrap(x, target_label, target_image): + """ Wrapper to use tensors as input and output. """ + return np.array( + self._hsja(x, target_label, target_image), dtype=self.np_dtype + ) + + if self.y_target is not None: + # targeted attack that requires target label and image. + wrap = tf.py_func( + hsja_wrap, [x[0], self.y_target[0], self.image_target[0]], self.tf_dtype + ) + else: + if self.image_target is not None: + # untargeted attack with an initialized image. + wrap = tf.py_func( + lambda x, target_image: hsja_wrap(x, None, target_image), + [x[0], self.image_target[0]], + self.tf_dtype, + ) + else: + # untargeted attack without an initialized image. + wrap = tf.py_func( + lambda x: hsja_wrap(x, None, None), [x[0]], self.tf_dtype + ) + + wrap.set_shape(x.get_shape()) + + return wrap + + def generate_np(self, x, **kwargs): + """ + Generate adversarial images in a for loop. + :param y: An array of shape (n, nb_classes) for true labels. + :param y_target: An array of shape (n, nb_classes) for target labels. + Required for targeted attack. + :param image_target: An array of shape (n, **image shape) for initial + target images. Required for targeted attack. + + See parse_params for other kwargs. + + """ + + x_adv = [] + + if "image_target" in kwargs and kwargs["image_target"] is not None: + image_target = np.copy(kwargs["image_target"]) + else: + image_target = None + if "y_target" in kwargs and kwargs["y_target"] is not None: + y_target = np.copy(kwargs["y_target"]) + else: + y_target = None + + for i, x_single in enumerate(x): + img = np.expand_dims(x_single, axis=0) + if image_target is not None: + single_img_target = np.expand_dims(image_target[i], axis=0) + kwargs["image_target"] = single_img_target + if y_target is not None: + single_y_target = np.expand_dims(y_target[i], axis=0) + kwargs["y_target"] = single_y_target + + adv_img = super(HopSkipJumpAttack, self).generate_np(img, **kwargs) + x_adv.append(adv_img) + + return np.concatenate(x_adv, axis=0) + + def parse_params( + self, + y_target=None, + image_target=None, + initial_num_evals=100, + max_num_evals=10000, + stepsize_search="geometric_progression", + num_iterations=64, + gamma=1.0, + constraint="l2", + batch_size=128, + verbose=True, + clip_min=0, + clip_max=1, + ): + """ + :param y: A tensor of shape (1, nb_classes) for true labels. + :param y_target: A tensor of shape (1, nb_classes) for target labels. + Required for targeted attack. + :param image_target: A tensor of shape (1, **image shape) for initial + target images. Required for targeted attack. + :param initial_num_evals: initial number of evaluations for + gradient estimation. + :param max_num_evals: maximum number of evaluations for gradient estimation. + :param stepsize_search: How to search for stepsize; choices are + 'geometric_progression', 'grid_search'. + 'geometric progression' initializes the stepsize + by ||x_t - x||_p / sqrt(iteration), and keep + decreasing by half until reaching the target + side of the boundary. 'grid_search' chooses the + optimal epsilon over a grid, in the scale of + ||x_t - x||_p. + :param num_iterations: The number of iterations. + :param gamma: The binary search threshold theta is gamma / d^{3/2} for + l2 attack and gamma / d^2 for linf attack. + :param constraint: The distance to optimize; choices are 'l2', 'linf'. + :param batch_size: batch_size for model prediction. + :param verbose: (boolean) Whether distance at each step is printed. + :param clip_min: (optional float) Minimum input component value + :param clip_max: (optional float) Maximum input component value + """ + + # ignore the y and y_target argument + self.y_target = y_target + self.image_target = image_target + self.initial_num_evals = initial_num_evals + self.max_num_evals = max_num_evals + self.stepsize_search = stepsize_search + self.num_iterations = num_iterations + self.gamma = gamma + self.constraint = constraint + self.batch_size = batch_size + self.clip_min = clip_min + self.clip_max = clip_max + self.verbose = verbose + + def _hsja(self, sample, target_label, target_image): + """ + Main algorithm for HopSkipJumpAttack. + + Return a tensor that constructs adversarial examples for the given + input. Generate uses tf.py_func in order to operate over tensors. + + :param sample: input image. Without the batchsize dimension. + :param target_label: integer for targeted attack, + None for nontargeted attack. Without the batchsize dimension. + :param target_image: an array with the same size as sample, or None. + Without the batchsize dimension. + + + Output: + perturbed image. + + """ + + # Original label required for untargeted attack. + if target_label is None: + original_label = np.argmax( + self.sess.run(self.logits, feed_dict={self.input_ph: sample[None]}) + ) + else: + target_label = np.argmax(target_label) + + def decision_function(images): + """ + Decision function output 1 on the desired side of the boundary, + 0 otherwise. + """ + images = clip_image(images, self.clip_min, self.clip_max) + prob = [] + for i in range(0, len(images), self.batch_size): + batch = images[i : i + self.batch_size] + prob_i = self.sess.run(self.logits, feed_dict={self.input_ph: batch}) + prob.append(prob_i) + prob = np.concatenate(prob, axis=0) + if target_label is None: + return np.argmax(prob, axis=1) != original_label + else: + return np.argmax(prob, axis=1) == target_label + + # Initialize. + if target_image is None: + perturbed = initialize( + decision_function, sample, self.shape, self.clip_min, self.clip_max + ) + else: + perturbed = target_image + + # Project the initialization to the boundary. + perturbed, dist_post_update = binary_search_batch( + sample, + np.expand_dims(perturbed, 0), + decision_function, + self.shape, + self.constraint, + self.theta, + ) + + dist = compute_distance(perturbed, sample, self.constraint) + + for j in np.arange(self.num_iterations): + current_iteration = j + 1 + + # Choose delta. + delta = select_delta( + dist_post_update, + current_iteration, + self.clip_max, + self.clip_min, + self.d, + self.theta, + self.constraint, + ) + + # Choose number of evaluations. + num_evals = int( + min([self.initial_num_evals * np.sqrt(j + 1), self.max_num_evals]) + ) + + # approximate gradient. + gradf = approximate_gradient( + decision_function, + perturbed, + num_evals, + delta, + self.constraint, + self.shape, + self.clip_min, + self.clip_max, + ) + if self.constraint == "linf": + update = np.sign(gradf) + else: + update = gradf + + # search step size. + if self.stepsize_search == "geometric_progression": + # find step size. + epsilon = geometric_progression_for_stepsize( + perturbed, update, dist, decision_function, current_iteration + ) + + # Update the sample. + perturbed = clip_image( + perturbed + epsilon * update, self.clip_min, self.clip_max + ) + + # Binary search to return to the boundary. + perturbed, dist_post_update = binary_search_batch( + sample, + perturbed[None], + decision_function, + self.shape, + self.constraint, + self.theta, + ) + + elif self.stepsize_search == "grid_search": + # Grid search for stepsize. + epsilons = np.logspace(-4, 0, num=20, endpoint=True) * dist + epsilons_shape = [20] + len(self.shape) * [1] + perturbeds = perturbed + epsilons.reshape(epsilons_shape) * update + perturbeds = clip_image(perturbeds, self.clip_min, self.clip_max) + idx_perturbed = decision_function(perturbeds) + + if np.sum(idx_perturbed) > 0: + # Select the perturbation that yields the minimum distance # after binary search. + perturbed, dist_post_update = binary_search_batch( + sample, + perturbeds[idx_perturbed], + decision_function, + self.shape, + self.constraint, + self.theta, + ) + + # compute new distance. + dist = compute_distance(perturbed, sample, self.constraint) + if self.verbose: + print( + "iteration: {:d}, {:s} distance {:.4E}".format( + j + 1, self.constraint, dist + ) + ) + + perturbed = np.expand_dims(perturbed, 0) + return perturbed + + +def BoundaryAttackPlusPlus(model, sess, dtypestr="float32", **kwargs): + """ + A previous name used for HopSkipJumpAttack. + """ + warn( + "BoundaryAttackPlusPlus will be removed after 2019-12-08; use HopSkipJumpAttack." + ) + return HopSkipJumpAttack(model, sess, dtypestr, **kwargs) + + +def _check_first_dimension(x, tensor_name): + message = "Tensor {} should have batch_size of 1.".format(tensor_name) + if x.get_shape().as_list()[0] is None: + check_batch = utils_tf.assert_equal(tf.shape(x)[0], 1, message=message) + with tf.control_dependencies([check_batch]): + x = tf.identity(x) + elif x.get_shape().as_list()[0] != 1: + raise ValueError(message) + + +def clip_image(image, clip_min, clip_max): + """ Clip an image, or an image batch, with upper and lower threshold. """ + return np.minimum(np.maximum(clip_min, image), clip_max) + + +def compute_distance(x_ori, x_pert, constraint="l2"): + """ Compute the distance between two images. """ + if constraint == "l2": + dist = np.linalg.norm(x_ori - x_pert) + elif constraint == "linf": + dist = np.max(abs(x_ori - x_pert)) + return dist + + +def approximate_gradient( + decision_function, sample, num_evals, delta, constraint, shape, clip_min, clip_max +): + """ Gradient direction estimation """ + # Generate random vectors. + noise_shape = [num_evals] + list(shape) + if constraint == "l2": + rv = np.random.randn(*noise_shape) + elif constraint == "linf": + rv = np.random.uniform(low=-1, high=1, size=noise_shape) + + axis = tuple(range(1, 1 + len(shape))) + rv = rv / np.sqrt(np.sum(rv ** 2, axis=axis, keepdims=True)) + perturbed = sample + delta * rv + perturbed = clip_image(perturbed, clip_min, clip_max) + rv = (perturbed - sample) / delta + + # query the model. + decisions = decision_function(perturbed) + decision_shape = [len(decisions)] + [1] * len(shape) + fval = 2 * decisions.astype(np_dtype).reshape(decision_shape) - 1.0 + + # Baseline subtraction (when fval differs) + if np.mean(fval) == 1.0: # label changes. + gradf = np.mean(rv, axis=0) + elif np.mean(fval) == -1.0: # label not change. + gradf = -np.mean(rv, axis=0) + else: + fval = fval - np.mean(fval) + gradf = np.mean(fval * rv, axis=0) + + # Get the gradient direction. + gradf = gradf / np.linalg.norm(gradf) + + return gradf + + +def project(original_image, perturbed_images, alphas, shape, constraint): + """ Projection onto given l2 / linf balls in a batch. """ + alphas_shape = [len(alphas)] + [1] * len(shape) + alphas = alphas.reshape(alphas_shape) + if constraint == "l2": + projected = (1 - alphas) * original_image + alphas * perturbed_images + elif constraint == "linf": + projected = clip_image( + perturbed_images, original_image - alphas, original_image + alphas + ) + return projected + + +def binary_search_batch( + original_image, perturbed_images, decision_function, shape, constraint, theta +): + """ Binary search to approach the boundary. """ + + # Compute distance between each of perturbed image and original image. + dists_post_update = np.array( + [ + compute_distance(original_image, perturbed_image, constraint) + for perturbed_image in perturbed_images + ] + ) + + # Choose upper thresholds in binary searchs based on constraint. + if constraint == "linf": + highs = dists_post_update + # Stopping criteria. + thresholds = np.minimum(dists_post_update * theta, theta) + else: + highs = np.ones(len(perturbed_images)) + thresholds = theta + + lows = np.zeros(len(perturbed_images)) + + while np.max((highs - lows) / thresholds) > 1: + # projection to mids. + mids = (highs + lows) / 2.0 + mid_images = project(original_image, perturbed_images, mids, shape, constraint) + + # Update highs and lows based on model decisions. + decisions = decision_function(mid_images) + lows = np.where(decisions == 0, mids, lows) + highs = np.where(decisions == 1, mids, highs) + + out_images = project(original_image, perturbed_images, highs, shape, constraint) + + # Compute distance of the output image to select the best choice. + # (only used when stepsize_search is grid_search.) + dists = np.array( + [ + compute_distance(original_image, out_image, constraint) + for out_image in out_images + ] + ) + idx = np.argmin(dists) + + dist = dists_post_update[idx] + out_image = out_images[idx] + return out_image, dist + + +def initialize(decision_function, sample, shape, clip_min, clip_max): + """ + Efficient Implementation of BlendedUniformNoiseAttack in Foolbox. + """ + success = 0 + num_evals = 0 + + # Find a misclassified random noise. + while True: + random_noise = np.random.uniform(clip_min, clip_max, size=shape) + success = decision_function(random_noise[None])[0] + if success: + break + num_evals += 1 + message = ( + "Initialization failed! Try to use a misclassified image as `target_image`" + ) + assert num_evals < 1e4, message + + # Binary search to minimize l2 distance to original image. + low = 0.0 + high = 1.0 + while high - low > 0.001: + mid = (high + low) / 2.0 + blended = (1 - mid) * sample + mid * random_noise + success = decision_function(blended[None])[0] + if success: + high = mid + else: + low = mid + + initialization = (1 - high) * sample + high * random_noise + return initialization + + +def geometric_progression_for_stepsize( + x, update, dist, decision_function, current_iteration +): + """Geometric progression to search for stepsize. + Keep decreasing stepsize by half until reaching + the desired side of the boundary. + """ + epsilon = dist / np.sqrt(current_iteration) + while True: + updated = x + epsilon * update + success = decision_function(updated[None])[0] + if success: + break + else: + epsilon = epsilon / 2.0 + + return epsilon + + +def select_delta( + dist_post_update, current_iteration, clip_max, clip_min, d, theta, constraint +): + """ + Choose the delta at the scale of distance + between x and perturbed sample. + """ + if current_iteration == 1: + delta = 0.1 * (clip_max - clip_min) + else: + if constraint == "l2": + delta = np.sqrt(d) * theta * dist_post_update + elif constraint == "linf": + delta = d * theta * dist_post_update + + return delta diff --git a/cleverhans_v3.1.0/cleverhans/attacks/lbfgs.py b/cleverhans_v3.1.0/cleverhans/attacks/lbfgs.py new file mode 100644 index 000000000..3e7231915 --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans/attacks/lbfgs.py @@ -0,0 +1,319 @@ +"""The LBFGS attack +""" + +import numpy as np +import tensorflow as tf + +from cleverhans.attacks.attack import Attack +from cleverhans.compat import reduce_sum, softmax_cross_entropy_with_logits +from cleverhans.model import CallableModelWrapper, Model, wrapper_warning +from cleverhans import utils +from cleverhans import utils_tf + +_logger = utils.create_logger("cleverhans.attacks.lbfgs") +tf_dtype = tf.as_dtype("float32") + + +class LBFGS(Attack): + """ + LBFGS is the first adversarial attack for convolutional neural networks, + and is a target & iterative attack. + Paper link: "https://arxiv.org/pdf/1312.6199.pdf" + :param model: cleverhans.model.Model + :param sess: tf.Session + :param dtypestr: dtype of the data + :param kwargs: passed through to super constructor + """ + + def __init__(self, model, sess, dtypestr="float32", **kwargs): + if not isinstance(model, Model): + wrapper_warning() + model = CallableModelWrapper(model, "probs") + + super(LBFGS, self).__init__(model, sess, dtypestr, **kwargs) + + self.feedable_kwargs = ("y_target",) + self.structural_kwargs = [ + "batch_size", + "binary_search_steps", + "max_iterations", + "initial_const", + "clip_min", + "clip_max", + ] + + def generate(self, x, **kwargs): + """ + Return a tensor that constructs adversarial examples for the given + input. Generate uses tf.py_func in order to operate over tensors. + :param x: (required) A tensor with the inputs. + :param kwargs: See `parse_params` + """ + assert ( + self.sess is not None + ), "Cannot use `generate` when no `sess` was provided" + self.parse_params(**kwargs) + + if self.y_target is None: + self.y_target, nb_classes = self.get_or_guess_labels(x, kwargs) + self.targeted_attack = False + else: + _, nb_classes = self.get_or_guess_labels(x, kwargs) + self.targeted_attack = True + + attack = LBFGS_impl( + self.sess, + x, + self.model.get_logits(x), + self.y_target, + self.targeted_attack, + self.binary_search_steps, + self.max_iterations, + self.initial_const, + self.clip_min, + self.clip_max, + nb_classes, + self.batch_size, + ) + + def lbfgs_wrap(x_val, y_val): + """ + Wrapper creating TensorFlow interface for use with py_func + """ + return np.array(attack.attack(x_val, y_val), dtype=self.np_dtype) + + wrap = tf.py_func(lbfgs_wrap, [x, self.y_target], self.tf_dtype) + wrap.set_shape(x.get_shape()) + + return wrap + + def parse_params( + self, + y_target=None, + batch_size=1, + binary_search_steps=5, + max_iterations=1000, + initial_const=1e-2, + clip_min=0, + clip_max=1, + ): + """ + :param y_target: (optional) A tensor with the one-hot target labels. + :param batch_size: The number of inputs to include in a batch and + process simultaneously. + :param binary_search_steps: The number of times we perform binary + search to find the optimal tradeoff- + constant between norm of the purturbation + and cross-entropy loss of classification. + :param max_iterations: The maximum number of iterations. + :param initial_const: The initial tradeoff-constant to use to tune the + relative importance of size of the perturbation + and cross-entropy loss of the classification. + :param clip_min: (optional float) Minimum input component value + :param clip_max: (optional float) Maximum input component value + """ + self.y_target = y_target + self.batch_size = batch_size + self.binary_search_steps = binary_search_steps + self.max_iterations = max_iterations + self.initial_const = initial_const + self.clip_min = clip_min + self.clip_max = clip_max + + +class LBFGS_impl(object): + """ + Return a tensor that constructs adversarial examples for the given + input. Generate uses tf.py_func in order to operate over tensors. + :param sess: a TF session. + :param x: A tensor with the inputs. + :param logits: A tensor with model's output logits. + :param targeted_label: A tensor with the target labels. + :param binary_search_steps: The number of times we perform binary + search to find the optimal tradeoff- + constant between norm of the purturbation + and cross-entropy loss of classification. + :param max_iterations: The maximum number of iterations. + :param initial_const: The initial tradeoff-constant to use to tune the + relative importance of size of the purturbation + and cross-entropy loss of the classification. + :param clip_min: Minimum input component value + :param clip_max: Maximum input component value + :param num_labels: The number of classes in the model's output. + :param batch_size: Number of attacks to run simultaneously. + """ + + def __init__( + self, + sess, + x, + logits, + targeted_label, + targeted_attack, + binary_search_steps, + max_iterations, + initial_const, + clip_min, + clip_max, + nb_classes, + batch_size, + ): + self.sess = sess + self.x = x + self.logits = logits + assert logits.op.type != "Softmax" + self.targeted_label = targeted_label + self.targeted_attack = targeted_attack + self.binary_search_steps = binary_search_steps + self.max_iterations = max_iterations + self.initial_const = initial_const + self.clip_min = clip_min + self.clip_max = clip_max + self.batch_size = batch_size + + self.repeat = self.binary_search_steps >= 10 + self.shape = tuple([self.batch_size] + list(self.x.get_shape().as_list()[1:])) + self.ori_img = tf.Variable(np.zeros(self.shape), dtype=tf_dtype, name="ori_img") + self.const = tf.Variable( + np.zeros(self.batch_size), dtype=tf_dtype, name="const" + ) + + self.score = softmax_cross_entropy_with_logits( + labels=self.targeted_label, logits=self.logits + ) + self.l2dist = reduce_sum(tf.square(self.x - self.ori_img)) + # small self.const will result small adversarial perturbation + # targeted attack aims at minimize loss against target label + # untargeted attack aims at maximize loss against True label + if self.targeted_attack: + self.loss = reduce_sum(self.score * self.const) + self.l2dist + else: + self.loss = -reduce_sum(self.score * self.const) + self.l2dist + (self.grad,) = tf.gradients(self.loss, self.x) + + def attack(self, x_val, targets): + """ + Perform the attack on the given instance for the given targets. + """ + + def lbfgs_objective(adv_x, self, targets, oimgs, CONST): + """ returns the function value and the gradient for fmin_l_bfgs_b """ + loss = self.sess.run( + self.loss, + feed_dict={ + self.x: adv_x.reshape(oimgs.shape), + self.targeted_label: targets, + self.ori_img: oimgs, + self.const: CONST, + }, + ) + grad = self.sess.run( + self.grad, + feed_dict={ + self.x: adv_x.reshape(oimgs.shape), + self.targeted_label: targets, + self.ori_img: oimgs, + self.const: CONST, + }, + ) + return loss, grad.flatten().astype(float) + + def attack_success(out, target, targeted_attack): + """ returns attack result """ + if targeted_attack: + return out == target + else: + return out != target + + # begin the main part for the attack + from scipy.optimize import fmin_l_bfgs_b + + oimgs = np.clip(x_val, self.clip_min, self.clip_max) + CONST = np.ones(self.batch_size) * self.initial_const + + # set the lower and upper bounds accordingly + lower_bound = np.zeros(self.batch_size) + upper_bound = np.ones(self.batch_size) * 1e10 + + # set the box constraints for the optimization function + clip_min = self.clip_min * np.ones(oimgs.shape[:]) + clip_max = self.clip_max * np.ones(oimgs.shape[:]) + clip_bound = list(zip(clip_min.flatten(), clip_max.flatten())) + + # placeholders for the best l2 and instance attack found so far + o_bestl2 = [1e10] * self.batch_size + o_bestattack = np.copy(oimgs) + + for outer_step in range(self.binary_search_steps): + _logger.debug( + " Binary search step %s of %s", outer_step, self.binary_search_steps + ) + + # The last iteration (if we run many steps) repeat the search once. + if self.repeat and outer_step == self.binary_search_steps - 1: + CONST = upper_bound + + # optimization function + adv_x, _, __ = fmin_l_bfgs_b( + lbfgs_objective, + oimgs.flatten().astype(float), + args=(self, targets, oimgs, CONST), + bounds=clip_bound, + maxiter=self.max_iterations, + iprint=0, + ) + + adv_x = adv_x.reshape(oimgs.shape) + assert ( + np.amax(adv_x) <= self.clip_max and np.amin(adv_x) >= self.clip_min + ), "fmin_l_bfgs_b returns are invalid" + + # adjust the best result (i.e., the adversarial example with the + # smallest perturbation in terms of L_2 norm) found so far + preds = np.atleast_1d( + utils_tf.model_argmax(self.sess, self.x, self.logits, adv_x) + ) + _logger.debug("predicted labels are %s", preds) + + l2s = np.zeros(self.batch_size) + for i in range(self.batch_size): + l2s[i] = np.sum(np.square(adv_x[i] - oimgs[i])) + + for e, (l2, pred, ii) in enumerate(zip(l2s, preds, adv_x)): + if l2 < o_bestl2[e] and attack_success( + pred, np.argmax(targets[e]), self.targeted_attack + ): + o_bestl2[e] = l2 + o_bestattack[e] = ii + + # adjust the constant as needed + for e in range(self.batch_size): + if attack_success( + preds[e], np.argmax(targets[e]), self.targeted_attack + ): + # success, divide const by two + upper_bound[e] = min(upper_bound[e], CONST[e]) + if upper_bound[e] < 1e9: + CONST[e] = (lower_bound[e] + upper_bound[e]) / 2 + else: + # failure, either multiply by 10 if no solution found yet + # or do binary search with the known upper bound + lower_bound[e] = max(lower_bound[e], CONST[e]) + if upper_bound[e] < 1e9: + CONST[e] = (lower_bound[e] + upper_bound[e]) / 2 + else: + CONST[e] *= 10 + + _logger.debug( + " Successfully generated adversarial examples " + "on %s of %s instances.", + sum(upper_bound < 1e9), + self.batch_size, + ) + o_bestl2 = np.array(o_bestl2) + mean = np.mean(np.sqrt(o_bestl2[o_bestl2 < 1e9])) + _logger.debug(" Mean successful distortion: {:.4g}".format(mean)) + + # return the best solution found + o_bestl2 = np.array(o_bestl2) + return o_bestattack diff --git a/cleverhans_v3.1.0/cleverhans/attacks/madry_et_al.py b/cleverhans_v3.1.0/cleverhans/attacks/madry_et_al.py new file mode 100644 index 000000000..c8c1fbe4b --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans/attacks/madry_et_al.py @@ -0,0 +1,16 @@ +""" +The MadryEtAl attack +""" + +from cleverhans.attacks.projected_gradient_descent import ProjectedGradientDescent + + +class MadryEtAl(ProjectedGradientDescent): + """ + The attack from Madry et al 2017 + """ + + def __init__(self, model, sess=None, dtypestr="float32", **kwargs): + super(MadryEtAl, self).__init__( + model, sess=sess, dtypestr=dtypestr, default_rand_init=True, **kwargs + ) diff --git a/cleverhans_v3.1.0/cleverhans/attacks/max_confidence.py b/cleverhans_v3.1.0/cleverhans/attacks/max_confidence.py new file mode 100644 index 000000000..a274cce9d --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans/attacks/max_confidence.py @@ -0,0 +1,123 @@ +"""The MaxConfidence attack. +""" +import warnings + +import tensorflow as tf + +from cleverhans.attacks.attack import Attack +from cleverhans.attacks.projected_gradient_descent import ProjectedGradientDescent +from cleverhans.model import Model + + +class MaxConfidence(Attack): + """ + The MaxConfidence attack. + + An attack designed for use against models that use confidence thresholding + as a defense. + If the underlying optimizer is optimal, this attack procedure gives the + optimal failure rate for every confidence threshold t > 0.5. + + Publication: https://openreview.net/forum?id=H1g0piA9tQ + + :param model: cleverhans.model.Model + :param sess: optional tf.session.Session + :param base_attacker: cleverhans.attacks.Attack + """ + + def __init__(self, model, sess=None, base_attacker=None): + if not isinstance(model, Model): + raise TypeError( + "Model must be cleverhans.model.Model, got " + str(type(model)) + ) + + super(MaxConfidence, self).__init__(model, sess) + if base_attacker is None: + self.base_attacker = ProjectedGradientDescent(model, sess=sess) + else: + self.base_attacker = base_attacker + self.structural_kwargs = self.base_attacker.structural_kwargs + self.feedable_kwargs = self.base_attacker.feedable_kwargs + + def generate(self, x, **kwargs): + """ + Generate symbolic graph for adversarial examples and return. + + :param x: The model's symbolic inputs. + :param kwargs: Keyword arguments for the base attacker + """ + + assert self.parse_params(**kwargs) + labels, _nb_classes = self.get_or_guess_labels(x, kwargs) + adv_x = self.attack(x, labels) + + return adv_x + + def parse_params(self, y=None, nb_classes=10, **kwargs): + self.y = y + self.nb_classes = nb_classes + self.params = kwargs + if len(kwargs.keys()) > 0: + warnings.warn( + "kwargs is unused and will be removed on or after " "2019-04-26." + ) + return True + + def attack(self, x, true_y): + """ + Runs the untargeted attack. + :param x: The input + :param true_y: The correct label for `x`. This attack aims to produce misclassification. + """ + adv_x_cls = [] + prob_cls = [] + m = tf.shape(x)[0] + true_y_idx = tf.argmax(true_y, axis=1) + + expanded_x = tf.concat([x] * self.nb_classes, axis=0) + target_ys = [ + tf.to_float(tf.one_hot(tf.ones(m, dtype=tf.int32) * cls, self.nb_classes)) + for cls in range(self.nb_classes) + ] + target_y = tf.concat(target_ys, axis=0) + adv_x_cls = self.attack_class(expanded_x, target_y) + expanded_all_probs = self.model.get_probs(adv_x_cls) + + adv_x_list = tf.split(adv_x_cls, self.nb_classes) + all_probs_list = tf.split(expanded_all_probs, self.nb_classes) + + for cls in range(self.nb_classes): + target_y = target_ys[cls] + all_probs = all_probs_list[cls] + # We don't actually care whether we hit the target class. + # We care about the probability of the most likely wrong class + cur_prob_cls = tf.reduce_max(all_probs - true_y, axis=1) + # Knock out examples that are correctly classified. + # This is not needed to be optimal for t >= 0.5, but may as well do it + # to get better failure rate at lower thresholds. + chosen_cls = tf.argmax(all_probs, axis=1) + eligible = tf.to_float(tf.not_equal(true_y_idx, chosen_cls)) + cur_prob_cls = cur_prob_cls * eligible + prob_cls.append(cur_prob_cls) + + probs = tf.concat([tf.expand_dims(e, 1) for e in prob_cls], axis=1) + # Don't need to censor here because we knocked out the true class above + # probs = probs - true_y + most_confident = tf.argmax(probs, axis=1) + fused_mask = tf.one_hot(most_confident, self.nb_classes) + masks = tf.split(fused_mask, num_or_size_splits=self.nb_classes, axis=1) + shape = [m] + [1] * (len(x.get_shape()) - 1) + reshaped_masks = [tf.reshape(mask, shape) for mask in masks] + out = sum(adv_x * rmask for adv_x, rmask in zip(adv_x_list, reshaped_masks)) + return out + + def attack_class(self, x, target_y): + """ + Run the attack on a specific target class. + :param x: tf Tensor. The input example. + :param target_y: tf Tensor. The attacker's desired target class. + Returns: + A targeted adversarial example, intended to be classified as the target class. + """ + adv = self.base_attacker.generate(x, y_target=target_y, **self.params) + return adv diff --git a/cleverhans_v3.1.0/cleverhans/attacks/momentum_iterative_method.py b/cleverhans_v3.1.0/cleverhans/attacks/momentum_iterative_method.py new file mode 100644 index 000000000..d1f6bdf43 --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans/attacks/momentum_iterative_method.py @@ -0,0 +1,199 @@ +"""The MomentumIterativeMethod attack. +""" + +import warnings + +import numpy as np +import tensorflow as tf + +from cleverhans.attacks.attack import Attack +from cleverhans.attacks.fast_gradient_method import optimize_linear +from cleverhans.compat import reduce_sum, reduce_mean, softmax_cross_entropy_with_logits +from cleverhans import utils_tf + + +class MomentumIterativeMethod(Attack): + """ + The Momentum Iterative Method (Dong et al. 2017). This method won + the first places in NIPS 2017 Non-targeted Adversarial Attacks and + Targeted Adversarial Attacks. The original paper used hard labels + for this attack; no label smoothing. + Paper link: https://arxiv.org/pdf/1710.06081.pdf + + :param model: cleverhans.model.Model + :param sess: optional tf.Session + :param dtypestr: dtype of the data + :param kwargs: passed through to super constructor + """ + + def __init__(self, model, sess=None, dtypestr="float32", **kwargs): + """ + Create a MomentumIterativeMethod instance. + Note: the model parameter should be an instance of the + cleverhans.model.Model abstraction provided by CleverHans. + """ + + super(MomentumIterativeMethod, self).__init__(model, sess, dtypestr, **kwargs) + self.feedable_kwargs = ( + "eps", + "eps_iter", + "y", + "y_target", + "clip_min", + "clip_max", + ) + self.structural_kwargs = [ + "ord", + "nb_iter", + "decay_factor", + "sanity_checks", + "clip_grad", + ] + + def generate(self, x, **kwargs): + """ + Generate symbolic graph for adversarial examples and return. + + :param x: The model's symbolic inputs. + :param kwargs: Keyword arguments. See `parse_params` for documentation. + """ + # Parse and save attack-specific parameters + assert self.parse_params(**kwargs) + + asserts = [] + + # If a data range was specified, check that the input was in that range + if self.clip_min is not None: + asserts.append( + utils_tf.assert_greater_equal(x, tf.cast(self.clip_min, x.dtype)) + ) + + if self.clip_max is not None: + asserts.append( + utils_tf.assert_less_equal(x, tf.cast(self.clip_max, x.dtype)) + ) + + # Initialize loop variables + momentum = tf.zeros_like(x) + adv_x = x + + # Fix labels to the first model predictions for loss computation + y, _nb_classes = self.get_or_guess_labels(x, kwargs) + y = y / reduce_sum(y, 1, keepdims=True) + targeted = self.y_target is not None + + def cond(i, _, __): + """Iterate until number of iterations completed""" + return tf.less(i, self.nb_iter) + + def body(i, ax, m): + """Do a momentum step""" + logits = self.model.get_logits(ax) + loss = softmax_cross_entropy_with_logits(labels=y, logits=logits) + if targeted: + loss = -loss + + # Define gradient of loss wrt input + (grad,) = tf.gradients(loss, ax) + + # Normalize current gradient and add it to the accumulated gradient + red_ind = list(range(1, len(grad.get_shape()))) + avoid_zero_div = tf.cast(1e-12, grad.dtype) + grad = grad / tf.maximum( + avoid_zero_div, reduce_mean(tf.abs(grad), red_ind, keepdims=True) + ) + m = self.decay_factor * m + grad + + optimal_perturbation = optimize_linear(m, self.eps_iter, self.ord) + if self.ord == 1: + raise NotImplementedError( + "This attack hasn't been tested for ord=1." + "It's not clear that FGM makes a good inner " + "loop step for iterative optimization since " + "it updates just one coordinate at a time." + ) + + # Update and clip adversarial example in current iteration + ax = ax + optimal_perturbation + ax = x + utils_tf.clip_eta(ax - x, self.ord, self.eps) + + if self.clip_min is not None and self.clip_max is not None: + ax = utils_tf.clip_by_value(ax, self.clip_min, self.clip_max) + + ax = tf.stop_gradient(ax) + + return i + 1, ax, m + + _, adv_x, _ = tf.while_loop( + cond, + body, + (tf.zeros([]), adv_x, momentum), + back_prop=True, + maximum_iterations=self.nb_iter, + ) + + if self.sanity_checks: + with tf.control_dependencies(asserts): + adv_x = tf.identity(adv_x) + + return adv_x + + def parse_params( + self, + eps=0.3, + eps_iter=0.06, + nb_iter=10, + y=None, + ord=np.inf, + decay_factor=1.0, + clip_min=None, + clip_max=None, + y_target=None, + sanity_checks=True, + **kwargs + ): + """ + Take in a dictionary of parameters and applies attack-specific checks + before saving them as attributes. + + Attack-specific parameters: + + :param eps: (optional float) maximum distortion of adversarial example + compared to original input + :param eps_iter: (optional float) step size for each attack iteration + :param nb_iter: (optional int) Number of attack iterations. + :param y: (optional) A tensor with the true labels. + :param y_target: (optional) A tensor with the labels to target. Leave + y_target=None if y is also set. Labels should be + one-hot-encoded. + :param ord: (optional) Order of the norm (mimics Numpy). + Possible values: np.inf, 1 or 2. + :param decay_factor: (optional) Decay factor for the momentum term. + :param clip_min: (optional float) Minimum input component value + :param clip_max: (optional float) Maximum input component value + """ + + # Save attack-specific parameters + self.eps = eps + self.eps_iter = eps_iter + self.nb_iter = nb_iter + self.y = y + self.y_target = y_target + self.ord = ord + self.decay_factor = decay_factor + self.clip_min = clip_min + self.clip_max = clip_max + self.sanity_checks = sanity_checks + + if self.y is not None and self.y_target is not None: + raise ValueError("Must not set both y and y_target") + # Check if order of the norm is acceptable given current implementation + if self.ord not in [np.inf, 1, 2]: + raise ValueError("Norm order must be either np.inf, 1, or 2.") + + if len(kwargs.keys()) > 0: + warnings.warn( + "kwargs is unused and will be removed on or after " "2019-04-26." + ) + + return True diff --git a/cleverhans_v3.1.0/cleverhans/attacks/noise.py b/cleverhans_v3.1.0/cleverhans/attacks/noise.py new file mode 100644 index 000000000..799b1a468 --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans/attacks/noise.py @@ -0,0 +1,86 @@ +"""The Noise attack + +""" +import warnings + +import numpy as np +import tensorflow as tf + +from cleverhans.attacks.attack import Attack + + +class Noise(Attack): + """ + A weak attack that just picks a random point in the attacker's action space. + When combined with an attack bundling function, this can be used to implement + random search. + + References: + https://arxiv.org/abs/1802.00420 recommends random search to help identify + gradient masking. + https://openreview.net/forum?id=H1g0piA9tQ recommends using noise as part + of an attack bundling recipe combining many different optimizers to yield + a stronger optimizer. + + :param model: cleverhans.model.Model + :param sess: optional tf.Session + :param dtypestr: dtype of the data + :param kwargs: passed through to super constructor + """ + + def __init__(self, model, sess=None, dtypestr="float32", **kwargs): + + super(Noise, self).__init__(model, sess=sess, dtypestr=dtypestr, **kwargs) + self.feedable_kwargs = ("eps", "clip_min", "clip_max") + self.structural_kwargs = ["ord"] + + def generate(self, x, **kwargs): + """ + Generate symbolic graph for adversarial examples and return. + + :param x: The model's symbolic inputs. + :param kwargs: See `parse_params` + """ + # Parse and save attack-specific parameters + assert self.parse_params(**kwargs) + + if self.ord != np.inf: + raise NotImplementedError(self.ord) + eta = tf.random_uniform(tf.shape(x), -self.eps, self.eps, dtype=self.tf_dtype) + adv_x = x + eta + if self.clip_min is not None or self.clip_max is not None: + assert self.clip_min is not None and self.clip_max is not None + adv_x = tf.clip_by_value(adv_x, self.clip_min, self.clip_max) + + return adv_x + + def parse_params(self, eps=0.3, ord=np.inf, clip_min=None, clip_max=None, **kwargs): + """ + Take in a dictionary of parameters and applies attack-specific checks + before saving them as attributes. + + Attack-specific parameters: + + :param eps: (optional float) maximum distortion of adversarial example + compared to original input + :param ord: (optional) Order of the norm (mimics Numpy). + Possible values: np.inf + :param clip_min: (optional float) Minimum input component value + :param clip_max: (optional float) Maximum input component value + """ + + # Save attack-specific parameters + self.eps = eps + self.ord = ord + self.clip_min = clip_min + self.clip_max = clip_max + + # Check if order of the norm is acceptable given current implementation + if self.ord not in [np.inf]: + raise ValueError("Norm order must be np.inf") + if len(kwargs.keys()) > 0: + warnings.warn( + "kwargs is unused and will be removed on or after " "2019-04-26." + ) + + return True diff --git a/cleverhans_v3.1.0/cleverhans/attacks/projected_gradient_descent.py b/cleverhans_v3.1.0/cleverhans/attacks/projected_gradient_descent.py new file mode 100644 index 000000000..eacec526b --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans/attacks/projected_gradient_descent.py @@ -0,0 +1,291 @@ +""" +The ProjectedGradientDescent attack. +""" + +import warnings + +import numpy as np +import tensorflow as tf + +from cleverhans.attacks.attack import Attack +from cleverhans.attacks.fast_gradient_method import FastGradientMethod +from cleverhans import utils_tf +from cleverhans.compat import softmax_cross_entropy_with_logits +from cleverhans.utils_tf import clip_eta, random_lp_vector + + +class ProjectedGradientDescent(Attack): + """ + This class implements either the Basic Iterative Method + (Kurakin et al. 2016) when rand_init is set to 0. or the + Madry et al. (2017) method when rand_minmax is larger than 0. + Paper link (Kurakin et al. 2016): https://arxiv.org/pdf/1607.02533.pdf + Paper link (Madry et al. 2017): https://arxiv.org/pdf/1706.06083.pdf + + :param model: cleverhans.model.Model + :param sess: optional tf.Session + :param dtypestr: dtype of the data + :param default_rand_init: whether to use random initialization by default + :param kwargs: passed through to super constructor + """ + + FGM_CLASS = FastGradientMethod + + def __init__( + self, model, sess=None, dtypestr="float32", default_rand_init=True, **kwargs + ): + """ + Create a ProjectedGradientDescent instance. + Note: the model parameter should be an instance of the + cleverhans.model.Model abstraction provided by CleverHans. + """ + + super(ProjectedGradientDescent, self).__init__( + model, sess=sess, dtypestr=dtypestr, **kwargs + ) + self.feedable_kwargs = ( + "eps", + "eps_iter", + "y", + "y_target", + "clip_min", + "clip_max", + ) + self.structural_kwargs = [ + "ord", + "nb_iter", + "rand_init", + "clip_grad", + "sanity_checks", + "loss_fn", + ] + self.default_rand_init = default_rand_init + + def generate(self, x, **kwargs): + """ + Generate symbolic graph for adversarial examples and return. + + :param x: The model's symbolic inputs. + :param kwargs: See `parse_params` + """ + # Parse and save attack-specific parameters + assert self.parse_params(**kwargs) + + asserts = [] + + # If a data range was specified, check that the input was in that range + if self.clip_min is not None: + asserts.append( + utils_tf.assert_greater_equal(x, tf.cast(self.clip_min, x.dtype)) + ) + + if self.clip_max is not None: + asserts.append( + utils_tf.assert_less_equal(x, tf.cast(self.clip_max, x.dtype)) + ) + + # Initialize loop variables + if self.rand_init: + eta = random_lp_vector( + tf.shape(x), + self.ord, + tf.cast(self.rand_init_eps, x.dtype), + dtype=x.dtype, + ) + else: + eta = tf.zeros(tf.shape(x)) + + # Clip eta + eta = clip_eta(eta, self.ord, self.eps) + adv_x = x + eta + if self.clip_min is not None or self.clip_max is not None: + adv_x = utils_tf.clip_by_value(adv_x, self.clip_min, self.clip_max) + + if self.y_target is not None: + y = self.y_target + targeted = True + elif self.y is not None: + y = self.y + targeted = False + else: + model_preds = self.model.get_probs(x) + preds_max = tf.reduce_max(model_preds, 1, keepdims=True) + y = tf.to_float(tf.equal(model_preds, preds_max)) + y = tf.stop_gradient(y) + targeted = False + del model_preds + + y_kwarg = "y_target" if targeted else "y" + + fgm_params = { + "eps": self.eps_iter, + y_kwarg: y, + "ord": self.ord, + "loss_fn": self.loss_fn, + "clip_min": self.clip_min, + "clip_max": self.clip_max, + "clip_grad": self.clip_grad, + } + if self.ord == 1: + raise NotImplementedError( + "FGM is not a good inner loop step for PGD " + " when ord=1, because ord=1 FGM changes only " + " one pixel at a time. Use the SparseL1Descent " + " attack instead, which allows fine-grained " + " control over the sparsity of the gradient " + " updates." + ) + + # Use getattr() to avoid errors in eager execution attacks + FGM = self.FGM_CLASS( + self.model, sess=getattr(self, "sess", None), dtypestr=self.dtypestr + ) + + def cond(i, _): + """Iterate until requested number of iterations is completed""" + return tf.less(i, self.nb_iter) + + def body(i, adv_x): + """Do a projected gradient step""" + adv_x = FGM.generate(adv_x, **fgm_params) + + # Clipping perturbation eta to self.ord norm ball + eta = adv_x - x + eta = clip_eta(eta, self.ord, self.eps) + adv_x = x + eta + + # Redo the clipping. + # FGM already did it, but subtracting and re-adding eta can add some + # small numerical error. + if self.clip_min is not None or self.clip_max is not None: + adv_x = utils_tf.clip_by_value(adv_x, self.clip_min, self.clip_max) + + return i + 1, adv_x + + _, adv_x = tf.while_loop( + cond, + body, + (tf.zeros([]), adv_x), + back_prop=True, + maximum_iterations=self.nb_iter, + ) + + # Asserts run only on CPU. + # When multi-GPU eval code tries to force all PGD ops onto GPU, this + # can cause an error. + common_dtype = tf.float32 + asserts.append( + utils_tf.assert_less_equal( + tf.cast(self.eps_iter, dtype=common_dtype), + tf.cast(self.eps, dtype=common_dtype), + ) + ) + if self.ord == np.inf and self.clip_min is not None: + # The 1e-6 is needed to compensate for numerical error. + # Without the 1e-6 this fails when e.g. eps=.2, clip_min=.5, + # clip_max=.7 + asserts.append( + utils_tf.assert_less_equal( + tf.cast(self.eps, x.dtype), + 1e-6 + + tf.cast(self.clip_max, x.dtype) + - tf.cast(self.clip_min, x.dtype), + ) + ) + + if self.sanity_checks: + with tf.control_dependencies(asserts): + adv_x = tf.identity(adv_x) + + return adv_x + + def parse_params( + self, + eps=0.3, + eps_iter=0.05, + nb_iter=10, + y=None, + ord=np.inf, + loss_fn=softmax_cross_entropy_with_logits, + clip_min=None, + clip_max=None, + y_target=None, + rand_init=None, + rand_init_eps=None, + clip_grad=False, + sanity_checks=True, + **kwargs + ): + """ + Take in a dictionary of parameters and applies attack-specific checks + before saving them as attributes. + + Attack-specific parameters: + + :param eps: (optional float) maximum distortion of adversarial example + compared to original input + :param eps_iter: (optional float) step size for each attack iteration + :param nb_iter: (optional int) Number of attack iterations. + :param y: (optional) A tensor with the true labels. + :param y_target: (optional) A tensor with the labels to target. Leave + y_target=None if y is also set. Labels should be + one-hot-encoded. + :param ord: (optional) Order of the norm (mimics Numpy). + Possible values: np.inf, 1 or 2. + :param loss_fn: Loss function that takes (labels, logits) as arguments and returns loss + :param clip_min: (optional float) Minimum input component value + :param clip_max: (optional float) Maximum input component value + :param rand_init: (optional) Start the gradient descent from a point chosen + uniformly at random in the norm ball of radius + rand_init_eps + :param rand_init_eps: (optional float) size of the norm ball from which + the initial starting point is chosen. Defaults to eps + :param clip_grad: (optional bool) Ignore gradient components at positions + where the input is already at the boundary of the domain, + and the update step will get clipped out. + :param sanity_checks: bool Insert tf asserts checking values + (Some tests need to run with no sanity checks because the + tests intentionally configure the attack strangely) + """ + + # Save attack-specific parameters + self.eps = eps + if rand_init is None: + rand_init = self.default_rand_init + self.rand_init = rand_init + if rand_init_eps is None: + rand_init_eps = self.eps + self.rand_init_eps = rand_init_eps + + self.eps_iter = eps_iter + self.nb_iter = nb_iter + self.y = y + self.y_target = y_target + self.ord = ord + self.loss_fn = loss_fn + self.clip_min = clip_min + self.clip_max = clip_max + self.clip_grad = clip_grad + + if isinstance(eps, float) and isinstance(eps_iter, float): + # If these are both known at compile time, we can check before anything + # is run. If they are tf, we can't check them yet. + assert eps_iter <= eps, (eps_iter, eps) + + if self.y is not None and self.y_target is not None: + raise ValueError("Must not set both y and y_target") + # Check if order of the norm is acceptable given current implementation + if self.ord not in [np.inf, 1, 2]: + raise ValueError("Norm order must be either np.inf, 1, or 2.") + + if self.clip_grad and (self.clip_min is None or self.clip_max is None): + raise ValueError("Must set clip_min and clip_max if clip_grad is set") + + self.sanity_checks = sanity_checks + + if len(kwargs.keys()) > 0: + warnings.warn( + "kwargs is unused and will be removed on or after " "2019-04-26." + ) + + return True diff --git a/cleverhans_v3.1.0/cleverhans/attacks/saliency_map_method.py b/cleverhans_v3.1.0/cleverhans/attacks/saliency_map_method.py new file mode 100644 index 000000000..59bce8e08 --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans/attacks/saliency_map_method.py @@ -0,0 +1,296 @@ +"""The SalienceMapMethod attack +""" +# pylint: disable=missing-docstring +import warnings + +import numpy as np +from six.moves import xrange +import tensorflow as tf + +from cleverhans.attacks.attack import Attack +from cleverhans.compat import reduce_sum, reduce_max, reduce_any + +tf_dtype = tf.as_dtype("float32") + + +class SaliencyMapMethod(Attack): + """ + The Jacobian-based Saliency Map Method (Papernot et al. 2016). + Paper link: https://arxiv.org/pdf/1511.07528.pdf + + :param model: cleverhans.model.Model + :param sess: optional tf.Session + :param dtypestr: dtype of the data + :param kwargs: passed through to super constructor + + :note: When not using symbolic implementation in `generate`, `sess` should + be provided + """ + + def __init__(self, model, sess=None, dtypestr="float32", **kwargs): + """ + Create a SaliencyMapMethod instance. + Note: the model parameter should be an instance of the + cleverhans.model.Model abstraction provided by CleverHans. + """ + + super(SaliencyMapMethod, self).__init__(model, sess, dtypestr, **kwargs) + + self.feedable_kwargs = ("y_target",) + self.structural_kwargs = [ + "theta", + "gamma", + "clip_max", + "clip_min", + "symbolic_impl", + ] + + def generate(self, x, **kwargs): + """ + Generate symbolic graph for adversarial examples and return. + + :param x: The model's symbolic inputs. + :param kwargs: See `parse_params` + """ + # Parse and save attack-specific parameters + assert self.parse_params(**kwargs) + + if self.symbolic_impl: + # Create random targets if y_target not provided + if self.y_target is None: + from random import randint + + def random_targets(gt): + result = gt.copy() + nb_s = gt.shape[0] + nb_classes = gt.shape[1] + + for i in range(nb_s): + result[i, :] = np.roll(result[i, :], randint(1, nb_classes - 1)) + + return result + + labels, nb_classes = self.get_or_guess_labels(x, kwargs) + self.y_target = tf.py_func(random_targets, [labels], self.tf_dtype) + self.y_target.set_shape([None, nb_classes]) + + x_adv = jsma_symbolic( + x, + model=self.model, + y_target=self.y_target, + theta=self.theta, + gamma=self.gamma, + clip_min=self.clip_min, + clip_max=self.clip_max, + ) + else: + raise NotImplementedError( + "The jsma_batch function has been removed." + " The symbolic_impl argument to SaliencyMapMethod will be removed" + " on 2019-07-18 or after. Any code that depends on the non-symbolic" + " implementation of the JSMA should be revised. Consider using" + " SaliencyMapMethod.generate_np() instead." + ) + + return x_adv + + def parse_params( + self, + theta=1.0, + gamma=1.0, + clip_min=0.0, + clip_max=1.0, + y_target=None, + symbolic_impl=True, + **kwargs + ): + """ + Take in a dictionary of parameters and applies attack-specific checks + before saving them as attributes. + + Attack-specific parameters: + + :param theta: (optional float) Perturbation introduced to modified + components (can be positive or negative) + :param gamma: (optional float) Maximum percentage of perturbed features + :param clip_min: (optional float) Minimum component value for clipping + :param clip_max: (optional float) Maximum component value for clipping + :param y_target: (optional) Target tensor if the attack is targeted + """ + self.theta = theta + self.gamma = gamma + self.clip_min = clip_min + self.clip_max = clip_max + self.y_target = y_target + self.symbolic_impl = symbolic_impl + + if len(kwargs.keys()) > 0: + warnings.warn( + "kwargs is unused and will be removed on or after " "2019-04-26." + ) + + return True + + +def jsma_batch(*args, **kwargs): + raise NotImplementedError( + "The jsma_batch function has been removed. Any code that depends on it should be revised." + ) + + +def jsma_symbolic(x, y_target, model, theta, gamma, clip_min, clip_max): + """ + TensorFlow implementation of the JSMA (see https://arxiv.org/abs/1511.07528 + for details about the algorithm design choices). + + :param x: the input placeholder + :param y_target: the target tensor + :param model: a cleverhans.model.Model object. + :param theta: delta for each feature adjustment + :param gamma: a float between 0 - 1 indicating the maximum distortion + percentage + :param clip_min: minimum value for components of the example returned + :param clip_max: maximum value for components of the example returned + :return: a tensor for the adversarial example + """ + + nb_classes = int(y_target.shape[-1].value) + nb_features = int(np.product(x.shape[1:]).value) + + if x.dtype == tf.float32 and y_target.dtype == tf.int64: + y_target = tf.cast(y_target, tf.int32) + + if x.dtype == tf.float32 and y_target.dtype == tf.float64: + warnings.warn( + "Downcasting labels---this should be harmless unless" " they are smoothed" + ) + y_target = tf.cast(y_target, tf.float32) + + max_iters = np.floor(nb_features * gamma / 2) + increase = bool(theta > 0) + + tmp = np.ones((nb_features, nb_features), int) + np.fill_diagonal(tmp, 0) + zero_diagonal = tf.constant(tmp, tf_dtype) + + # Compute our initial search domain. We optimize the initial search domain + # by removing all features that are already at their maximum values (if + # increasing input features---otherwise, at their minimum value). + if increase: + search_domain = tf.reshape(tf.cast(x < clip_max, tf_dtype), [-1, nb_features]) + else: + search_domain = tf.reshape(tf.cast(x > clip_min, tf_dtype), [-1, nb_features]) + + # Loop variables + # x_in: the tensor that holds the latest adversarial outputs that are in + # progress. + # y_in: the tensor for target labels + # domain_in: the tensor that holds the latest search domain + # cond_in: the boolean tensor to show if more iteration is needed for + # generating adversarial samples + def condition(x_in, y_in, domain_in, i_in, cond_in): + # Repeat the loop until we have achieved misclassification or + # reaches the maximum iterations + return tf.logical_and(tf.less(i_in, max_iters), cond_in) + + # Same loop variables as above + def body(x_in, y_in, domain_in, i_in, cond_in): + # Create graph for model logits and predictions + logits = model.get_logits(x_in) + preds = tf.nn.softmax(logits) + preds_onehot = tf.one_hot(tf.argmax(preds, axis=1), depth=nb_classes) + + # create the Jacobian graph + list_derivatives = [] + for class_ind in xrange(nb_classes): + derivatives = tf.gradients(logits[:, class_ind], x_in) + list_derivatives.append(derivatives[0]) + grads = tf.reshape( + tf.stack(list_derivatives), shape=[nb_classes, -1, nb_features] + ) + + # Compute the Jacobian components + # To help with the computation later, reshape the target_class + # and other_class to [nb_classes, -1, 1]. + # The last dimention is added to allow broadcasting later. + target_class = tf.reshape( + tf.transpose(y_in, perm=[1, 0]), shape=[nb_classes, -1, 1] + ) + other_classes = tf.cast(tf.not_equal(target_class, 1), tf_dtype) + + grads_target = reduce_sum(grads * target_class, axis=0) + grads_other = reduce_sum(grads * other_classes, axis=0) + + # Remove the already-used input features from the search space + # Subtract 2 times the maximum value from those value so that + # they won't be picked later + increase_coef = (4 * int(increase) - 2) * tf.cast( + tf.equal(domain_in, 0), tf_dtype + ) + + target_tmp = grads_target + target_tmp -= increase_coef * reduce_max( + tf.abs(grads_target), axis=1, keepdims=True + ) + target_sum = tf.reshape(target_tmp, shape=[-1, nb_features, 1]) + tf.reshape( + target_tmp, shape=[-1, 1, nb_features] + ) + + other_tmp = grads_other + other_tmp += increase_coef * reduce_max( + tf.abs(grads_other), axis=1, keepdims=True + ) + other_sum = tf.reshape(other_tmp, shape=[-1, nb_features, 1]) + tf.reshape( + other_tmp, shape=[-1, 1, nb_features] + ) + + # Create a mask to only keep features that match conditions + if increase: + scores_mask = (target_sum > 0) & (other_sum < 0) + else: + scores_mask = (target_sum < 0) & (other_sum > 0) + + # Create a 2D numpy array of scores for each pair of candidate features + scores = ( + tf.cast(scores_mask, tf_dtype) * (-target_sum * other_sum) * zero_diagonal + ) + + # Extract the best two pixels + best = tf.argmax( + tf.reshape(scores, shape=[-1, nb_features * nb_features]), axis=1 + ) + + p1 = tf.mod(best, nb_features) + p2 = tf.floordiv(best, nb_features) + p1_one_hot = tf.one_hot(p1, depth=nb_features) + p2_one_hot = tf.one_hot(p2, depth=nb_features) + + # Check if more modification is needed for each sample + mod_not_done = tf.equal(reduce_sum(y_in * preds_onehot, axis=1), 0) + cond = mod_not_done & (reduce_sum(domain_in, axis=1) >= 2) + + # Update the search domain + cond_float = tf.reshape(tf.cast(cond, tf_dtype), shape=[-1, 1]) + to_mod = (p1_one_hot + p2_one_hot) * cond_float + + domain_out = domain_in - to_mod + + # Apply the modification to the images + to_mod_reshape = tf.reshape(to_mod, shape=([-1] + x_in.shape[1:].as_list())) + if increase: + x_out = tf.minimum(clip_max, x_in + to_mod_reshape * theta) + else: + x_out = tf.maximum(clip_min, x_in - to_mod_reshape * theta) + + # Increase the iterator, and check if all misclassifications are done + i_out = tf.add(i_in, 1) + cond_out = reduce_any(cond) + + return x_out, y_in, domain_out, i_out, cond_out + + # Run loop to do JSMA + x_adv, _, _, _, _ = tf.while_loop( + condition, body, [x, y_target, search_domain, 0, True], parallel_iterations=1 + ) + + return x_adv diff --git a/cleverhans_v3.1.0/cleverhans/attacks/semantic.py b/cleverhans_v3.1.0/cleverhans/attacks/semantic.py new file mode 100644 index 000000000..2d84cb88d --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans/attacks/semantic.py @@ -0,0 +1,40 @@ +"""Semantic adversarial examples +""" + +from cleverhans.attacks.attack import Attack + + +class Semantic(Attack): + """ + Semantic adversarial examples + + https://arxiv.org/abs/1703.06857 + + Note: data must either be centered (so that the negative image can be + made by simple negation) or must be in the interval [-1, 1] + + :param model: cleverhans.model.Model + :param center: bool + If True, assumes data has 0 mean so the negative image is just negation. + If False, assumes data is in the interval [0, max_val] + :param max_val: float + Maximum value allowed in the input data + :param sess: optional tf.Session + :param dtypestr: dtype of data + :param kwargs: passed through to the super constructor + """ + + def __init__( + self, model, center, max_val=1.0, sess=None, dtypestr="float32", **kwargs + ): + super(Semantic, self).__init__(model, sess, dtypestr, **kwargs) + self.center = center + self.max_val = max_val + if hasattr(model, "dataset_factory"): + if "center" in model.dataset_factory.kwargs: + assert center == model.dataset_factory.kwargs["center"] + + def generate(self, x, **kwargs): + if self.center: + return -x + return self.max_val - x diff --git a/cleverhans_v3.1.0/cleverhans/attacks/sparse_l1_descent.py b/cleverhans_v3.1.0/cleverhans/attacks/sparse_l1_descent.py new file mode 100644 index 000000000..ceef10a44 --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans/attacks/sparse_l1_descent.py @@ -0,0 +1,365 @@ +""" +The SparseL1Descent attack. +""" + +import warnings +from distutils.version import LooseVersion + +import tensorflow as tf + +from cleverhans.attacks.attack import Attack +from cleverhans import utils_tf +from cleverhans.utils_tf import clip_eta, random_lp_vector +from cleverhans.compat import reduce_max, reduce_sum, softmax_cross_entropy_with_logits + + +class SparseL1Descent(Attack): + """ + This class implements a variant of Projected Gradient Descent for the l1-norm + (Tramer and Boneh 2019). The l1-norm case is more tricky than the l-inf and l2 + cases covered by the ProjectedGradientDescent class, because the steepest + descent direction for the l1-norm is too sparse (it updates a single + coordinate in the adversarial perturbation in each step). This attack has an + additional parameter that controls the sparsity of the update step. For + moderately sparse update steps, the attack vastly outperforms Projected + Steepest Descent and is competitive with other attacks targeted at the l1-norm + such as the ElasticNetMethod attack (which is much more computationally + expensive). + Paper link (Tramer and Boneh 2019): https://arxiv.org/pdf/1904.13000.pdf + + :param model: cleverhans.model.Model + :param sess: optional tf.Session + :param dtypestr: dtype of the data + :param kwargs: passed through to super constructor + """ + + def __init__(self, model, sess=None, dtypestr="float32", **kwargs): + """ + Create a SparseL1Descent instance. + Note: the model parameter should be an instance of the + cleverhans.model.Model abstraction provided by CleverHans. + """ + + super(SparseL1Descent, self).__init__( + model, sess=sess, dtypestr=dtypestr, **kwargs + ) + self.feedable_kwargs = ( + "eps", + "eps_iter", + "y", + "y_target", + "clip_min", + "clip_max", + "grad_sparsity", + ) + self.structural_kwargs = ["nb_iter", "rand_init", "clip_grad", "sanity_checks"] + + def generate(self, x, **kwargs): + """ + Generate symbolic graph for adversarial examples and return. + + :param x: The model's symbolic inputs. + :param kwargs: See `parse_params` + """ + # Parse and save attack-specific parameters + assert self.parse_params(**kwargs) + + asserts = [] + + # If a data range was specified, check that the input was in that range + if self.clip_min is not None: + asserts.append( + utils_tf.assert_greater_equal(x, tf.cast(self.clip_min, x.dtype)) + ) + + if self.clip_max is not None: + asserts.append( + utils_tf.assert_less_equal(x, tf.cast(self.clip_max, x.dtype)) + ) + + # Initialize loop variables + if self.rand_init: + eta = random_lp_vector( + tf.shape(x), ord=1, eps=tf.cast(self.eps, x.dtype), dtype=x.dtype + ) + else: + eta = tf.zeros(tf.shape(x)) + + # Clip eta + eta = clip_eta(eta, ord=1, eps=self.eps) + adv_x = x + eta + if self.clip_min is not None or self.clip_max is not None: + adv_x = utils_tf.clip_by_value(adv_x, self.clip_min, self.clip_max) + + if self.y_target is not None: + y = self.y_target + targeted = True + elif self.y is not None: + y = self.y + targeted = False + else: + model_preds = self.model.get_probs(x) + preds_max = tf.reduce_max(model_preds, 1, keepdims=True) + y = tf.to_float(tf.equal(model_preds, preds_max)) + y = tf.stop_gradient(y) + targeted = False + del model_preds + + y_kwarg = "y_target" if targeted else "y" + + def cond(i, _): + """Iterate until requested number of iterations is completed""" + return tf.less(i, self.nb_iter) + + def body(i, adv_x): + """Do a projected gradient step""" + + labels, _ = self.get_or_guess_labels(adv_x, {y_kwarg: y}) + logits = self.model.get_logits(adv_x) + + adv_x = sparse_l1_descent( + adv_x, + logits, + y=labels, + eps=self.eps_iter, + q=self.grad_sparsity, + clip_min=self.clip_min, + clip_max=self.clip_max, + clip_grad=self.clip_grad, + targeted=(self.y_target is not None), + sanity_checks=self.sanity_checks, + ) + + # Clipping perturbation eta to the l1-ball + eta = adv_x - x + eta = clip_eta(eta, ord=1, eps=self.eps) + adv_x = x + eta + + # Redo the clipping. + # Subtracting and re-adding eta can add some small numerical error. + if self.clip_min is not None or self.clip_max is not None: + adv_x = utils_tf.clip_by_value(adv_x, self.clip_min, self.clip_max) + + return i + 1, adv_x + + _, adv_x = tf.while_loop( + cond, + body, + (tf.zeros([]), adv_x), + back_prop=True, + maximum_iterations=self.nb_iter, + ) + + # Asserts run only on CPU. + # When multi-GPU eval code tries to force all PGD ops onto GPU, this + # can cause an error. + common_dtype = tf.float32 + asserts.append( + utils_tf.assert_less_equal( + tf.cast(self.eps_iter, dtype=common_dtype), + tf.cast(self.eps, dtype=common_dtype), + ) + ) + + if self.sanity_checks: + with tf.control_dependencies(asserts): + adv_x = tf.identity(adv_x) + + return adv_x + + def parse_params( + self, + eps=10.0, + eps_iter=1.0, + nb_iter=20, + y=None, + clip_min=None, + clip_max=None, + y_target=None, + rand_init=False, + clip_grad=False, + grad_sparsity=99, + sanity_checks=True, + **kwargs + ): + """ + Take in a dictionary of parameters and applies attack-specific checks + before saving them as attributes. + + Attack-specific parameters: + + :param eps: (optional float) maximum distortion of adversarial example + compared to original input + :param eps_iter: (optional float) step size for each attack iteration + :param nb_iter: (optional int) Number of attack iterations. + :param y: (optional) A tensor with the true labels. + :param y_target: (optional) A tensor with the labels to target. Leave + y_target=None if y is also set. Labels should be + one-hot-encoded. + :param clip_min: (optional float) Minimum input component value + :param clip_max: (optional float) Maximum input component value + :param clip_grad: (optional bool) Ignore gradient components + at positions where the input is already at the boundary + of the domain, and the update step will get clipped out. + :param grad_sparsity (optional) Relative sparsity of the gradient update + step, in percent. Only gradient values larger + than this percentile are retained. This parameter can + be a scalar, or a vector of the same length as the + input batch dimension. + :param sanity_checks: bool Insert tf asserts checking values + (Some tests need to run with no sanity checks because the + tests intentionally configure the attack strangely) + """ + + # Save attack-specific parameters + self.eps = eps + self.rand_init = rand_init + self.eps_iter = eps_iter + self.nb_iter = nb_iter + self.y = y + self.y_target = y_target + self.clip_min = clip_min + self.clip_max = clip_max + self.clip_grad = clip_grad + self.grad_sparsity = grad_sparsity + + if isinstance(eps, float) and isinstance(eps_iter, float): + # If these are both known at compile time, we can check before anything + # is run. If they are tf, we can't check them yet. + assert eps_iter <= eps, (eps_iter, eps) + + if self.y is not None and self.y_target is not None: + raise ValueError("Must not set both y and y_target") + + if self.clip_grad and (self.clip_min is None or self.clip_max is None): + raise ValueError("Must set clip_min and clip_max if clip_grad is set") + + # The grad_sparsity argument governs the sparsity of the gradient + # update. It indicates the percentile value above which gradient entries + # are retained. It can be specified as a scalar or as a 1-dimensional + # vector of the same size as the input's batch dimension. + if isinstance(self.grad_sparsity, int) or isinstance(self.grad_sparsity, float): + if not 0 < self.grad_sparsity < 100: + raise ValueError("grad_sparsity should be in (0, 100)") + else: + self.grad_sparsity = tf.convert_to_tensor(self.grad_sparsity) + if len(self.grad_sparsity.shape) > 1: + raise ValueError("grad_sparsity should either be a scalar or a vector") + + self.sanity_checks = sanity_checks + + if len(kwargs.keys()) > 0: + warnings.warn( + "kwargs is unused and will be removed on or after " "2019-04-26." + ) + + return True + + +def sparse_l1_descent( + x, + logits, + y=None, + eps=1.0, + q=99, + clip_min=None, + clip_max=None, + clip_grad=False, + targeted=False, + sanity_checks=True, +): + """ + TensorFlow implementation of the Dense L1 Descent Method. + :param x: the input placeholder + :param logits: output of model.get_logits + :param y: (optional) A placeholder for the true labels. If targeted + is true, then provide the target label. Otherwise, only provide + this parameter if you'd like to use true labels when crafting + adversarial samples. Otherwise, model predictions are used as + labels to avoid the "label leaking" effect (explained in this + paper: https://arxiv.org/abs/1611.01236). Default is None. + Labels should be one-hot-encoded. + :param eps: the epsilon (input variation parameter) + :param q: the percentile above which gradient values are retained. Either a + scalar or a vector of same length as the input batch dimension. + :param clip_min: Minimum float value for adversarial example components + :param clip_max: Maximum float value for adversarial example components + :param clip_grad: (optional bool) Ignore gradient components + at positions where the input is already at the boundary + of the domain, and the update step will get clipped out. + :param targeted: Is the attack targeted or untargeted? Untargeted, the + default, will try to make the label incorrect. Targeted + will instead try to move in the direction of being more + like y. + :return: a tensor for the adversarial example + """ + + asserts = [] + + # If a data range was specified, check that the input was in that range + if clip_min is not None: + asserts.append(utils_tf.assert_greater_equal(x, tf.cast(clip_min, x.dtype))) + + if clip_max is not None: + asserts.append(utils_tf.assert_less_equal(x, tf.cast(clip_max, x.dtype))) + + # Make sure the caller has not passed probs by accident + assert logits.op.type != "Softmax" + + if y is None: + # Using model predictions as ground truth to avoid label leaking + preds_max = reduce_max(logits, 1, keepdims=True) + y = tf.to_float(tf.equal(logits, preds_max)) + y = tf.stop_gradient(y) + y = y / reduce_sum(y, 1, keepdims=True) + + # Compute loss + loss = softmax_cross_entropy_with_logits(labels=y, logits=logits) + if targeted: + loss = -loss + + # Define gradient of loss wrt input + (grad,) = tf.gradients(loss, x) + + if clip_grad: + grad = utils_tf.zero_out_clipped_grads(grad, x, clip_min, clip_max) + + red_ind = list(range(1, len(grad.get_shape()))) + dim = tf.reduce_prod(tf.shape(x)[1:]) + + abs_grad = tf.reshape(tf.abs(grad), (-1, dim)) + + # if q is a scalar, broadcast it to a vector of same length as the batch dim + q = tf.cast(tf.broadcast_to(q, tf.shape(x)[0:1]), tf.float32) + k = tf.cast(tf.floor(q / 100 * tf.cast(dim, tf.float32)), tf.int32) + + # `tf.sort` is much faster than `tf.contrib.distributions.percentile`. + # For TF <= 1.12, use `tf.nn.top_k` as `tf.sort` is not implemented. + if LooseVersion(tf.__version__) <= LooseVersion("1.12.0"): + # `tf.sort` is only available in TF 1.13 onwards + sorted_grad = -tf.nn.top_k(-abs_grad, k=dim, sorted=True)[0] + else: + sorted_grad = tf.sort(abs_grad, axis=-1) + + idx = tf.stack((tf.range(tf.shape(abs_grad)[0]), k), -1) + percentiles = tf.gather_nd(sorted_grad, idx) + tied_for_max = tf.greater_equal(abs_grad, tf.expand_dims(percentiles, -1)) + tied_for_max = tf.reshape(tf.cast(tied_for_max, x.dtype), tf.shape(grad)) + num_ties = tf.reduce_sum(tied_for_max, red_ind, keepdims=True) + + optimal_perturbation = tf.sign(grad) * tied_for_max / num_ties + + # Add perturbation to original example to obtain adversarial example + adv_x = x + utils_tf.mul(eps, optimal_perturbation) + + # If clipping is needed, reset all values outside of [clip_min, clip_max] + if (clip_min is not None) or (clip_max is not None): + # We don't currently support one-sided clipping + assert clip_min is not None and clip_max is not None + adv_x = utils_tf.clip_by_value(adv_x, clip_min, clip_max) + + if sanity_checks: + with tf.control_dependencies(asserts): + adv_x = tf.identity(adv_x) + + return adv_x diff --git a/cleverhans_v3.1.0/cleverhans/attacks/spatial_transformation_method.py b/cleverhans_v3.1.0/cleverhans/attacks/spatial_transformation_method.py new file mode 100644 index 000000000..f2f0315e7 --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans/attacks/spatial_transformation_method.py @@ -0,0 +1,127 @@ +"""The SpatialTransformationMethod attack +""" +import warnings + +from cleverhans.attacks.attack import Attack + + +class SpatialTransformationMethod(Attack): + """ + Spatial transformation attack + """ + + def __init__(self, model, sess=None, dtypestr="float32", **kwargs): + """ + Create a SpatialTransformationMethod instance. + Note: the model parameter should be an instance of the + cleverhans.model.Model abstraction provided by CleverHans. + + :param model: cleverhans.model.Model + :param sess: optional tf.Session + :param dtypestr: dtype of the data + :param kwargs: passed through to super constructor + """ + + super(SpatialTransformationMethod, self).__init__( + model, sess, dtypestr, **kwargs + ) + self.feedable_kwargs = ( + "n_samples", + "dx_min", + "dx_max", + "n_dxs", + "dy_min", + "dy_max", + "n_dys", + "angle_min", + "angle_max", + "n_angles", + "black_border_size", + ) + + def generate(self, x, **kwargs): + """ + Generate symbolic graph for adversarial examples and return. + :param x: The model's symbolic inputs. + :param kwargs: See `parse_params` + """ + # Parse and save attack-specific parameters + assert self.parse_params(**kwargs) + + from cleverhans.attacks_tf import spm + + labels, _ = self.get_or_guess_labels(x, kwargs) + + return spm( + x, + self.model, + y=labels, + n_samples=self.n_samples, + dx_min=self.dx_min, + dx_max=self.dx_max, + n_dxs=self.n_dxs, + dy_min=self.dy_min, + dy_max=self.dy_max, + n_dys=self.n_dys, + angle_min=self.angle_min, + angle_max=self.angle_max, + n_angles=self.n_angles, + black_border_size=self.black_border_size, + ) + + def parse_params( + self, + n_samples=None, + dx_min=-0.1, + dx_max=0.1, + n_dxs=2, + dy_min=-0.1, + dy_max=0.1, + n_dys=2, + angle_min=-30, + angle_max=30, + n_angles=6, + black_border_size=0, + **kwargs + ): + """ + Take in a dictionary of parameters and applies attack-specific checks + before saving them as attributes. + :param n_samples: (optional) The number of transformations sampled to + construct the attack. Set it to None to run + full grid attack. + :param dx_min: (optional float) Minimum translation ratio along x-axis. + :param dx_max: (optional float) Maximum translation ratio along x-axis. + :param n_dxs: (optional int) Number of discretized translation ratios + along x-axis. + :param dy_min: (optional float) Minimum translation ratio along y-axis. + :param dy_max: (optional float) Maximum translation ratio along y-axis. + :param n_dys: (optional int) Number of discretized translation ratios + along y-axis. + :param angle_min: (optional float) Largest counter-clockwise rotation + angle. + :param angle_max: (optional float) Largest clockwise rotation angle. + :param n_angles: (optional int) Number of discretized angles. + :param black_border_size: (optional int) size of the black border in pixels. + """ + self.n_samples = n_samples + self.dx_min = dx_min + self.dx_max = dx_max + self.n_dxs = n_dxs + self.dy_min = dy_min + self.dy_max = dy_max + self.n_dys = n_dys + self.angle_min = angle_min + self.angle_max = angle_max + self.n_angles = n_angles + self.black_border_size = black_border_size + + if self.dx_min < -1 or self.dy_min < -1 or self.dx_max > 1 or self.dy_max > 1: + raise ValueError( + "The value of translation must be bounded " "within [-1, 1]" + ) + if len(kwargs.keys()) > 0: + warnings.warn( + "kwargs is unused and will be removed on or after " "2019-04-26." + ) + return True diff --git a/cleverhans_v3.1.0/cleverhans/attacks/spsa.py b/cleverhans_v3.1.0/cleverhans/attacks/spsa.py new file mode 100644 index 000000000..1badf3de2 --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans/attacks/spsa.py @@ -0,0 +1,806 @@ +"""The SPSA attack +""" +# pylint: disable=missing-docstring +import warnings + +import numpy as np +from six.moves import xrange +import tensorflow as tf +import tensorflow_addons as tfa + +from cleverhans.attacks.attack import Attack +from cleverhans.compat import reduce_mean, reduce_sum, reduce_max +from cleverhans.model import Model +from cleverhans import utils_tf + +tf_dtype = tf.as_dtype("float32") + + +class SPSA(Attack): + """ + This implements the SPSA adversary, as in https://arxiv.org/abs/1802.05666 + (Uesato et al. 2018). SPSA is a gradient-free optimization method, which + is useful when the model is non-differentiable, or more generally, the + gradients do not point in useful directions. + + :param model: cleverhans.model.Model + :param sess: optional tf.Session + :param dtypestr: dtype of the data + :param kwargs: passed through to super constructor + """ + + DEFAULT_SPSA_SAMPLES = 128 + DEFAULT_SPSA_ITERS = 1 + DEFAULT_DELTA = 0.01 + DEFAULT_LEARNING_RATE = 0.01 + + def __init__(self, model, sess=None, dtypestr="float32", **kwargs): + super(SPSA, self).__init__(model, sess, dtypestr, **kwargs) + + self.feedable_kwargs = ("eps", "clip_min", "clip_max", "y", "y_target") + self.structural_kwargs = [ + "nb_iter", + "spsa_samples", + "spsa_iters", + "early_stop_loss_threshold", + "is_debug", + "is_targeted", + ] + + assert isinstance(self.model, Model) + + def generate( + self, + x, + y=None, + y_target=None, + eps=None, + clip_min=None, + clip_max=None, + nb_iter=None, + is_targeted=None, + early_stop_loss_threshold=None, + learning_rate=DEFAULT_LEARNING_RATE, + delta=DEFAULT_DELTA, + spsa_samples=DEFAULT_SPSA_SAMPLES, + batch_size=None, + spsa_iters=DEFAULT_SPSA_ITERS, + is_debug=False, + epsilon=None, + num_steps=None, + ): + """ + Generate symbolic graph for adversarial examples. + + :param x: The model's symbolic inputs. Must be a batch of size 1. + :param y: A Tensor or None. The index of the correct label. + :param y_target: A Tensor or None. The index of the target label in a + targeted attack. + :param eps: The size of the maximum perturbation, measured in the + L-infinity norm. + :param clip_min: If specified, the minimum input value + :param clip_max: If specified, the maximum input value + :param nb_iter: The number of optimization steps. + :param early_stop_loss_threshold: A float or None. If specified, the + attack will end as soon as the loss + is below `early_stop_loss_threshold`. + :param learning_rate: Learning rate of ADAM optimizer. + :param delta: Perturbation size used for SPSA approximation. + :param spsa_samples: Number of inputs to evaluate at a single time. + The true batch size (the number of evaluated + inputs for each update) is `spsa_samples * + spsa_iters` + :param batch_size: Deprecated param that is an alias for spsa_samples + :param spsa_iters: Number of model evaluations before performing an + update, where each evaluation is on `spsa_samples` + different inputs. + :param is_debug: If True, print the adversarial loss after each update. + :param epsilon: Deprecated alias for `eps` + :param num_steps: Deprecated alias for `nb_iter`. + :param is_targeted: Deprecated argument. Ignored. + """ + + if epsilon is not None: + if eps is not None: + raise ValueError( + "Should not specify both eps and its deprecated " "alias, epsilon" + ) + warnings.warn( + "`epsilon` is deprecated. Switch to `eps`. `epsilon` may " + "be removed on or after 2019-04-15." + ) + eps = epsilon + del epsilon + + if num_steps is not None: + if nb_iter is not None: + raise ValueError( + "Should not specify both nb_iter and its deprecated " + "alias, num_steps" + ) + warnings.warn( + "`num_steps` is deprecated. Switch to `nb_iter`. " + "`num_steps` may be removed on or after 2019-04-15." + ) + nb_iter = num_steps + del num_steps + assert nb_iter is not None + + if (y is not None) + (y_target is not None) != 1: + raise ValueError( + "Must specify exactly one of y (untargeted attack, " + "cause the input not to be classified as this true " + "label) and y_target (targeted attack, cause the " + "input to be classified as this target label)." + ) + + if is_targeted is not None: + warnings.warn( + "`is_targeted` is deprecated. Simply do not specify it." + " It may become an error to specify it on or after " + "2019-04-15." + ) + assert is_targeted == y_target is not None + + is_targeted = y_target is not None + + if x.get_shape().as_list()[0] is None: + check_batch = utils_tf.assert_equal(tf.shape(x)[0], 1) + with tf.control_dependencies([check_batch]): + x = tf.identity(x) + elif x.get_shape().as_list()[0] != 1: + raise ValueError("For SPSA, input tensor x must have batch_size of 1.") + + if batch_size is not None: + warnings.warn( + 'The "batch_size" argument to SPSA is deprecated, and will ' + "be removed on 2019-03-17. " + "Please use spsa_samples instead." + ) + spsa_samples = batch_size + + optimizer = SPSAAdam( + lr=learning_rate, + delta=delta, + num_samples=spsa_samples, + num_iters=spsa_iters, + ) + + def loss_fn(x, label): + """ + Margin logit loss, with correct sign for targeted vs untargeted loss. + """ + logits = self.model.get_logits(x) + loss_multiplier = 1 if is_targeted else -1 + return loss_multiplier * margin_logit_loss( + logits, + label, + nb_classes=self.model.nb_classes or logits.get_shape()[-1], + ) + + y_attack = y_target if is_targeted else y + adv_x = projected_optimization( + loss_fn, + x, + y_attack, + eps, + num_steps=nb_iter, + optimizer=optimizer, + early_stop_loss_threshold=early_stop_loss_threshold, + is_debug=is_debug, + clip_min=clip_min, + clip_max=clip_max, + ) + return adv_x + + def generate_np(self, x_val, **kwargs): + if "epsilon" in kwargs: + warnings.warn("Using deprecated argument: see `generate`") + assert "eps" not in kwargs + kwargs["eps"] = kwargs["epsilon"] + del kwargs["epsilon"] + assert "eps" in kwargs + + if "num_steps" in kwargs: + warnings.warn("Using deprecated argument: see `generate`") + assert "nb_iter" not in kwargs + kwargs["nb_iter"] = kwargs["num_steps"] + del kwargs["num_steps"] + + if "y" in kwargs and kwargs["y"] is not None: + assert kwargs["y"].dtype in [np.int32, np.int64] + if "y_target" in kwargs and kwargs["y_target"] is not None: + assert kwargs["y_target"].dtype in [np.int32, np.int64] + + # Call self.generate() sequentially for each image in the batch + x_adv = [] + batch_size = x_val.shape[0] + y = kwargs.pop("y", [None] * batch_size) + assert len(x_val) == len(y), "# of images and labels should match" + for x_single, y_single in zip(x_val, y): + x = np.expand_dims(x_single, axis=0) + adv_img = super(SPSA, self).generate_np(x, y=y_single, **kwargs) + x_adv.append(adv_img) + return np.concatenate(x_adv, axis=0) + + +def _project_perturbation( + perturbation, epsilon, input_image, clip_min=None, clip_max=None +): + """Project `perturbation` onto L-infinity ball of radius `epsilon`. + Also project into hypercube such that the resulting adversarial example + is between clip_min and clip_max, if applicable. + """ + + if clip_min is None or clip_max is None: + raise NotImplementedError( + "_project_perturbation currently has clipping " "hard-coded in." + ) + + # Ensure inputs are in the correct range + with tf.control_dependencies( + [ + utils_tf.assert_less_equal( + input_image, tf.cast(clip_max, input_image.dtype) + ), + utils_tf.assert_greater_equal( + input_image, tf.cast(clip_min, input_image.dtype) + ), + ] + ): + clipped_perturbation = utils_tf.clip_by_value(perturbation, -epsilon, epsilon) + new_image = utils_tf.clip_by_value( + input_image + clipped_perturbation, clip_min, clip_max + ) + return new_image - input_image + + +class TensorOptimizer(object): + """Optimizer for Tensors rather than tf.Variables. + + TensorOptimizers implement optimizers where the values being optimized + are ordinary Tensors, rather than Variables. TF Variables can have strange + behaviors when being assigned multiple times within a single sess.run() + call, particularly in Distributed TF, so this avoids thinking about those + issues. These are helper classes for the `projected_optimization` + method. Apart from not using Variables, they follow an interface very + similar to tf.Optimizer. + """ + + def _compute_gradients(self, loss_fn, x, unused_optim_state): + """Compute a new value of `x` to minimize `loss_fn`. + + Args: + loss_fn: a callable that takes `x`, a batch of images, and returns + a batch of loss values. `x` will be optimized to minimize + `loss_fn(x)`. + x: A list of Tensors, the values to be updated. This is analogous + to the `var_list` argument in standard TF Optimizer. + unused_optim_state: A (possibly nested) dict, containing any state + info needed for the optimizer. + + Returns: + new_x: A list of Tensors, the same length as `x`, which are updated + new_optim_state: A dict, with the same structure as `optim_state`, + which have been updated. + """ + + # Assumes `x` is a list, + # and contains a tensor representing a batch of images + assert len(x) == 1 and isinstance( + x, list + ), "x should be a list and contain only one image tensor" + x = x[0] + loss = reduce_mean(loss_fn(x), axis=0) + return tf.gradients(loss, x) + + def _apply_gradients(self, grads, x, optim_state): + """ + Given a gradient, make one optimization step. + + :param grads: list of tensors, same length as `x`, containing the corresponding gradients + :param x: list of tensors to update + :param optim_state: dict + + Returns: + new_x: list of tensors, updated version of `x` + new_optim_state: dict, updated version of `optim_state` + """ + raise NotImplementedError("_apply_gradients should be defined in each subclass") + + def minimize(self, loss_fn, x, optim_state): + """ + Analogous to tf.Optimizer.minimize + + :param loss_fn: tf Tensor, representing the loss to minimize + :param x: list of Tensor, analogous to tf.Optimizer's var_list + :param optim_state: A possibly nested dict, containing any optimizer state. + + Returns: + new_x: list of Tensor, updated version of `x` + new_optim_state: dict, updated version of `optim_state` + """ + grads = self._compute_gradients(loss_fn, x, optim_state) + return self._apply_gradients(grads, x, optim_state) + + def init_state(self, x): + """Returns the initial state of the optimizer. + + Args: + x: A list of Tensors, which will be optimized. + + Returns: + A dictionary, representing the initial state of the optimizer. + """ + raise NotImplementedError("init_state should be defined in each subclass") + + +class TensorGradientDescent(TensorOptimizer): + """Vanilla Gradient Descent TensorOptimizer.""" + + def __init__(self, lr): + self._lr = lr + + def init_state(self, x): + return {} + + def _apply_gradients(self, grads, x, optim_state): + new_x = [None] * len(x) + for i in xrange(len(x)): + new_x[i] = x[i] - self._lr * grads[i] + return new_x, optim_state + + +class TensorAdam(TensorOptimizer): + """The Adam optimizer defined in https://arxiv.org/abs/1412.6980.""" + + def __init__(self, lr=0.001, beta1=0.9, beta2=0.999, epsilon=1e-9): + self._lr = lr + self._beta1 = beta1 + self._beta2 = beta2 + self._epsilon = epsilon + + def init_state(self, x): + """ + Initialize t, m, and u + """ + optim_state = {} + optim_state["t"] = 0.0 + optim_state["m"] = [tf.zeros_like(v) for v in x] + optim_state["u"] = [tf.zeros_like(v) for v in x] + return optim_state + + def _apply_gradients(self, grads, x, optim_state): + """Refer to parent class documentation.""" + new_x = [None] * len(x) + new_optim_state = { + "t": optim_state["t"] + 1.0, + "m": [None] * len(x), + "u": [None] * len(x), + } + t = new_optim_state["t"] + for i in xrange(len(x)): + g = grads[i] + m_old = optim_state["m"][i] + u_old = optim_state["u"][i] + new_optim_state["m"][i] = self._beta1 * m_old + (1.0 - self._beta1) * g + new_optim_state["u"][i] = self._beta2 * u_old + (1.0 - self._beta2) * g * g + m_hat = new_optim_state["m"][i] / (1.0 - tf.pow(self._beta1, t)) + u_hat = new_optim_state["u"][i] / (1.0 - tf.pow(self._beta2, t)) + new_x[i] = x[i] - self._lr * m_hat / (tf.sqrt(u_hat) + self._epsilon) + return new_x, new_optim_state + + +class SPSAAdam(TensorAdam): + """Optimizer for gradient-free attacks in https://arxiv.org/abs/1802.05666. + + Gradients estimates are computed using Simultaneous Perturbation Stochastic + Approximation (SPSA), combined with the ADAM update rule. + """ + + def __init__( + self, + lr=0.01, + delta=0.01, + num_samples=128, + num_iters=1, + compare_to_analytic_grad=False, + ): + super(SPSAAdam, self).__init__(lr=lr) + assert num_samples % 2 == 0, "number of samples must be even" + self._delta = delta + self._num_samples = num_samples // 2 # Since we mirror +/- delta later + self._num_iters = num_iters + self._compare_to_analytic_grad = compare_to_analytic_grad + + def _get_delta(self, x, delta): + x_shape = x.get_shape().as_list() + delta_x = delta * tf.sign( + tf.random_uniform( + [self._num_samples] + x_shape[1:], + minval=-1.0, + maxval=1.0, + dtype=tf_dtype, + ) + ) + return delta_x + + def _compute_gradients(self, loss_fn, x, unused_optim_state): + """Compute gradient estimates using SPSA.""" + # Assumes `x` is a list, containing a [1, H, W, C] image + # If static batch dimension is None, tf.reshape to batch size 1 + # so that static shape can be inferred + assert len(x) == 1 + static_x_shape = x[0].get_shape().as_list() + if static_x_shape[0] is None: + x[0] = tf.reshape(x[0], [1] + static_x_shape[1:]) + assert x[0].get_shape().as_list()[0] == 1 + x = x[0] + x_shape = x.get_shape().as_list() + + def body(i, grad_array): + delta = self._delta + delta_x = self._get_delta(x, delta) + delta_x = tf.concat([delta_x, -delta_x], axis=0) + loss_vals = tf.reshape( + loss_fn(x + delta_x), [2 * self._num_samples] + [1] * (len(x_shape) - 1) + ) + avg_grad = reduce_mean(loss_vals * delta_x, axis=0) / delta + avg_grad = tf.expand_dims(avg_grad, axis=0) + new_grad_array = grad_array.write(i, avg_grad) + return i + 1, new_grad_array + + def cond(i, _): + return i < self._num_iters + + _, all_grads = tf.while_loop( + cond, + body, + loop_vars=[0, tf.TensorArray(size=self._num_iters, dtype=tf_dtype)], + back_prop=False, + parallel_iterations=1, + ) + avg_grad = reduce_sum(all_grads.stack(), axis=0) + return [avg_grad] + + +def margin_logit_loss(model_logits, label, nb_classes=10, num_classes=None): + """Computes difference between logit for `label` and next highest logit. + + The loss is high when `label` is unlikely (targeted by default). + This follows the same interface as `loss_fn` for TensorOptimizer and + projected_optimization, i.e. it returns a batch of loss values. + """ + if num_classes is not None: + warnings.warn( + "`num_classes` is depreciated. Switch to `nb_classes`." + " `num_classes` may be removed on or after 2019-04-23." + ) + nb_classes = num_classes + del num_classes + if "int" in str(label.dtype): + logit_mask = tf.one_hot(label, depth=nb_classes, axis=-1) + else: + logit_mask = label + if "int" in str(logit_mask.dtype): + logit_mask = tf.to_float(logit_mask) + try: + label_logits = reduce_sum(logit_mask * model_logits, axis=-1) + except TypeError: + raise TypeError( + "Could not take row-wise dot product between " + "logit mask, of dtype " + + str(logit_mask.dtype) + + " and model_logits, of dtype " + + str(model_logits.dtype) + ) + logits_with_target_label_neg_inf = model_logits - logit_mask * 99999 + highest_nonlabel_logits = reduce_max(logits_with_target_label_neg_inf, axis=-1) + loss = highest_nonlabel_logits - label_logits + return loss + + +def _apply_black_border(x, border_size): + orig_height = x.get_shape().as_list()[1] + orig_width = x.get_shape().as_list()[2] + x = tf.image.resize_images( + x, (orig_width - 2 * border_size, orig_height - 2 * border_size) + ) + + return tf.pad( + x, + [[0, 0], [border_size, border_size], [border_size, border_size], [0, 0]], + "CONSTANT", + ) + + +def _apply_transformation(inputs): + x, trans = inputs[0], inputs[1] + dx, dy, angle = trans[0], trans[1], trans[2] + height = x.get_shape().as_list()[1] + width = x.get_shape().as_list()[2] + + # Pad the image to prevent two-step rotation / translation from truncating + # corners + max_dist_from_center = np.sqrt(height ** 2 + width ** 2) / 2 + min_edge_from_center = float(np.min([height, width])) / 2 + padding = np.ceil(max_dist_from_center - min_edge_from_center).astype(np.int32) + x = tf.pad(x, [[0, 0], [padding, padding], [padding, padding], [0, 0]], "CONSTANT") + + # Apply rotation + angle *= np.pi / 180 + x = tfa.image.rotate(x, angle, interpolation="BILINEAR") + + # Apply translation + dx_in_px = -dx * height + dy_in_px = -dy * width + translation = tf.convert_to_tensor([dx_in_px, dy_in_px]) + + try: + x = tfa.image.translate(x, translation, interpolation="BILINEAR") + except AttributeError as e: + print("WARNING: SpatialAttack requires tf 1.6 or higher") + raise e + x = tfa.image.translate(x, translation, interpolation="BILINEAR") + return tf.image.resize_image_with_crop_or_pad(x, height, width) + + +def spm( + x, + model, + y=None, + n_samples=None, + dx_min=-0.1, + dx_max=0.1, + n_dxs=5, + dy_min=-0.1, + dy_max=0.1, + n_dys=5, + angle_min=-30, + angle_max=30, + n_angles=31, + black_border_size=0, +): + """ + TensorFlow implementation of the Spatial Transformation Method. + :return: a tensor for the adversarial example + """ + if y is None: + preds = model.get_probs(x) + # Using model predictions as ground truth to avoid label leaking + preds_max = reduce_max(preds, 1, keepdims=True) + y = tf.to_float(tf.equal(preds, preds_max)) + y = tf.stop_gradient(y) + del preds + y = y / reduce_sum(y, 1, keepdims=True) + + # Define the range of transformations + dxs = np.linspace(dx_min, dx_max, n_dxs) + dys = np.linspace(dy_min, dy_max, n_dys) + angles = np.linspace(angle_min, angle_max, n_angles) + + if n_samples is None: + import itertools + + transforms = list(itertools.product(*[dxs, dys, angles])) + else: + sampled_dxs = np.random.choice(dxs, n_samples) + sampled_dys = np.random.choice(dys, n_samples) + sampled_angles = np.random.choice(angles, n_samples) + transforms = zip(sampled_dxs, sampled_dys, sampled_angles) + transformed_ims = parallel_apply_transformations(x, transforms, black_border_size) + + def _compute_xent(x): + preds = model.get_logits(x) + return tf.nn.softmax_cross_entropy_with_logits_v2(labels=y, logits=preds) + + all_xents = tf.map_fn( + _compute_xent, transformed_ims, parallel_iterations=1 + ) # Must be 1 to avoid keras race conditions + + # Return the adv_x with worst accuracy + + # all_xents is n_total_samples x batch_size (SB) + all_xents = tf.stack(all_xents) # SB + + # We want the worst case sample, with the largest xent_loss + worst_sample_idx = tf.argmax(all_xents, axis=0) # B + + batch_size = tf.shape(x)[0] + keys = tf.stack( + [tf.range(batch_size, dtype=tf.int32), tf.cast(worst_sample_idx, tf.int32)], + axis=1, + ) + transformed_ims_bshwc = tf.einsum("sbhwc->bshwc", transformed_ims) + after_lookup = tf.gather_nd(transformed_ims_bshwc, keys) # BHWC + return after_lookup + + +def parallel_apply_transformations(x, transforms, black_border_size=0): + """ + Apply image transformations in parallel. + :param transforms: TODO + :param black_border_size: int, size of black border to apply + Returns: + Transformed images + """ + transforms = tf.convert_to_tensor(transforms, dtype=tf.float32) + x = _apply_black_border(x, black_border_size) + + num_transforms = transforms.get_shape().as_list()[0] + im_shape = x.get_shape().as_list()[1:] + + # Pass a copy of x and a transformation to each iteration of the map_fn + # callable + tiled_x = tf.reshape( + tf.tile(x, [num_transforms, 1, 1, 1]), [num_transforms, -1] + im_shape + ) + elems = [tiled_x, transforms] + transformed_ims = tf.map_fn( + _apply_transformation, + elems, + dtype=tf.float32, + parallel_iterations=1, # Must be 1 to avoid keras race conditions + ) + return transformed_ims + + +def projected_optimization( + loss_fn, + input_image, + label, + epsilon, + num_steps, + clip_min=None, + clip_max=None, + optimizer=TensorAdam(), + project_perturbation=_project_perturbation, + early_stop_loss_threshold=None, + is_debug=False, +): + """Generic projected optimization, generalized to work with approximate + gradients. Used for e.g. the SPSA attack. + + Args: + :param loss_fn: A callable which takes `input_image` and `label` as + arguments, and returns a batch of loss values. Same + interface as TensorOptimizer. + :param input_image: Tensor, a batch of images + :param label: Tensor, a batch of labels + :param epsilon: float, the L-infinity norm of the maximum allowable + perturbation + :param num_steps: int, the number of steps of gradient descent + :param clip_min: float, minimum pixel value + :param clip_max: float, maximum pixel value + :param optimizer: A `TensorOptimizer` object + :param project_perturbation: A function, which will be used to enforce + some constraint. It should have the same + signature as `_project_perturbation`. + :param early_stop_loss_threshold: A float or None. If specified, the attack will end if the loss is below + `early_stop_loss_threshold`. + Enabling this option can have several different effects: + - Setting the threshold to 0. guarantees that if a successful attack is found, it is returned. + This increases the attack success rate, because without early stopping the optimizer can accidentally + bounce back to a point where the attack fails. + - Early stopping can make the attack run faster because it may run for fewer steps. + - Early stopping can make the attack run slower because the loss must be calculated at each step. + The loss is not calculated as part of the normal SPSA optimization procedure. + For most reasonable choices of hyperparameters, early stopping makes the attack much faster because + it decreases the number of steps dramatically. + :param is_debug: A bool. If True, print debug info for attack progress. + + Returns: + adversarial version of `input_image`, with L-infinity difference less than + epsilon, which tries to minimize loss_fn. + + Note that this function is not intended as an Attack by itself. Rather, it + is designed as a helper function which you can use to write your own attack + methods. The method uses a tf.while_loop to optimize a loss function in + a single sess.run() call. + """ + assert num_steps is not None + if is_debug: + with tf.device("/cpu:0"): + input_image = tf.Print( + input_image, [], "Starting PGD attack with epsilon: %s" % epsilon + ) + + init_perturbation = tf.random_uniform( + tf.shape(input_image), + minval=tf.cast(-epsilon, input_image.dtype), + maxval=tf.cast(epsilon, input_image.dtype), + dtype=input_image.dtype, + ) + init_perturbation = project_perturbation( + init_perturbation, epsilon, input_image, clip_min=clip_min, clip_max=clip_max + ) + init_optim_state = optimizer.init_state([init_perturbation]) + nest = tf.nest + + def loop_body(i, perturbation, flat_optim_state): + """Update perturbation to input image.""" + optim_state = nest.pack_sequence_as( + structure=init_optim_state, flat_sequence=flat_optim_state + ) + + def wrapped_loss_fn(x): + return loss_fn(input_image + x, label) + + new_perturbation_list, new_optim_state = optimizer.minimize( + wrapped_loss_fn, [perturbation], optim_state + ) + projected_perturbation = project_perturbation( + new_perturbation_list[0], + epsilon, + input_image, + clip_min=clip_min, + clip_max=clip_max, + ) + + # Be careful with this bool. A value of 0. is a valid threshold but evaluates to False, so we must explicitly + # check whether the value is None. + early_stop = early_stop_loss_threshold is not None + compute_loss = is_debug or early_stop + # Don't waste time building the loss graph if we're not going to use it + if compute_loss: + # NOTE: this step is not actually redundant with the optimizer step. + # SPSA calculates the loss at randomly perturbed points but doesn't calculate the loss at the current point. + loss = reduce_mean(wrapped_loss_fn(projected_perturbation), axis=0) + + if is_debug: + with tf.device("/cpu:0"): + loss = tf.Print(loss, [loss], "Total batch loss") + + if early_stop: + i = tf.cond( + tf.less(loss, early_stop_loss_threshold), + lambda: float(num_steps), + lambda: i, + ) + + return i + 1, projected_perturbation, nest.flatten(new_optim_state) + + def cond(i, *_): + return tf.less(i, num_steps) + + flat_init_optim_state = nest.flatten(init_optim_state) + _, final_perturbation, _ = tf.while_loop( + cond, + loop_body, + loop_vars=(tf.constant(0.0), init_perturbation, flat_init_optim_state), + parallel_iterations=1, + back_prop=False, + maximum_iterations=num_steps, + ) + if project_perturbation is _project_perturbation: + # TODO: this assert looks totally wrong. + # Not bothering to fix it now because it's only an assert. + # 1) Multiplying by 1.1 gives a huge margin of error. This should probably + # take the difference and allow a tolerance of 1e-6 or something like + # that. + # 2) I think it should probably check the *absolute value* of + # final_perturbation + perturbation_max = epsilon * 1.1 + check_diff = utils_tf.assert_less_equal( + final_perturbation, + tf.cast(perturbation_max, final_perturbation.dtype), + message="final_perturbation must change no pixel by more than " + "%s" % perturbation_max, + ) + else: + # TODO: let caller pass in a check_diff function as well as + # project_perturbation + check_diff = tf.no_op() + + if clip_min is None or clip_max is None: + raise NotImplementedError("This function only supports clipping for now") + check_range = [ + utils_tf.assert_less_equal(input_image, tf.cast(clip_max, input_image.dtype)), + utils_tf.assert_greater_equal( + input_image, tf.cast(clip_min, input_image.dtype) + ), + ] + + with tf.control_dependencies([check_diff] + check_range): + adversarial_image = input_image + final_perturbation + return tf.stop_gradient(adversarial_image) diff --git a/cleverhans_v3.1.0/cleverhans/attacks/virtual_adversarial_method.py b/cleverhans_v3.1.0/cleverhans/attacks/virtual_adversarial_method.py new file mode 100644 index 000000000..e1a62e08d --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans/attacks/virtual_adversarial_method.py @@ -0,0 +1,153 @@ +"""The VirtualAdversarialMethod attack + +""" + +import warnings + +import tensorflow as tf + +from cleverhans.attacks.attack import Attack +from cleverhans.model import Model, CallableModelWrapper +from cleverhans.model import wrapper_warning_logits +from cleverhans import utils_tf + +tf_dtype = tf.as_dtype("float32") + + +class VirtualAdversarialMethod(Attack): + """ + This attack was originally proposed by Miyato et al. (2016) and was used + for virtual adversarial training. + Paper link: https://arxiv.org/abs/1507.00677 + + :param model: cleverhans.model.Model + :param sess: optional tf.Session + :param dtypestr: dtype of the data + :param kwargs: passed through to super constructor + """ + + def __init__(self, model, sess=None, dtypestr="float32", **kwargs): + """ + Note: the model parameter should be an instance of the + cleverhans.model.Model abstraction provided by CleverHans. + """ + if not isinstance(model, Model): + wrapper_warning_logits() + model = CallableModelWrapper(model, "logits") + + super(VirtualAdversarialMethod, self).__init__(model, sess, dtypestr, **kwargs) + + self.feedable_kwargs = ("eps", "xi", "clip_min", "clip_max") + self.structural_kwargs = ["num_iterations"] + + def generate(self, x, **kwargs): + """ + Generate symbolic graph for adversarial examples and return. + + :param x: The model's symbolic inputs. + :param kwargs: See `parse_params` + """ + # Parse and save attack-specific parameters + assert self.parse_params(**kwargs) + + return vatm( + self.model, + x, + self.model.get_logits(x), + eps=self.eps, + num_iterations=self.num_iterations, + xi=self.xi, + clip_min=self.clip_min, + clip_max=self.clip_max, + ) + + def parse_params( + self, + eps=2.0, + nb_iter=None, + xi=1e-6, + clip_min=None, + clip_max=None, + num_iterations=None, + **kwargs + ): + """ + Take in a dictionary of parameters and applies attack-specific checks + before saving them as attributes. + + Attack-specific parameters: + + :param eps: (optional float )the epsilon (input variation parameter) + :param nb_iter: (optional) the number of iterations + Defaults to 1 if not specified + :param xi: (optional float) the finite difference parameter + :param clip_min: (optional float) Minimum input component value + :param clip_max: (optional float) Maximum input component value + :param num_iterations: Deprecated alias for `nb_iter` + """ + # Save attack-specific parameters + self.eps = eps + if num_iterations is not None: + warnings.warn( + "`num_iterations` is deprecated. Switch to `nb_iter`." + " The old name will be removed on or after 2019-04-26." + ) + # Note: when we remove the deprecated alias, we can put the default + # value of 1 for nb_iter back in the method signature + assert nb_iter is None + nb_iter = num_iterations + del num_iterations + if nb_iter is None: + nb_iter = 1 + self.num_iterations = nb_iter + self.xi = xi + self.clip_min = clip_min + self.clip_max = clip_max + if len(kwargs.keys()) > 0: + warnings.warn( + "kwargs is unused and will be removed on or after " "2019-04-26." + ) + return True + + +def vatm( + model, + x, + logits, + eps, + num_iterations=1, + xi=1e-6, + clip_min=None, + clip_max=None, + scope=None, +): + """ + Tensorflow implementation of the perturbation method used for virtual + adversarial training: https://arxiv.org/abs/1507.00677 + :param model: the model which returns the network unnormalized logits + :param x: the input placeholder + :param logits: the model's unnormalized output tensor (the input to + the softmax layer) + :param eps: the epsilon (input variation parameter) + :param num_iterations: the number of iterations + :param xi: the finite difference parameter + :param clip_min: optional parameter that can be used to set a minimum + value for components of the example returned + :param clip_max: optional parameter that can be used to set a maximum + value for components of the example returned + :param seed: the seed for random generator + :return: a tensor for the adversarial example + """ + with tf.name_scope(scope, "virtual_adversarial_perturbation"): + d = tf.random_normal(tf.shape(x), dtype=tf_dtype) + for _ in range(num_iterations): + d = xi * utils_tf.l2_batch_normalize(d) + logits_d = model.get_logits(x + d) + kl = utils_tf.kl_with_logits(logits, logits_d) + Hd = tf.gradients(kl, d)[0] + d = tf.stop_gradient(Hd) + d = eps * utils_tf.l2_batch_normalize(d) + adv_x = x + d + if (clip_min is not None) and (clip_max is not None): + adv_x = tf.clip_by_value(adv_x, clip_min, clip_max) + return adv_x diff --git a/cleverhans_v3.1.0/cleverhans/attacks_tf.py b/cleverhans_v3.1.0/cleverhans/attacks_tf.py new file mode 100644 index 000000000..7c7b88dd8 --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans/attacks_tf.py @@ -0,0 +1,243 @@ +# pylint: disable=missing-docstring +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import warnings + +import numpy as np + +from cleverhans.attacks.carlini_wagner_l2 import ( + CWL2 as CarliniWagnerL2, +) # pylint: disable=unused-import +from cleverhans.attacks.deep_fool import ( + deepfool_batch, + deepfool_attack, +) # pylint: disable=unused-import +from cleverhans.attacks.elastic_net_method import ( + EAD as ElasticNetMethod, +) # pylint: disable=unused-import +from cleverhans.attacks.lbfgs import ( + LBFGS_impl as LBFGS_attack, +) # pylint: disable=unused-import +from cleverhans.attacks.saliency_map_method import ( + jsma_symbolic, +) # pylint: disable=unused-import +from cleverhans.attacks.spsa import ( + TensorOptimizer, + TensorGradientDescent, + TensorAdam, +) # pylint: disable=unused-import +from cleverhans.attacks.spsa import ( + SPSAAdam, + margin_logit_loss, + _apply_black_border, +) # pylint: disable=unused-import +from cleverhans.attacks.spsa import ( + _apply_transformation, + spm, + parallel_apply_transformations, +) # pylint: disable=unused-import +from cleverhans.attacks.virtual_adversarial_method import ( + vatm, +) # pylint: disable=unused-import +from cleverhans.utils_tf import ( + jacobian_graph, + jacobian_augmentation, +) # pylint: disable=unused-import +from cleverhans import utils + +np_dtype = np.dtype("float32") + +_logger = utils.create_logger("cleverhans.attacks.tf") + +warnings.warn( + "attacks_tf is deprecated and will be removed on 2019-07-18" + " or after. Code should import functions from their new locations directly." +) + + +def fgsm(x, predictions, eps=0.3, clip_min=None, clip_max=None): + warnings.warn( + "This function is deprecated and will be removed on or after " + "2019-04-09. Switch to cleverhans.attacks.FastGradientMethod." + ) + return fgm( + x, + predictions, + y=None, + eps=eps, + ord=np.inf, + clip_min=clip_min, + clip_max=clip_max, + ) + + +def fgm(x, preds, *args, **kwargs): + if preds.op.type == "Softmax": + (logits,) = preds.op.inputs + else: + raise TypeError("Unclear how to get logits") + warnings.warn( + "This function is deprecated. Switch to passing *logits* to" + " cleverhans.attacks.fgm" + ) + from cleverhans.attacks import fgm as logits_fgm + + return logits_fgm(x, logits, *args, **kwargs) + + +def apply_perturbations(i, j, X, increase, theta, clip_min, clip_max): + """ + TensorFlow implementation for apply perturbations to input features based + on saliency maps + :param i: index of first selected feature + :param j: index of second selected feature + :param X: a matrix containing our input features for our sample + :param increase: boolean; true if we are increasing pixels, false otherwise + :param theta: delta for each feature adjustment + :param clip_min: mininum value for a feature in our sample + :param clip_max: maximum value for a feature in our sample + : return: a perturbed input feature matrix for a target class + """ + warnings.warn( + "This function is dead code and will be removed on or after 2019-07-18" + ) + + # perturb our input sample + if increase: + X[0, i] = np.minimum(clip_max, X[0, i] + theta) + X[0, j] = np.minimum(clip_max, X[0, j] + theta) + else: + X[0, i] = np.maximum(clip_min, X[0, i] - theta) + X[0, j] = np.maximum(clip_min, X[0, j] - theta) + + return X + + +def saliency_map(grads_target, grads_other, search_domain, increase): + """ + TensorFlow implementation for computing saliency maps + :param grads_target: a matrix containing forward derivatives for the + target class + :param grads_other: a matrix where every element is the sum of forward + derivatives over all non-target classes at that index + :param search_domain: the set of input indices that we are considering + :param increase: boolean; true if we are increasing pixels, false otherwise + :return: (i, j, search_domain) the two input indices selected and the + updated search domain + """ + warnings.warn( + "This function is dead code and will be removed on or after 2019-07-18" + ) + + # Compute the size of the input (the number of features) + nf = len(grads_target) + + # Remove the already-used input features from the search space + invalid = list(set(range(nf)) - search_domain) + increase_coef = 2 * int(increase) - 1 + grads_target[invalid] = -increase_coef * np.max(np.abs(grads_target)) + grads_other[invalid] = increase_coef * np.max(np.abs(grads_other)) + + # Create a 2D numpy array of the sum of grads_target and grads_other + target_sum = grads_target.reshape((1, nf)) + grads_target.reshape((nf, 1)) + other_sum = grads_other.reshape((1, nf)) + grads_other.reshape((nf, 1)) + + # Create a mask to only keep features that match saliency map conditions + if increase: + scores_mask = (target_sum > 0) & (other_sum < 0) + else: + scores_mask = (target_sum < 0) & (other_sum > 0) + + # Create a 2D numpy array of the scores for each pair of candidate features + scores = scores_mask * (-target_sum * other_sum) + + # A pixel can only be selected (and changed) once + np.fill_diagonal(scores, 0) + + # Extract the best two pixels + best = np.argmax(scores) + p1, p2 = best % nf, best // nf + + # Remove used pixels from our search domain + search_domain.discard(p1) + search_domain.discard(p2) + + return p1, p2, search_domain + + +def jacobian(sess, x, grads, target, X, nb_features, nb_classes, feed=None): + """ + TensorFlow implementation of the foward derivative / Jacobian + :param x: the input placeholder + :param grads: the list of TF gradients returned by jacobian_graph() + :param target: the target misclassification class + :param X: numpy array with sample input + :param nb_features: the number of features in the input + :return: matrix of forward derivatives flattened into vectors + """ + warnings.warn( + "This function is dead code and will be removed on or after 2019-07-18" + ) + + # Prepare feeding dictionary for all gradient computations + feed_dict = {x: X} + if feed is not None: + feed_dict.update(feed) + + # Initialize a numpy array to hold the Jacobian component values + jacobian_val = np.zeros((nb_classes, nb_features), dtype=np_dtype) + + # Compute the gradients for all classes + for class_ind, grad in enumerate(grads): + run_grad = sess.run(grad, feed_dict) + jacobian_val[class_ind] = np.reshape(run_grad, (1, nb_features)) + + # Sum over all classes different from the target class to prepare for + # saliency map computation in the next step of the attack + other_classes = utils.other_classes(nb_classes, target) + grad_others = np.sum(jacobian_val[other_classes, :], axis=0) + + return jacobian_val[target], grad_others + + +class UnrolledOptimizer(TensorOptimizer): + def __init__(self, *args, **kwargs): + warnings.warn( + "UnrolledOptimizer has been renamed to TensorOptimizer." + " The old name may be removed on or after 2019-04-25." + ) + super(UnrolledOptimizer, self).__init__(*args, **kwargs) + + +class UnrolledGradientDescent(TensorGradientDescent): + def __init__(self, *args, **kwargs): + warnings.warn( + "UnrolledGradientDescent has been renamed to " + "TensorGradientDescent." + " The old name may be removed on or after 2019-04-25." + ) + super(UnrolledGradientDescent, self).__init__(*args, **kwargs) + + +class UnrolledAdam(TensorAdam): + def __init__(self, *args, **kwargs): + warnings.warn( + "UnrolledAdam has been renamed to TensorAdam." + " The old name may be removed on or after 2019-04-25." + ) + super(UnrolledAdam, self).__init__(*args, **kwargs) + + +def pgd_attack(*args, **kwargs): + warnings.warn( + "cleverhans.attacks_tf.pgd_attack has been renamed to " + "cleverhans.attacks.projected_optimization. " + "Please switch to the new name. The current name will " + "become unsupport on or after 2019-04-24." + ) + from cleverhans.attacks import projected_optimization + + return projected_optimization(*args, **kwargs) diff --git a/cleverhans_v3.1.0/cleverhans/attacks_tfe.py b/cleverhans_v3.1.0/cleverhans/attacks_tfe.py new file mode 100644 index 000000000..9362ed132 --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans/attacks_tfe.py @@ -0,0 +1,184 @@ +""" +Attacks for TensorFlow Eager +""" +from distutils.version import LooseVersion + +import numpy as np +import tensorflow as tf + +from cleverhans import attacks +from cleverhans import utils +from cleverhans.model import CallableModelWrapper, wrapper_warning +from cleverhans.model import Model +from cleverhans.loss import LossCrossEntropy + +_logger = utils.create_logger("cleverhans.attacks_tfe") + + +if LooseVersion(tf.__version__) < LooseVersion("1.8.0"): + error_msg = ("For eager execution", "use Tensorflow version greather than 1.8.0.") + raise ValueError(error_msg) + + +class Attack(attacks.Attack): + """ + Abstract base class for all eager attack classes. + :param model: An instance of the cleverhans.model.Model class. + :param back: The backend to use. Inherited from AttackBase class. + :param dtypestr: datatype of the input data samples and crafted + adversarial attacks. + """ + + def __init__(self, model, dtypestr="float32"): + super(Attack, self).__init__(model, dtypestr=dtypestr) + # Validate the input arguments. + if dtypestr != "float32" and dtypestr != "float64": + raise ValueError("Unexpected input for argument dtypestr.") + self.tf_dtype = tf.as_dtype(dtypestr) + self.np_dtype = np.dtype(dtypestr) + + if not isinstance(model, Model): + raise ValueError( + "The model argument should be an instance of" + " the cleverhans.model.Model class." + ) + # Prepare attributes + self.model = model + self.dtypestr = dtypestr + + def construct_graph(self, **kwargs): + """ + Constructs the graph required to run the attacks. + Is inherited from the attack class, is overloaded + to raise an error. + """ + error = "This method is not required for eager execution." + raise AttributeError(error) + + def generate_np(self, x_val, **kwargs): + """ + Generate adversarial examples and return them as a NumPy array. + + :param x_val: A NumPy array with the original inputs. + :param **kwargs: optional parameters used by child classes. + :return: A NumPy array holding the adversarial examples. + """ + tfe = tf.contrib.eager + x = tfe.Variable(x_val) + adv_x = self.generate(x, **kwargs) + return adv_x.numpy() + + def construct_variables(self, kwargs): + """ + Construct the inputs to the attack graph. + Is inherited from the attack class, is overloaded + to raise an error. + """ + error = "This method is not required for eager execution." + raise AttributeError(error) + + +class FastGradientMethod(Attack, attacks.FastGradientMethod): + """ + Inherited class from Attack and cleverhans.attacks.FastGradientMethod. + + This attack was originally implemented by Goodfellow et al. (2015) with the + infinity norm (and is known as the "Fast Gradient Sign Method"). This + implementation extends the attack to other norms, and is therefore called + the Fast Gradient Method. + Paper link: https://arxiv.org/abs/1412.6572 + """ + + def __init__(self, model, dtypestr="float32", **kwargs): + """ + Creates a FastGradientMethod instance in eager execution. + :model: cleverhans.model.Model + :dtypestr: datatype in the string format. + """ + del kwargs + if not isinstance(model, Model): + wrapper_warning() + model = CallableModelWrapper(model, "probs") + + super(FastGradientMethod, self).__init__(model, dtypestr) + + def generate(self, x, **kwargs): + """ + Generates the adversarial sample for the given input. + :param x: The model's inputs. + :param eps: (optional float) attack step size (input variation) + :param ord: (optional) Order of the norm (mimics NumPy). + Possible values: np.inf, 1 or 2. + :param y: (optional) A tf variable` with the model labels. Only provide + this parameter if you'd like to use true labels when crafting + adversarial samples. Otherwise, model predictions are used as + labels to avoid the "label leaking" effect (explained in this + paper: https://arxiv.org/abs/1611.01236). Default is None. + Labels should be one-hot-encoded. + :param y_target: (optional) A tf variable` with the labels to target. + Leave y_target=None if y is also set. + Labels should be one-hot-encoded. + :param clip_min: (optional float) Minimum input component value + :param clip_max: (optional float) Maximum input component value + """ + # Parse and save attack-specific parameters + assert self.parse_params(**kwargs) + labels, _nb_classes = self.get_or_guess_labels(x, kwargs) + return self.fgm(x, labels=labels, targeted=(self.y_target is not None)) + + def fgm(self, x, labels, targeted=False): + """ + TensorFlow Eager implementation of the Fast Gradient Method. + :param x: the input variable + :param targeted: Is the attack targeted or untargeted? Untargeted, the + default, will try to make the label incorrect. + Targeted will instead try to move in the direction + of being more like y. + :return: a tensor for the adversarial example + """ + # Compute loss + with tf.GradientTape() as tape: + # input should be watched because it may be + # combination of trainable and non-trainable variables + tape.watch(x) + loss_obj = LossCrossEntropy(self.model, smoothing=0.0) + loss = loss_obj.fprop(x=x, y=labels) + if targeted: + loss = -loss + + # Define gradient of loss wrt input + grad = tape.gradient(loss, x) + optimal_perturbation = attacks.optimize_linear(grad, self.eps, self.ord) + + # Add perturbation to original example to obtain adversarial example + adv_x = x + optimal_perturbation + + # If clipping is needed + # reset all values outside of [clip_min, clip_max] + if (self.clip_min is not None) and (self.clip_max is not None): + adv_x = tf.clip_by_value(adv_x, self.clip_min, self.clip_max) + return adv_x + + +class BasicIterativeMethod(Attack, attacks.BasicIterativeMethod): + """ + Inherited class from Attack and cleverhans.attacks.BasicIterativeMethod. + + The Basic Iterative Method (Kurakin et al. 2016). The original paper used + hard labels for this attack; no label smoothing. + Paper link: https://arxiv.org/pdf/1607.02533.pdf + """ + + FGM_CLASS = FastGradientMethod + + def __init__(self, model, dtypestr="float32"): + """ + Creates a BasicIterativeMethod instance in eager execution. + :param model: cleverhans.model.Model + :param dtypestr: datatype in the string format. + """ + if not isinstance(model, Model): + wrapper_warning() + model = CallableModelWrapper(model, "probs") + + super(BasicIterativeMethod, self).__init__(model, dtypestr) diff --git a/cleverhans_v3.1.0/cleverhans/augmentation.py b/cleverhans_v3.1.0/cleverhans/augmentation.py new file mode 100644 index 000000000..f291e1aab --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans/augmentation.py @@ -0,0 +1,57 @@ +""" +Dataset augmentation functionality + +NOTE: This module is much more free to change than many other modules +in CleverHans. CleverHans is very conservative about changes to any +code that affects the output of benchmark tests (attacks, evaluation +methods, etc.). This module provides *dataset augmentation* code for +building models to be benchmarked, not *benchmarks,* and +thus is free to change rapidly to provide better speed, accuracy, +etc. +""" + +import tensorflow as tf + +# Convenient renaming of existing function +random_horizontal_flip = tf.image.random_flip_left_right + + +def random_shift(x, pad=(4, 4), mode="REFLECT"): + """Pad a single image and then crop to the original size with a random + offset.""" + assert mode in "REFLECT SYMMETRIC CONSTANT".split() + assert x.get_shape().ndims == 3 + xp = tf.pad(x, [[pad[0], pad[0]], [pad[1], pad[1]], [0, 0]], mode) + return tf.random_crop(xp, tf.shape(x)) + + +def batch_augment(x, func, device="/CPU:0"): + """ + Apply dataset augmentation to a batch of exmaples. + :param x: Tensor representing a batch of examples. + :param func: Callable implementing dataset augmentation, operating on + a single image. + :param device: String specifying which device to use. + """ + with tf.device(device): + return tf.map_fn(func, x) + + +def random_crop_and_flip(x, pad_rows=4, pad_cols=4): + """Augment a batch by randomly cropping and horizontally flipping it.""" + rows = tf.shape(x)[1] + cols = tf.shape(x)[2] + channels = x.get_shape()[3] + + def _rand_crop_img(img): + """Randomly crop an individual image""" + return tf.random_crop(img, [rows, cols, channels]) + + # Some of these ops are only on CPU. + # This function will often be called with the device set to GPU. + # We need to set it to CPU temporarily to avoid an exception. + with tf.device("/CPU:0"): + x = tf.image.resize_image_with_crop_or_pad(x, rows + pad_rows, cols + pad_cols) + x = tf.map_fn(_rand_crop_img, x) + x = tf.image.random_flip_left_right(x) + return x diff --git a/cleverhans_v3.1.0/cleverhans/canary.py b/cleverhans_v3.1.0/cleverhans/canary.py new file mode 100644 index 000000000..b5f652ba0 --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans/canary.py @@ -0,0 +1,80 @@ +""" +Canary code that dies if the underlying hardware / drivers aren't working right. +""" +import time + +import numpy as np +import tensorflow as tf +from cleverhans.utils_tf import infer_devices + +last_run = None + + +def run_canary(): + """ + Runs some code that will crash if the GPUs / GPU driver are suffering from + a common bug. This helps to prevent contaminating results in the rest of + the library with incorrect calculations. + """ + + # Note: please do not edit this function unless you have access to a machine + # with GPUs suffering from the bug and can verify that the canary still + # crashes after your edits. Due to the transient nature of the GPU bug it is + # not possible to unit test the canary in our continuous integration system. + + global last_run + current = time.time() + if last_run is None or current - last_run > 3600: + last_run = current + else: + # Run the canary at most once per hour + return + + # Try very hard not to let the canary affect the graph for the rest of the + # python process + canary_graph = tf.Graph() + with canary_graph.as_default(): + devices = infer_devices() + num_devices = len(devices) + if num_devices < 3: + # We have never observed GPU failure when less than 3 GPUs were used + return + + v = np.random.RandomState([2018, 10, 16]).randn(2, 2) + # Try very hard not to let this Variable end up in any collections used + # by the rest of the python process + w = tf.Variable(v, trainable=False, collections=[]) + loss = tf.reduce_sum(tf.square(w)) + + grads = [] + for device in devices: + with tf.device(device): + (grad,) = tf.gradients(loss, w) + grads.append(grad) + + sess = tf.Session() + sess.run(tf.variables_initializer([w])) + grads = sess.run(grads) + first = grads[0] + for grad in grads[1:]: + if not np.allclose(first, grad): + first_string = str(first) + grad_string = str(grad) + raise RuntimeError( + "Something is wrong with your GPUs or GPU driver." + "%(num_devices)d different GPUS were asked to " + "calculate the same 2x2 gradient. One returned " + "%(first_string)s and another returned " + "%(grad_string)s. This can usually be fixed by " + "rebooting the machine." + % { + "num_devices": num_devices, + "first_string": first_string, + "grad_string": grad_string, + } + ) + sess.close() + + +if __name__ == "__main__": + run_canary() diff --git a/cleverhans_v3.1.0/cleverhans/compat.py b/cleverhans_v3.1.0/cleverhans/compat.py new file mode 100644 index 000000000..a793fdbdc --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans/compat.py @@ -0,0 +1,105 @@ +""" +Wrapper functions for writing code that is compatible with many versions +of TensorFlow. +""" +import warnings +import tensorflow as tf + +# The following 2 imports are not used in this module. They are imported so that users of cleverhans.compat can +# get access to device_lib, app, and flags. A pylint bug makes these imports cause errors when using python3+tf1.8. +# Doing the sanitized import here once makes it possible to do "from cleverhans.compat import flags" throughout the +# library without needing to repeat the pylint boilerplate. +from tensorflow.python.client import ( + device_lib, +) # pylint: disable=no-name-in-module,unused-import +from tensorflow.python.platform import ( + app, + flags, +) # pylint: disable=no-name-in-module,unused-import + + +def _wrap(f): + """ + Wraps a callable `f` in a function that warns that the function is deprecated. + """ + + def wrapper(*args, **kwargs): + """ + Issues a deprecation warning and passes through the arguments. + """ + warnings.warn( + str(f) + + " is deprecated. Switch to calling the equivalent function in tensorflow. " + " This function was originally needed as a compatibility layer for old versions of tensorflow, " + " but support for those versions has now been dropped." + ) + return f(*args, **kwargs) + + return wrapper + + +reduce_sum = _wrap(tf.reduce_sum) +reduce_max = _wrap(tf.reduce_max) +reduce_min = _wrap(tf.reduce_min) +reduce_mean = _wrap(tf.reduce_mean) +reduce_prod = _wrap(tf.reduce_prod) +reduce_any = _wrap(tf.reduce_any) + + +def reduce_function( + op_func, input_tensor, axis=None, keepdims=None, name=None, reduction_indices=None +): + """ + This function used to be needed to support tf 1.4 and early, but support for tf 1.4 and earlier is now dropped. + :param op_func: expects the function to handle eg: tf.reduce_sum. + :param input_tensor: The tensor to reduce. Should have numeric type. + :param axis: The dimensions to reduce. If None (the default), + reduces all dimensions. Must be in the range + [-rank(input_tensor), rank(input_tensor)). + :param keepdims: If true, retains reduced dimensions with length 1. + :param name: A name for the operation (optional). + :param reduction_indices: The old (deprecated) name for axis. + :return: outputs same value as op_func. + """ + + warnings.warn( + "`reduce_function` is deprecated and may be removed on or after 2019-09-08." + ) + + out = op_func( + input_tensor, + axis=axis, + keepdims=keepdims, + name=name, + reduction_indices=reduction_indices, + ) + + return out + + +def softmax_cross_entropy_with_logits(sentinel=None, labels=None, logits=None, dim=-1): + """ + Wrapper around tf.nn.softmax_cross_entropy_with_logits_v2 to handle + deprecated warning + """ + # Make sure that all arguments were passed as named arguments. + if sentinel is not None: + name = "softmax_cross_entropy_with_logits" + raise ValueError( + "Only call `%s` with " + "named arguments (labels=..., logits=..., ...)" % name + ) + if labels is None or logits is None: + raise ValueError("Both labels and logits must be provided.") + + try: + f = tf.nn.softmax_cross_entropy_with_logits_v2 + except AttributeError: + raise RuntimeError( + "This version of TensorFlow is no longer supported. See cleverhans/README.md" + ) + + labels = tf.stop_gradient(labels) + loss = f(labels=labels, logits=logits, dim=dim) + + return loss diff --git a/cleverhans_v3.1.0/cleverhans/confidence_report.py b/cleverhans_v3.1.0/cleverhans/confidence_report.py new file mode 100644 index 000000000..dca0767af --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans/confidence_report.py @@ -0,0 +1,469 @@ +"""Functionality for making confidence reports. + +A confidence report is a dictionary. +Each dictionary key is the name of a type of data: + clean : Clean data + bundled : bundled adversarial examples +Each value in the dictionary contains an array of bools indicating whether +the model got each example correct and an array containing the confidence +that the model assigned to each prediction. +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from collections import OrderedDict +import logging +import time +import warnings + +import numpy as np +import six +import tensorflow as tf + +from cleverhans.attacks import MaxConfidence +from cleverhans.attacks import Semantic +from cleverhans.evaluation import correctness_and_confidence +from cleverhans.evaluation import run_attack +from cleverhans.utils import set_log_level +from cleverhans.serial import load, save +from cleverhans.utils_tf import infer_devices + +# Defaults. Imported elsewhere so that command line script defaults match +# function defaults. +TRAIN_START = 0 +TRAIN_END = 60000 +TEST_START = 0 +TEST_END = 10000 +WHICH_SET = "test" +RECIPE = "basic_max_confidence_recipe" +REPORT_PATH = None +# Used for `make_confidence_report` but not `make_confidence_report_bundled` +devices = infer_devices() +num_devices = len(devices) +BATCH_SIZE = 128 * num_devices +MC_BATCH_SIZE = 16 * num_devices +NB_ITER = 40 +BASE_EPS_ITER = None # Differs by dataset +SAVE_ADVX = 1 + + +class ConfidenceReport(OrderedDict): + """ + A data structure reporting how much confidence a model assigned to its + predictions on each example and whether those predictions were correct. + This class is just a dictionary with some type checks. + It maps string data type names (like "clean" for clean data or "Semantic" + for semantic adversarial examples) to ConfidenceReportEntry instances. + + :param iterable: optional iterable containing (key, value) tuples + """ + + def __init__(self, iterable=None): + super(ConfidenceReport, self).__init__() + # This field tracks whether the report is completed. + # It's important e.g. for reports that are made by bundlers and repeatedly + # written to disk during the process. This field makes it possible to tell + # whether a report on disk is complete or whether the bundling process + # got killed (e.g. due to VM migration) + self.completed = False + if iterable is not None: + # pickle sometimes wants to use this interface to unpickle the OrderedDict + for key, value in iterable: + self[key] = value + + def __setitem__(self, key, value): + assert isinstance(key, six.string_types) + if not isinstance(value, ConfidenceReportEntry): + raise TypeError( + "`value` must be a ConfidenceReportEntry, but got " + + str(value) + + " of type " + + str(type(value)) + ) + super(ConfidenceReport, self).__setitem__(key, value) + + +class ConfidenceReportEntry(object): + """ + A data structure reporting how much confidence a model assigned to its + predictions on each example and whether those predictions were correct. + + :param correctness: ndarray, one bool per example indicating whether it was + correct + :param confidence: ndarray, one floating point value per example reporting + the probability assigned to the prediction for that example + """ + + def __init__(self, correctness, confidence): + assert isinstance(correctness, np.ndarray) + assert isinstance(correctness, np.ndarray) + assert correctness.ndim == 1 + assert confidence.ndim == 1 + assert correctness.dtype == np.bool, correctness.dtype + assert np.issubdtype(confidence.dtype, np.floating) + assert correctness.shape == confidence.shape + assert confidence.min() >= 0.0 + assert confidence.max() <= 1.0 + self.correctness = correctness + self.confidence = confidence + + def __getitem__(self, key): + warnings.warn( + "Dictionary confidence report entries are deprecated. " + "Switch to accessing the appropriate field of " + "ConfidenceReportEntry. " + "Dictionary-style access will be removed on or after " + "2019-04-24." + ) + assert key in ["correctness", "confidence"] + return self.__dict__[key] + + def __setitem__(self, key, value): + warnings.warn( + "Dictionary confidence report entries are deprecated." + "Switch to accessing the appropriate field of " + "ConfidenceReportEntry. " + "Dictionary-style access will be removed on or after " + "2019-04-24." + ) + assert key in ["correctness", "confidence"] + self.__dict__[key] = value + + +def make_confidence_report_bundled( + filepath, + train_start=TRAIN_START, + train_end=TRAIN_END, + test_start=TEST_START, + test_end=TEST_END, + which_set=WHICH_SET, + recipe=RECIPE, + report_path=REPORT_PATH, + nb_iter=NB_ITER, + base_eps=None, + base_eps_iter=None, + base_eps_iter_small=None, + batch_size=BATCH_SIZE, +): + """ + Load a saved model, gather its predictions, and save a confidence report. + :param filepath: path to model to evaluate + :param train_start: index of first training set example to use + :param train_end: index of last training set example to use + :param test_start: index of first test set example to use + :param test_end: index of last test set example to use + :param which_set: 'train' or 'test' + :param nb_iter: int, number of iterations of attack algorithm + (note that different recipes will use this differently, + for example many will run two attacks, one with nb_iter + iterations and one with 25X more) + :param base_eps: float, epsilon parameter for threat model, on a scale of [0, 1]. + Inferred from the dataset if not specified. + :param base_eps_iter: float, a step size used in different ways by different recipes. + Typically the step size for a PGD attack. + Inferred from the dataset if not specified. + :param base_eps_iter_small: float, a second step size for a more fine-grained attack. + Inferred from the dataset if not specified. + :param batch_size: int, batch size + """ + # Avoid circular import + from cleverhans import attack_bundling + + if callable(recipe): + run_recipe = recipe + else: + run_recipe = getattr(attack_bundling, recipe) + + # Set logging level to see debug information + set_log_level(logging.INFO) + + # Create TF session + sess = tf.Session() + + assert filepath.endswith(".joblib") + if report_path is None: + report_path = filepath[: -len(".joblib")] + "_bundled_report.joblib" + + with sess.as_default(): + model = load(filepath) + assert len(model.get_params()) > 0 + factory = model.dataset_factory + factory.kwargs["train_start"] = train_start + factory.kwargs["train_end"] = train_end + factory.kwargs["test_start"] = test_start + factory.kwargs["test_end"] = test_end + dataset = factory() + + center = dataset.kwargs["center"] + if "max_val" in factory.kwargs: + max_value = factory.kwargs["max_val"] + elif hasattr(dataset, "max_val"): + max_value = dataset.max_val + else: + raise AttributeError("Can't find max_value specification") + min_value = 0.0 - center * max_value + value_range = max_value - min_value + + if "CIFAR" in str(factory.cls): + if base_eps is None: + base_eps = 8.0 / 255.0 + if base_eps_iter is None: + base_eps_iter = 2.0 / 255.0 + if base_eps_iter_small is None: + base_eps_iter_small = 1.0 / 255.0 + elif "MNIST" in str(factory.cls): + if base_eps is None: + base_eps = 0.3 + if base_eps_iter is None: + base_eps_iter = 0.1 + base_eps_iter_small = None + else: + # Note that it is not required to specify base_eps_iter_small + if base_eps is None or base_eps_iter is None: + raise NotImplementedError( + "Not able to infer threat model from " + str(factory.cls) + ) + + eps = base_eps * value_range + eps_iter = base_eps_iter * value_range + if base_eps_iter_small is None: + eps_iter_small = None + else: + eps_iter_small = base_eps_iter_small * value_range + clip_min = min_value + clip_max = max_value + + x_data, y_data = dataset.get_set(which_set) + assert x_data.max() <= max_value + assert x_data.min() >= min_value + + assert eps_iter <= eps + assert eps_iter_small is None or eps_iter_small <= eps + + # Different recipes take different arguments. + # For now I don't have an idea for a beautiful unifying framework, so + # we get an if statement. + if recipe == "random_search_max_confidence_recipe": + # pylint always checks against the default recipe here + # pylint: disable=no-value-for-parameter + run_recipe( + sess=sess, + model=model, + x=x_data, + y=y_data, + eps=eps, + clip_min=clip_min, + clip_max=clip_max, + report_path=report_path, + ) + else: + run_recipe( + sess=sess, + model=model, + x=x_data, + y=y_data, + nb_classes=dataset.NB_CLASSES, + eps=eps, + clip_min=clip_min, + clip_max=clip_max, + eps_iter=eps_iter, + nb_iter=nb_iter, + report_path=report_path, + eps_iter_small=eps_iter_small, + batch_size=batch_size, + ) + + +def print_stats(correctness, confidence, name): + """ + Prints out accuracy, coverage, etc. statistics + :param correctness: ndarray + One bool per example specifying whether it was correctly classified + :param confidence: ndarray + The probability associated with each prediction + :param name: str + The name of this type of data (e.g. "clean", "MaxConfidence") + """ + accuracy = correctness.mean() + wrongness = 1 - correctness + denom1 = np.maximum(1, wrongness.sum()) + ave_prob_on_mistake = (wrongness * confidence).sum() / denom1 + assert ave_prob_on_mistake <= 1.0, ave_prob_on_mistake + denom2 = np.maximum(1, correctness.sum()) + ave_prob_on_correct = (correctness * confidence).sum() / denom2 + covered = confidence > 0.5 + cov_half = covered.mean() + acc_half = (correctness * covered).sum() / np.maximum(1, covered.sum()) + print("Accuracy on %s examples: %0.4f" % (name, accuracy)) + print("Average prob on mistakes: %0.4f" % ave_prob_on_mistake) + print("Average prob on correct: %0.4f" % ave_prob_on_correct) + print("Accuracy when prob thresholded at .5: %0.4f" % acc_half) + print("Coverage when prob thresholded at .5: %0.4f" % cov_half) + + success_rate = acc_half * cov_half + # Success is correctly classifying a covered example + print("Success rate at .5: %0.4f" % success_rate) + # Failure is misclassifying a covered example + failure_rate = (1.0 - acc_half) * cov_half + print("Failure rate at .5: %0.4f" % failure_rate) + print() + + +def make_confidence_report( + filepath, + train_start=TRAIN_START, + train_end=TRAIN_END, + test_start=TEST_START, + test_end=TEST_END, + batch_size=BATCH_SIZE, + which_set=WHICH_SET, + mc_batch_size=MC_BATCH_SIZE, + report_path=REPORT_PATH, + base_eps_iter=BASE_EPS_ITER, + nb_iter=NB_ITER, + save_advx=SAVE_ADVX, +): + """ + Load a saved model, gather its predictions, and save a confidence report. + + + This function works by running a single MaxConfidence attack on each example. + This provides a reasonable estimate of the true failure rate quickly, so + long as the model does not suffer from gradient masking. + However, this estimate is mostly intended for development work and not + for publication. A more accurate estimate may be obtained by running + make_confidence_report_bundled.py instead. + + :param filepath: path to model to evaluate + :param train_start: index of first training set example to use + :param train_end: index of last training set example to use + :param test_start: index of first test set example to use + :param test_end: index of last test set example to use + :param batch_size: size of evaluation batches + :param which_set: 'train' or 'test' + :param mc_batch_size: batch size for MaxConfidence attack + :param base_eps_iter: step size if the data were in [0,1] + (Step size will be rescaled proportional to the actual data range) + :param nb_iter: Number of iterations of PGD to run per class + :param save_advx: bool. If True, saves the adversarial examples to disk. + On by default, but can be turned off to save memory, etc. + """ + + # Set TF random seed to improve reproducibility + tf.set_random_seed(1234) + + # Set logging level to see debug information + set_log_level(logging.INFO) + + # Create TF session + sess = tf.Session() + + if report_path is None: + assert filepath.endswith(".joblib") + report_path = filepath[: -len(".joblib")] + "_report.joblib" + + with sess.as_default(): + model = load(filepath) + assert len(model.get_params()) > 0 + factory = model.dataset_factory + factory.kwargs["train_start"] = train_start + factory.kwargs["train_end"] = train_end + factory.kwargs["test_start"] = test_start + factory.kwargs["test_end"] = test_end + dataset = factory() + + center = dataset.kwargs["center"] + max_val = dataset.kwargs["max_val"] + value_range = max_val * (1.0 + center) + min_value = 0.0 - center * max_val + + if "CIFAR" in str(factory.cls): + base_eps = 8.0 / 255.0 + if base_eps_iter is None: + base_eps_iter = 2.0 / 255.0 + elif "MNIST" in str(factory.cls): + base_eps = 0.3 + if base_eps_iter is None: + base_eps_iter = 0.1 + else: + raise NotImplementedError(str(factory.cls)) + + mc_params = { + "eps": base_eps * value_range, + "eps_iter": base_eps_iter * value_range, + "nb_iter": nb_iter, + "clip_min": min_value, + "clip_max": max_val, + } + + x_data, y_data = dataset.get_set(which_set) + + report = ConfidenceReport() + + semantic = Semantic(model, center, max_val, sess) + mc = MaxConfidence(model, sess=sess) + + jobs = [ + ("clean", None, None, None, False), + ("Semantic", semantic, None, None, False), + ("mc", mc, mc_params, mc_batch_size, True), + ] + + for job in jobs: + name, attack, attack_params, job_batch_size, save_this_job = job + if job_batch_size is None: + job_batch_size = batch_size + t1 = time.time() + if save_advx and save_this_job: + # If we want to save the adversarial examples to the filesystem, we need + # to fetch all of them. Otherwise they're just computed one batch at a + # time and discarded + + # The path to save to + assert report_path.endswith(".joblib") + advx_path = report_path[: -len(".joblib")] + "_advx_" + name + ".npy" + + # Fetch the adversarial examples + x_data = run_attack( + sess, + model, + x_data, + y_data, + attack, + attack_params, + batch_size=job_batch_size, + devices=devices, + ) + + # Turn off the attack so `correctness_and_confidence` won't run it a + # second time. + attack = None + attack_params = None + + # Save the adversarial examples + np.save(advx_path, x_data) + + # Run correctness and confidence evaluation on adversarial examples + packed = correctness_and_confidence( + sess, + model, + x_data, + y_data, + batch_size=job_batch_size, + devices=devices, + attack=attack, + attack_params=attack_params, + ) + t2 = time.time() + print("Evaluation took", t2 - t1, "seconds") + correctness, confidence = packed + + report[name] = ConfidenceReportEntry( + correctness=correctness, confidence=confidence + ) + + print_stats(correctness, confidence, name) + + save(report_path, report) diff --git a/cleverhans_v3.1.0/cleverhans/dataset.py b/cleverhans_v3.1.0/cleverhans/dataset.py new file mode 100644 index 000000000..53cdd70a0 --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans/dataset.py @@ -0,0 +1,347 @@ +"""Dataset class for CleverHans + +""" +# pylint: disable=missing-docstring + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import array +import functools +import gzip +import operator +import os +import struct +import tempfile +import sys +import warnings + +import numpy as np +import tensorflow as tf + +try: + from tensorflow.python.keras.utils import np_utils + from tensorflow.keras.datasets import cifar10 +except ImportError: + # In tf 1.8, np_utils doesn't seem to be publicly exposed. + # In later tf versions, it is, and in pre-tf keras it was too. + from tensorflow.python.keras import _impl + + np_utils = _impl.keras.utils.np_utils + # In tf 1.8, "from tensorflow.keras.datasets import cifar10" doesn't work even though the module exists + cifar10 = tf.keras.datasets.cifar10 + warnings.warn( + "Support for TensorFlow versions prior to 1.12 is deprecated." + " CleverHans using earlier versions may quit working on or after 2019-07-07." + ) +from cleverhans import utils + + +class Dataset(object): + """Abstract base class representing a dataset.""" + + # The number of classes in the dataset. Should be specified by subclasses. + NB_CLASSES = None + + def __init__(self, kwargs=None): + if kwargs is None: + kwargs = {} + if "self" in kwargs: + del kwargs["self"] + self.kwargs = kwargs + + def get_factory(self): + """Returns a picklable callable that recreates the dataset.""" + + return Factory(type(self), self.kwargs) + + def get_set(self, which_set): + """Returns the training set or test set as an (x_data, y_data) tuple. + :param which_set: 'train' or 'test' + """ + return (getattr(self, "x_" + which_set), getattr(self, "y_" + which_set)) + + def to_tensorflow(self): + raise NotImplementedError() + + @classmethod + def in_memory_dataset(cls, x, y, shuffle=None, repeat=True): + assert x.shape[0] == y.shape[0] + d = tf.data.Dataset.range(x.shape[0]) + if repeat: + d = d.repeat() + if shuffle: + d = d.shuffle(shuffle) + + def lookup(p): + return x[p], y[p] + + d = d.map(lambda i: tf.py_func(lookup, [i], [tf.float32] * 2)) + return d + + +class MNIST(Dataset): + """The MNIST dataset""" + + NB_CLASSES = 10 + + def __init__( + self, + train_start=0, + train_end=60000, + test_start=0, + test_end=10000, + center=False, + max_val=1.0, + ): + kwargs = locals() + if "__class__" in kwargs: + del kwargs["__class__"] + super(MNIST, self).__init__(kwargs) + x_train, y_train, x_test, y_test = data_mnist( + train_start=train_start, + train_end=train_end, + test_start=test_start, + test_end=test_end, + ) + + if center: + x_train = x_train * 2.0 - 1.0 + x_test = x_test * 2.0 - 1.0 + x_train *= max_val + x_test *= max_val + + self.x_train = x_train.astype("float32") + self.y_train = y_train.astype("float32") + self.x_test = x_test.astype("float32") + self.y_test = y_test.astype("float32") + + def to_tensorflow(self, shuffle=4096): + return ( + self.in_memory_dataset(self.x_train, self.y_train, shuffle), + self.in_memory_dataset(self.x_test, self.y_test, repeat=False), + ) + + +class CIFAR10(Dataset): + """The CIFAR-10 dataset""" + + NB_CLASSES = 10 + + LABEL_NAMES = [ + "airplane", + "automobile", + "bird", + "cat", + "deer", + "dog", + "frog", + "horse", + "ship", + "truck", + ] + + def __init__( + self, + train_start=0, + train_end=60000, + test_start=0, + test_end=10000, + center=False, + max_val=1.0, + ): + kwargs = locals() + if "__class__" in kwargs: + del kwargs["__class__"] + super(CIFAR10, self).__init__(kwargs) + packed = data_cifar10( + train_start=train_start, + train_end=train_end, + test_start=test_start, + test_end=test_end, + ) + x_train, y_train, x_test, y_test = packed + + if center: + x_train = x_train * 2.0 - 1.0 + x_test = x_test * 2.0 - 1.0 + x_train *= max_val + x_test *= max_val + + self.x_train = x_train + self.y_train = y_train + self.x_test = x_test + self.y_test = y_test + self.max_val = max_val + + def to_tensorflow(self, shuffle=4096): + # This is much more efficient with data augmentation, see tutorials. + return ( + self.in_memory_dataset(self.x_train, self.y_train, shuffle), + self.in_memory_dataset(self.x_test, self.y_test, repeat=False), + ) + + +class Factory(object): + """ + A callable that creates an object of the specified type and configuration. + """ + + def __init__(self, cls, kwargs): + self.cls = cls + self.kwargs = kwargs + + def __call__(self): + """Returns the created object.""" + return self.cls(**self.kwargs) + + +def maybe_download_file(url, datadir=None, force=False): + try: + from urllib.request import urlretrieve + except ImportError: + from urllib import urlretrieve + + if not datadir: + datadir = tempfile.gettempdir() + file_name = url[url.rfind("/") + 1 :] + dest_file = os.path.join(datadir, file_name) + + isfile = os.path.isfile(dest_file) + + if force or not isfile: + urlretrieve(url, dest_file) + return dest_file + + +def download_and_parse_mnist_file(file_name, datadir=None, force=False): + url = os.path.join('https://storage.googleapis.com/cvdf-datasets/mnist/', file_name) + file_name = maybe_download_file(url, datadir=datadir, force=force) + + # Open the file and unzip it if necessary + if os.path.splitext(file_name)[1] == ".gz": + open_fn = gzip.open + else: + open_fn = open + + # Parse the file + with open_fn(file_name, "rb") as file_descriptor: + header = file_descriptor.read(4) + assert len(header) == 4 + + zeros, data_type, n_dims = struct.unpack(">HBB", header) + assert zeros == 0 + + hex_to_data_type = { + 0x08: "B", + 0x09: "b", + 0x0B: "h", + 0x0C: "i", + 0x0D: "f", + 0x0E: "d", + } + data_type = hex_to_data_type[data_type] + + # data_type unicode to ascii conversion (Python2 fix) + if sys.version_info[0] < 3: + data_type = data_type.encode("ascii", "ignore") + + dim_sizes = struct.unpack(">" + "I" * n_dims, file_descriptor.read(4 * n_dims)) + + data = array.array(data_type, file_descriptor.read()) + data.byteswap() + + desired_items = functools.reduce(operator.mul, dim_sizes) + assert len(data) == desired_items + return np.array(data).reshape(dim_sizes) + + +def data_mnist( + datadir=tempfile.gettempdir(), + train_start=0, + train_end=60000, + test_start=0, + test_end=10000, +): + """ + Load and preprocess MNIST dataset + :param datadir: path to folder where data should be stored + :param train_start: index of first training set example + :param train_end: index of last training set example + :param test_start: index of first test set example + :param test_end: index of last test set example + :return: tuple of four arrays containing training data, training labels, + testing data and testing labels. + """ + assert isinstance(train_start, int) + assert isinstance(train_end, int) + assert isinstance(test_start, int) + assert isinstance(test_end, int) + + X_train = ( + download_and_parse_mnist_file("train-images-idx3-ubyte.gz", datadir=datadir) + / 255.0 + ) + Y_train = download_and_parse_mnist_file( + "train-labels-idx1-ubyte.gz", datadir=datadir + ) + X_test = ( + download_and_parse_mnist_file("t10k-images-idx3-ubyte.gz", datadir=datadir) + / 255.0 + ) + Y_test = download_and_parse_mnist_file("t10k-labels-idx1-ubyte.gz", datadir=datadir) + + X_train = np.expand_dims(X_train, -1) + X_test = np.expand_dims(X_test, -1) + + X_train = X_train[train_start:train_end] + Y_train = Y_train[train_start:train_end] + X_test = X_test[test_start:test_end] + Y_test = Y_test[test_start:test_end] + + Y_train = utils.to_categorical(Y_train, nb_classes=10) + Y_test = utils.to_categorical(Y_test, nb_classes=10) + return X_train, Y_train, X_test, Y_test + + + +def data_cifar10(train_start=0, train_end=50000, test_start=0, test_end=10000): + """ + Preprocess CIFAR10 dataset + :return: + """ + + # These values are specific to CIFAR10 + img_rows = 32 + img_cols = 32 + nb_classes = 10 + + # the data, shuffled and split between train and test sets + (x_train, y_train), (x_test, y_test) = cifar10.load_data() + + if tf.keras.backend.image_data_format() == "channels_first": + x_train = x_train.reshape(x_train.shape[0], 3, img_rows, img_cols) + x_test = x_test.reshape(x_test.shape[0], 3, img_rows, img_cols) + else: + x_train = x_train.reshape(x_train.shape[0], img_rows, img_cols, 3) + x_test = x_test.reshape(x_test.shape[0], img_rows, img_cols, 3) + x_train = x_train.astype("float32") + x_test = x_test.astype("float32") + x_train /= 255 + x_test /= 255 + print("x_train shape:", x_train.shape) + print(x_train.shape[0], "train samples") + print(x_test.shape[0], "test samples") + + # convert class vectors to binary class matrices + y_train = np_utils.to_categorical(y_train, nb_classes) + y_test = np_utils.to_categorical(y_test, nb_classes) + + x_train = x_train[train_start:train_end, :, :, :] + y_train = y_train[train_start:train_end, :] + x_test = x_test[test_start:test_end, :] + y_test = y_test[test_start:test_end, :] + + return x_train, y_train, x_test, y_test diff --git a/cleverhans_v3.1.0/cleverhans/devtools/LICENSE.txt b/cleverhans_v3.1.0/cleverhans/devtools/LICENSE.txt new file mode 100644 index 000000000..eaac7d77e --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans/devtools/LICENSE.txt @@ -0,0 +1,30 @@ +The devtools module is a derivative work from the devtools module of pylearn2. +We reproduce the corresponding license here. + +Copyright (c) 2011--2014, Université de Montréal +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/cleverhans/model_zoo/deep_k_nearest_neighbors/__init__.py b/cleverhans_v3.1.0/cleverhans/devtools/__init__.py similarity index 100% rename from cleverhans/model_zoo/deep_k_nearest_neighbors/__init__.py rename to cleverhans_v3.1.0/cleverhans/devtools/__init__.py diff --git a/cleverhans_v3.1.0/cleverhans/devtools/autopep8_all.py b/cleverhans_v3.1.0/cleverhans/devtools/autopep8_all.py new file mode 100644 index 000000000..74f54e39c --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans/devtools/autopep8_all.py @@ -0,0 +1,16 @@ +""" +Run this script to run autopep8 on everything in the library +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + + +from cleverhans.devtools.list_files import list_files +from cleverhans.utils import shell_call + +for f in list_files(".py"): + + command = ["autopep8", "-i", "--indent-size", "2", f] + shell_call(command) diff --git a/cleverhans_v3.1.0/cleverhans/devtools/checks.py b/cleverhans_v3.1.0/cleverhans/devtools/checks.py new file mode 100644 index 000000000..14c189f1d --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans/devtools/checks.py @@ -0,0 +1,32 @@ +"""Functionality for building tests. + +We have to call this file "checks" and not anything with "test" as a +substring or nosetests will execute it. +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import time +import unittest + +import numpy as np + + +class CleverHansTest(unittest.TestCase): + """TestCase with some extra features""" + + def setUp(self): + self.test_start = time.time() + # seed the randomness + np.random.seed(1234) + + def tearDown(self): + print(self.id(), "took", time.time() - self.test_start, "seconds") + + def assertClose(self, x, y, *args, **kwargs): + """Assert that `x` and `y` have close to the same value""" + # self.assertTrue(np.allclose(x, y)) doesn't give a useful message + # on failure + assert np.allclose(x, y, *args, **kwargs), (x, y) diff --git a/cleverhans_v3.1.0/cleverhans/devtools/list_files.py b/cleverhans_v3.1.0/cleverhans/devtools/list_files.py new file mode 100644 index 000000000..121352bd5 --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans/devtools/list_files.py @@ -0,0 +1,77 @@ +"""Code for listing files that belong to the library.""" +import os +import cleverhans + + +def list_files(suffix=""): + """ + Returns a list of all files in CleverHans with the given suffix. + + Parameters + ---------- + suffix : str + + Returns + ------- + + file_list : list + A list of all files in CleverHans whose filepath ends with `suffix`. + """ + + cleverhans_path = os.path.abspath(cleverhans.__path__[0]) + # In some environments cleverhans_path does not point to a real directory. + # In such case return empty list. + if not os.path.isdir(cleverhans_path): + return [] + repo_path = os.path.abspath(os.path.join(cleverhans_path, os.pardir)) + file_list = _list_files(cleverhans_path, suffix) + + extra_dirs = [ + "cleverhans_tutorials", + "examples", + "scripts", + "tests_tf", + "tests_pytorch", + ] + + for extra_dir in extra_dirs: + extra_path = os.path.join(repo_path, extra_dir) + if os.path.isdir(extra_path): + extra_files = _list_files(extra_path, suffix) + extra_files = [os.path.join(os.pardir, path) for path in extra_files] + file_list = file_list + extra_files + + return file_list + + +def _list_files(path, suffix=""): + """ + Returns a list of all files ending in `suffix` contained within `path`. + + Parameters + ---------- + path : str + a filepath + suffix : str + + Returns + ------- + l : list + A list of all files ending in `suffix` contained within `path`. + (If `path` is a file rather than a directory, it is considered + to "contain" itself) + """ + if os.path.isdir(path): + incomplete = os.listdir(path) + complete = [os.path.join(path, entry) for entry in incomplete] + lists = [_list_files(subpath, suffix) for subpath in complete] + flattened = [] + for one_list in lists: + for elem in one_list: + flattened.append(elem) + return flattened + else: + assert os.path.exists(path), "couldn't find file '%s'" % path + if path.endswith(suffix): + return [path] + return [] diff --git a/cleverhans_v3.1.0/cleverhans/devtools/mocks.py b/cleverhans_v3.1.0/cleverhans/devtools/mocks.py new file mode 100644 index 000000000..0556a70b3 --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans/devtools/mocks.py @@ -0,0 +1,83 @@ +"""Utility functions for mocking up tests. + +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import copy + +import numpy as np + +from cleverhans.dataset import Dataset, np_utils + + +def random_feed_dict(rng, placeholders): + """ + Returns random data to be used with `feed_dict`. + :param rng: A numpy.random.RandomState instance + :param placeholders: List of tensorflow placeholders + :return: A dict mapping placeholders to random numpy values + """ + + output = {} + + for placeholder in placeholders: + if placeholder.dtype != "float32": + raise NotImplementedError() + value = rng.randn(*placeholder.shape).astype("float32") + output[placeholder] = value + + return output + + +class SimpleDataset(Dataset): + """ + A dataset containing random values. + Values are uniformly distributed, either in [0, max_val] or [-1, max_val]. + """ + + def __init__( + self, + dim=2, + train_start=0, + train_end=3, + test_start=0, + test_end=5, + center=False, + max_val=1.0, + nb_classes=5, + ): + kwargs = copy.copy(locals()) + del kwargs["self"] + if "__class__" in kwargs: + del kwargs["__class__"] + super(SimpleDataset, self).__init__(kwargs) + self.__dict__.update(kwargs) + train_x_rng = np.random.RandomState([2018, 11, 9, 1]) + # Even if train_start is not 0, we should still generate the first training examples from the rng. + # This way the dataset looks like it is an array of deterministic data that we index using train_start. + self.x_train = train_x_rng.uniform( + -center * max_val, max_val, (train_end, dim) + )[train_start:] + # Use a second rng for the test set so that it also looks like an array of deterministic data that we + # index into, unaffected by the number of training examples. + test_x_rng = np.random.RandomState([2018, 11, 9, 2]) + self.x_test = test_x_rng.uniform(-center * max_val, max_val, (test_end, dim))[ + test_start: + ] + # Likewise, to keep the number of examples read from the rng affecting the values of the labels, we + # must generate the labels from a different rng + train_y_rng = np.random.RandomState([2018, 11, 9, 3]) + self.y_train = train_y_rng.randint(low=0, high=nb_classes, size=(train_end, 1))[ + train_start: + ] + test_y_rng = np.random.RandomState([2018, 11, 9, 4]) + self.y_test = test_y_rng.randint(low=0, high=nb_classes, size=(test_end, 1))[ + test_start: + ] + assert self.x_train.shape[0] == self.y_train.shape[0] + assert self.x_test.shape[0] == self.y_test.shape[0] + self.y_train = np_utils.to_categorical(self.y_train, nb_classes) + self.y_test = np_utils.to_categorical(self.y_test, nb_classes) diff --git a/examples/nips17_adversarial_competition/eval_infra/code/eval_lib/tests/__init__.py b/cleverhans_v3.1.0/cleverhans/devtools/tests/__init__.py similarity index 100% rename from examples/nips17_adversarial_competition/eval_infra/code/eval_lib/tests/__init__.py rename to cleverhans_v3.1.0/cleverhans/devtools/tests/__init__.py diff --git a/cleverhans_v3.1.0/cleverhans/devtools/tests/test_format.py b/cleverhans_v3.1.0/cleverhans/devtools/tests/test_format.py new file mode 100644 index 000000000..07a2d69e9 --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans/devtools/tests/test_format.py @@ -0,0 +1,140 @@ +""" +Unit tests for format checking +""" + +from __future__ import print_function + + +import os +import subprocess + +import cleverhans +from cleverhans.devtools.list_files import list_files +from cleverhans.utils import shell_call + +# Enter a manual list of files that are allowed to violate PEP8 here +whitelist_pep8 = [ + # This file is broken but could be fixed + "../examples/multigpu_advtrain/test_attack_multigpu.py" +] + +all_py_files = list_files(".py") + + +def update_whitelist(): + """Add files to the whitelist""" + global whitelist_pep8 + # We don't want to test RL-attack because it has so many dependencies + # not used elsewhere, and pylint wants to import them all + whitelist_pep8.extend( + [ + os.path.relpath(path, cleverhans.__path__[0]) + for path in all_py_files + if "RL-attack" in path + ] + ) + # Similarly, we don't want to require robust_vision_benchmark installed + whitelist_pep8.extend( + [ + os.path.relpath(path, cleverhans.__path__[0]) + for path in all_py_files + if "robust_vision_benchmark" in path + ] + ) + # Similarly, we don't want to require that cloud be installed + whitelist_pep8.extend( + [ + os.path.relpath(path, cleverhans.__path__[0]) + for path in all_py_files + if "cloud_client" in path + ] + ) + # This example has more dependencies too + whitelist_pep8.extend( + [ + os.path.relpath(path, cleverhans.__path__[0]) + for path in all_py_files + if "facenet_adversarial_faces" in path + ] + ) + # This too + whitelist_pep8.extend( + [ + os.path.relpath(path, cleverhans.__path__[0]) + for path in all_py_files + if "madry_lab_challenges" in path + ] + ) + # This code is no longer compatible with new versions of cleverhans / scipy and will be removed soon + whitelist_pep8.extend( + [ + os.path.relpath(path, cleverhans.__path__[0]) + for path in all_py_files + if "nips17_adversarial_competition" in path + ] + ) + + +update_whitelist() + + +whitelist_docstrings = [] + + +def test_format_pep8(): + """ + Test if pep8 is respected. + """ + files_to_check = [] + module_dir = cleverhans.__path__[0] + for path in all_py_files: + rel_path = os.path.relpath(path, module_dir) + if rel_path in whitelist_pep8: + continue + else: + files_to_check.append(path) + repo_dir = os.path.join(module_dir, os.pardir) + rcpath = os.path.join(repo_dir, ".pylintrc") + assert os.path.exists(rcpath) + + # We must run pylint via the command line and subprocess because of + # problems with the pylint module. + # The documentation claims you can run it as a python module, but + # the documentation is wrong: https://github.com/PyCQA/pylint/issues/1870 + # If you run the version described in the linked issue, pylint + # calls sys.exit once it is done, so it kills the test. + + # Running all files in one pylint command is important for 2 reasons: + # 1) Correctness: pylint can detect issues that require access to multiple + # files, such as cyclic imports + # 2) Speed: pylint imports modules for deep analysis, so if you run + # multiple subprocesses each needs to re-import tensorflow. + # On Ian's laptop, pylint takes about 10s per file to run on the repo, + # and there are about 90 files as of the writing of this comment. + # Running pylint on all files simultaneously takes about 70s, so it + # is a little better than a 10X speedup. + + # Running multiple jobs in parallel helps but far less than linearly. + # On Ian's 4-core laptop, running 4 jobs drops the runtime from 70s + # to 45s. + # Some of the work is I/O, so it actually makes some sense to run + # more jobs than cores. On Ian's 4-core laptop, running 8 jobs drops + # the runtime to 40s. + # There's a further complication though: I think each job needs to + # redo imports, so the total amount of work to do increases with + # the number of jobs. On Ian's laptop, using 64 jobs causes the + # runtime to increase to 220s. There is not an obvious simple + # formula like "use one job per CPU core" or "use way more jobs + # than cores to saturate I/O". For now I'm hoping that 8 will be + # a reasonable default: it gets good performance on my laptop, + # and on machines with fewer than 4 cores there should still be + # a benefit to not being blocked on I/O. + + try: + shell_call(["pylint", "--rcfile", rcpath, "--jobs", "8"] + files_to_check) + except subprocess.CalledProcessError as e: + raise ValueError(e.output.decode("utf-8")) + + +if __name__ == "__main__": + test_format_pep8() diff --git a/cleverhans_v3.1.0/cleverhans/devtools/version.py b/cleverhans_v3.1.0/cleverhans/devtools/version.py new file mode 100644 index 000000000..22b5e8e89 --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans/devtools/version.py @@ -0,0 +1,36 @@ +""" +Utility functions for keeping track of the version of CleverHans. + +These functions provide a finer level of granularity than the +manually specified version string attached to each release. +""" +import hashlib +from cleverhans.devtools.list_files import list_files + + +def dev_version(): + """ + Returns a hexdigest of all the python files in the module. + """ + + md5_hash = hashlib.md5() + py_files = sorted(list_files(suffix=".py")) + if not py_files: + return "" + for filename in py_files: + with open(filename, "rb") as fobj: + content = fobj.read() + md5_hash.update(content) + return md5_hash.hexdigest() + + +def append_dev_version(release_version): + """ + If dev version is not empty appends it to release_version. + """ + + dev_version_value = dev_version() + if dev_version_value: + return release_version + "-" + dev_version_value + else: + return release_version diff --git a/cleverhans_v3.1.0/cleverhans/evaluation.py b/cleverhans_v3.1.0/cleverhans/evaluation.py new file mode 100644 index 000000000..abfe18615 --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans/evaluation.py @@ -0,0 +1,794 @@ +""" +Functionality for evaluating expressions across entire datasets. +Includes multi-GPU support for fast evaluation. +""" + +from distutils.version import LooseVersion +import warnings +import numpy as np +from six.moves import range +import tensorflow as tf + +import cleverhans +from cleverhans import canary +from cleverhans.utils import create_logger +from cleverhans.utils_tf import infer_devices + + +def accuracy( + sess, + model, + x, + y, + batch_size=None, + devices=None, + feed=None, + attack=None, + attack_params=None, +): + """ + Compute the accuracy of a TF model on some data + :param sess: TF session to use when training the graph + :param model: cleverhans.model.Model instance + :param x: numpy array containing input examples (e.g. MNIST().x_test ) + :param y: numpy array containing example labels (e.g. MNIST().y_test ) + :param batch_size: Number of examples to use in a single evaluation batch. + If not specified, this function will use a reasonable guess and + may run out of memory. + When choosing the batch size, keep in mind that the batch will + be divided up evenly among available devices. If you can fit 128 + examples in memory on one GPU and you have 8 GPUs, you probably + want to use a batch size of 1024 (unless a different batch size + runs faster with the ops you are using, etc.) + :param devices: An optional list of string device names to use. + If not specified, this function will use all visible GPUs. + :param feed: An optional dictionary that is appended to the feeding + dictionary before the session runs. Can be used to feed + the learning phase of a Keras model for instance. + :param attack: cleverhans.attack.Attack + Optional. If no attack specified, evaluates the model on clean data. + If attack is specified, evaluates the model on adversarial examples + created by the attack. + :param attack_params: dictionary + If attack is specified, this dictionary is passed to attack.generate + as keyword arguments. + :return: a float with the accuracy value + """ + + _check_x(x) + _check_y(y) + if x.shape[0] != y.shape[0]: + raise ValueError("Number of input examples and labels do not match.") + + factory = _CorrectFactory(model, attack, attack_params) + + (correct,) = batch_eval_multi_worker( + sess, factory, [x, y], batch_size=batch_size, devices=devices, feed=feed + ) + + return correct.mean() + + +def class_and_confidence( + sess, + model, + x, + y=None, + batch_size=None, + devices=None, + feed=None, + attack=None, + attack_params=None, +): + """ + Return the model's classification of the input data, and the confidence + (probability) assigned to each example. + :param sess: tf.Session + :param model: cleverhans.model.Model + :param x: numpy array containing input examples (e.g. MNIST().x_test ) + :param y: numpy array containing true labels + (Needed only if using an attack that avoids these labels) + :param batch_size: Number of examples to use in a single evaluation batch. + If not specified, this function will use a reasonable guess and + may run out of memory. + When choosing the batch size, keep in mind that the batch will + be divided up evenly among available devices. If you can fit 128 + examples in memory on one GPU and you have 8 GPUs, you probably + want to use a batch size of 1024 (unless a different batch size + runs faster with the ops you are using, etc.) + :param devices: An optional list of string device names to use. + If not specified, this function will use all visible GPUs. + :param feed: An optional dictionary that is appended to the feeding + dictionary before the session runs. Can be used to feed + the learning phase of a Keras model for instance. + :param attack: cleverhans.attack.Attack + Optional. If no attack specified, evaluates the model on clean data. + If attack is specified, evaluates the model on adversarial examples + created by the attack. + :param attack_params: dictionary + If attack is specified, this dictionary is passed to attack.generate + as keyword arguments. + :return: + an ndarray of ints indicating the class assigned to each example + an ndarray of probabilities assigned to the prediction for each example + """ + + _check_x(x) + inputs = [x] + if attack is not None: + inputs.append(y) + _check_y(y) + if x.shape[0] != y.shape[0]: + raise ValueError("Number of input examples and labels do not match.") + + factory = _ClassAndProbFactory(model, attack, attack_params) + + out = batch_eval_multi_worker( + sess, factory, inputs, batch_size=batch_size, devices=devices, feed=feed + ) + + classes, confidence = out + + assert classes.shape == (x.shape[0],) + assert confidence.shape == (x.shape[0],) + min_confidence = confidence.min() + if min_confidence < 0.0: + raise ValueError( + "Model does not return valid probabilities: " + str(min_confidence) + ) + max_confidence = confidence.max() + if max_confidence > 1.0: + raise ValueError( + "Model does not return valid probablities: " + str(max_confidence) + ) + assert confidence.min() >= 0.0, confidence.min() + + return out + + +def correctness_and_confidence( + sess, + model, + x, + y, + batch_size=None, + devices=None, + feed=None, + attack=None, + attack_params=None, +): + """ + Report whether the model is correct and its confidence on each example in + a dataset. + :param sess: tf.Session + :param model: cleverhans.model.Model + :param x: numpy array containing input examples (e.g. MNIST().x_test ) + :param y: numpy array containing example labels (e.g. MNIST().y_test ) + :param batch_size: Number of examples to use in a single evaluation batch. + If not specified, this function will use a reasonable guess and + may run out of memory. + When choosing the batch size, keep in mind that the batch will + be divided up evenly among available devices. If you can fit 128 + examples in memory on one GPU and you have 8 GPUs, you probably + want to use a batch size of 1024 (unless a different batch size + runs faster with the ops you are using, etc.) + :param devices: An optional list of string device names to use. + If not specified, this function will use all visible GPUs. + :param feed: An optional dictionary that is appended to the feeding + dictionary before the session runs. Can be used to feed + the learning phase of a Keras model for instance. + :param attack: cleverhans.attack.Attack + Optional. If no attack specified, evaluates the model on clean data. + If attack is specified, evaluates the model on adversarial examples + created by the attack. + :param attack_params: dictionary + If attack is specified, this dictionary is passed to attack.generate + as keyword arguments. + :return: + an ndarray of bools indicating whether each example is correct + an ndarray of probabilities assigned to the prediction for each example + """ + + _check_x(x) + _check_y(y) + if x.shape[0] != y.shape[0]: + raise ValueError("Number of input examples and labels do not match.") + + factory = _CorrectAndProbFactory(model, attack, attack_params) + + out = batch_eval_multi_worker( + sess, factory, [x, y], batch_size=batch_size, devices=devices, feed=feed + ) + + correctness, confidence = out + + assert correctness.shape == (x.shape[0],) + assert confidence.shape == (x.shape[0],) + min_confidence = confidence.min() + if min_confidence < 0.0: + raise ValueError( + "Model does not return valid probabilities: " + str(min_confidence) + ) + max_confidence = confidence.max() + if max_confidence > 1.0: + raise ValueError( + "Model does not return valid probablities: " + str(max_confidence) + ) + assert confidence.min() >= 0.0, confidence.min() + + return out + + +def run_attack( + sess, + model, + x, + y, + attack, + attack_params, + batch_size=None, + devices=None, + feed=None, + pass_y=False, +): + """ + Run attack on every example in a dataset. + :param sess: tf.Session + :param model: cleverhans.model.Model + :param x: numpy array containing input examples (e.g. MNIST().x_test ) + :param y: numpy array containing example labels (e.g. MNIST().y_test ) + :param attack: cleverhans.attack.Attack + :param attack_params: dictionary + passed to attack.generate as keyword arguments. + :param batch_size: Number of examples to use in a single evaluation batch. + If not specified, this function will use a reasonable guess and + may run out of memory. + When choosing the batch size, keep in mind that the batch will + be divided up evenly among available devices. If you can fit 128 + examples in memory on one GPU and you have 8 GPUs, you probably + want to use a batch size of 1024 (unless a different batch size + runs faster with the ops you are using, etc.) + :param devices: An optional list of string device names to use. + If not specified, this function will use all visible GPUs. + :param feed: An optional dictionary that is appended to the feeding + dictionary before the session runs. Can be used to feed + the learning phase of a Keras model for instance. + :param pass_y: bool. If true pass 'y' to `attack.generate` + :return: + an ndarray of bools indicating whether each example is correct + an ndarray of probabilities assigned to the prediction for each example + """ + + _check_x(x) + _check_y(y) + + factory = _AttackFactory(model, attack, attack_params, pass_y) + + (out,) = batch_eval_multi_worker( + sess, factory, [x, y], batch_size=batch_size, devices=devices, feed=feed + ) + return out + + +def batch_eval_multi_worker( + sess, graph_factory, numpy_inputs, batch_size=None, devices=None, feed=None +): + """ + Generic computation engine for evaluating an expression across a whole + dataset, divided into batches. + + This function assumes that the work can be parallelized with one worker + device handling one batch of data. If you need multiple devices per + batch, use `batch_eval`. + + The tensorflow graph for multiple workers is large, so the first few + runs of the graph will be very slow. If you expect to run the graph + few times (few calls to `batch_eval_multi_worker` that each run few + batches) the startup cost might dominate the runtime, and it might be + preferable to use the single worker `batch_eval` just because its + startup cost will be lower. + + :param sess: tensorflow Session + :param graph_factory: callable + When called, returns (tf_inputs, tf_outputs) where: + tf_inputs is a list of placeholders to feed from the dataset + tf_outputs is a list of tf tensors to calculate + Example: tf_inputs is [x, y] placeholders, tf_outputs is [accuracy]. + This factory must make new tensors when called, rather than, e.g. + handing out a reference to existing tensors. + This factory must make exactly equivalent expressions every time + it is called, otherwise the results of `batch_eval` will vary + depending on how work is distributed to devices. + This factory must respect "with tf.device()" context managers + that are active when it is called, otherwise work will not be + distributed to devices correctly. + :param numpy_inputs: + A list of numpy arrays defining the dataset to be evaluated. + The list should have the same length as tf_inputs. + Each array should have the same number of examples (shape[0]). + Example: numpy_inputs is [MNIST().x_test, MNIST().y_test] + :param batch_size: Number of examples to use in a single evaluation batch. + If not specified, this function will use a reasonable guess and + may run out of memory. + When choosing the batch size, keep in mind that the batch will + be divided up evenly among available devices. If you can fit 128 + examples in memory on one GPU and you have 8 GPUs, you probably + want to use a batch size of 1024 (unless a different batch size + runs faster with the ops you are using, etc.) + :param devices: List of devices to run on. If unspecified, uses all + available GPUs if any GPUS are available, otherwise uses CPUs. + :param feed: An optional dictionary that is appended to the feeding + dictionary before the session runs. Can be used to feed + the learning phase of a Keras model for instance. + :returns: List of numpy arrays corresponding to the outputs produced by + the graph_factory + """ + canary.run_canary() + global _batch_eval_multi_worker_cache + + devices = infer_devices(devices) + + if batch_size is None: + # For big models this might result in OOM and then the user + # should just specify batch_size + batch_size = len(devices) * DEFAULT_EXAMPLES_PER_DEVICE + + n = len(numpy_inputs) + assert n > 0 + m = numpy_inputs[0].shape[0] + for i in range(1, n): + m_i = numpy_inputs[i].shape[0] + if m != m_i: + raise ValueError( + "All of numpy_inputs must have the same number of examples, but the first one has " + + str(m) + + " examples and input " + + str(i) + + " has " + + str(m_i) + + "examples." + ) + out = [] + + replicated_tf_inputs = [] + replicated_tf_outputs = [] + p = None + + num_devices = len(devices) + assert batch_size % num_devices == 0 + device_batch_size = batch_size // num_devices + + cache_key = (graph_factory, tuple(devices)) + if cache_key in _batch_eval_multi_worker_cache: + # Retrieve graph for multi-GPU inference from cache. + # This avoids adding tf ops to the graph + packed = _batch_eval_multi_worker_cache[cache_key] + replicated_tf_inputs, replicated_tf_outputs = packed + p = len(replicated_tf_outputs[0]) + assert p > 0 + else: + # This graph has not been built before. + # Build it now. + + for device in devices: + with tf.device(device): + tf_inputs, tf_outputs = graph_factory() + assert len(tf_inputs) == n + if p is None: + p = len(tf_outputs) + assert p > 0 + else: + assert len(tf_outputs) == p + replicated_tf_inputs.append(tf_inputs) + replicated_tf_outputs.append(tf_outputs) + del tf_inputs + del tf_outputs + # Store the result in the cache + packed = replicated_tf_inputs, replicated_tf_outputs + _batch_eval_multi_worker_cache[cache_key] = packed + for _ in range(p): + out.append([]) + flat_tf_outputs = [] + for output in range(p): + for dev_idx in range(num_devices): + flat_tf_outputs.append(replicated_tf_outputs[dev_idx][output]) + + # pad data to have # examples be multiple of batch size + # we discard the excess later + num_batches = int(np.ceil(float(m) / batch_size)) + needed_m = num_batches * batch_size + excess = needed_m - m + if excess > m: + raise NotImplementedError( + ( + "Your batch size (%(batch_size)d) is bigger" + " than the dataset (%(m)d), this function is " + "probably overkill." + ) + % locals() + ) + + def pad(array): + """Pads an array with replicated examples to have `excess` more entries""" + if excess > 0: + array = np.concatenate((array, array[:excess]), axis=0) + return array + + numpy_inputs = [pad(numpy_input) for numpy_input in numpy_inputs] + orig_m = m + m = needed_m + + for start in range(0, m, batch_size): + batch = start // batch_size + if batch % 100 == 0 and batch > 0: + _logger.debug("Batch " + str(batch)) + + # Compute batch start and end indices + end = start + batch_size + numpy_input_batches = [numpy_input[start:end] for numpy_input in numpy_inputs] + feed_dict = {} + for dev_idx, tf_inputs in enumerate(replicated_tf_inputs): + for tf_input, numpy_input in zip(tf_inputs, numpy_input_batches): + dev_start = dev_idx * device_batch_size + dev_end = (dev_idx + 1) * device_batch_size + value = numpy_input[dev_start:dev_end] + assert value.shape[0] == device_batch_size + feed_dict[tf_input] = value + if feed is not None: + feed_dict.update(feed) + flat_output_batches = sess.run(flat_tf_outputs, feed_dict=feed_dict) + for e in flat_output_batches: + assert e.shape[0] == device_batch_size, e.shape + + output_batches = [] + for output in range(p): + o_start = output * num_devices + o_end = (output + 1) * num_devices + device_values = flat_output_batches[o_start:o_end] + assert len(device_values) == num_devices + output_batches.append(device_values) + + for out_elem, device_values in zip(out, output_batches): + assert len(device_values) == num_devices, (len(device_values), num_devices) + for device_value in device_values: + assert device_value.shape[0] == device_batch_size + out_elem.extend(device_values) + + out = [np.concatenate(x, axis=0) for x in out] + for e in out: + assert e.shape[0] == m, e.shape + + # Trim off the examples we used to pad up to batch size + out = [e[:orig_m] for e in out] + assert len(out) == p, (len(out), p) + + return out + + +def batch_eval( + sess, tf_inputs, tf_outputs, numpy_inputs, batch_size=None, feed=None, args=None +): + """ + A helper function that computes a tensor on numpy inputs by batches. + This version uses exactly the tensorflow graph constructed by the + caller, so the caller can place specific ops on specific devices + to implement model parallelism. + Most users probably prefer `batch_eval_multi_worker` which maps + a single-device expression to multiple devices in order to evaluate + faster by parallelizing across data. + + :param sess: tf Session to use + :param tf_inputs: list of tf Placeholders to feed from the dataset + :param tf_outputs: list of tf tensors to calculate + :param numpy_inputs: list of numpy arrays defining the dataset + :param batch_size: int, batch size to use for evaluation + If not specified, this function will try to guess the batch size, + but might get an out of memory error or run the model with an + unsupported batch size, etc. + :param feed: An optional dictionary that is appended to the feeding + dictionary before the session runs. Can be used to feed + the learning phase of a Keras model for instance. + :param args: dict or argparse `Namespace` object. + Deprecated and included only for backwards compatibility. + Should contain `batch_size` + """ + + if args is not None: + warnings.warn( + "`args` is deprecated and will be removed on or " + "after 2019-03-09. Pass `batch_size` directly." + ) + if "batch_size" in args: + assert batch_size is None + batch_size = args["batch_size"] + + if batch_size is None: + batch_size = DEFAULT_EXAMPLES_PER_DEVICE + + n = len(numpy_inputs) + assert n > 0 + assert n == len(tf_inputs) + m = numpy_inputs[0].shape[0] + for i in range(1, n): + assert numpy_inputs[i].shape[0] == m + out = [] + for _ in tf_outputs: + out.append([]) + for start in range(0, m, batch_size): + batch = start // batch_size + if batch % 100 == 0 and batch > 0: + _logger.debug("Batch " + str(batch)) + + # Compute batch start and end indices + start = batch * batch_size + end = start + batch_size + numpy_input_batches = [numpy_input[start:end] for numpy_input in numpy_inputs] + cur_batch_size = numpy_input_batches[0].shape[0] + assert cur_batch_size <= batch_size + for e in numpy_input_batches: + assert e.shape[0] == cur_batch_size + + feed_dict = dict(zip(tf_inputs, numpy_input_batches)) + if feed is not None: + feed_dict.update(feed) + numpy_output_batches = sess.run(tf_outputs, feed_dict=feed_dict) + for e in numpy_output_batches: + assert e.shape[0] == cur_batch_size, e.shape + for out_elem, numpy_output_batch in zip(out, numpy_output_batches): + out_elem.append(numpy_output_batch) + + out = [np.concatenate(x, axis=0) for x in out] + for e in out: + assert e.shape[0] == m, e.shape + return out + + +DEFAULT_EXAMPLES_PER_DEVICE = 128 + + +class _CorrectFactory(object): + """ + A factory for an expression for one bool per example indicating + whether each example is correct. + """ + + def __init__(self, model, attack=None, attack_params=None): + if attack_params is None: + attack_params = {} + self.model = model + self.attack = attack + self.attack_params = attack_params + hashable_attack_params = tuple( + (key, attack_params[key]) for key in sorted(attack_params.keys()) + ) + self.properties_to_hash = (model, attack, hashable_attack_params) + + def __hash__(self): + # Make factory hashable so that no two factories for the + # same model will be used to build redundant tf graphs + return self.properties_to_hash.__hash__() + + def __eq__(self, other): + # Make factory hashable so that no two factories for the + # same model will be used to build redundant tf graphs + if not isinstance(other, _CorrectFactory): + return False + return self.properties_to_hash == other.properties_to_hash + + def __call__(self): + x_batch = self.model.make_input_placeholder() + y_batch = self.model.make_label_placeholder() + + if LooseVersion(tf.__version__) < LooseVersion("1.0.0"): + raise NotImplementedError() + + if self.attack is None: + x_input = x_batch + else: + attack_params = self.attack_params + if attack_params is None: + attack_params = {} + x_input = self.attack.generate(x_batch, y=y_batch, **attack_params) + + predictions = self.model.get_probs(x_input) + correct = tf.equal(tf.argmax(y_batch, axis=-1), tf.argmax(predictions, axis=-1)) + + return (x_batch, y_batch), (correct,) + + +class _ClassAndProbFactory(object): + """ + A factory for an expression for the following tuple per (optionally + adversarial) example: + - integer class assigned to the example by the model + - probability assigned to that prediction + """ + + def __init__(self, model, attack=None, attack_params=None): + if attack_params is None: + attack_params = {} + self.model = model + self.attack = attack + self.attack_params = attack_params + hashable_attack_params = tuple( + (key, attack_params[key]) for key in sorted(attack_params.keys()) + ) + self.properties_to_hash = (model, attack, hashable_attack_params) + + def __hash__(self): + # Make factory hashable so that no two factories for the + # same model will be used to build redundant tf graphs + return self.properties_to_hash.__hash__() + + def __eq__(self, other): + # Make factory hashable so that no two factories for the + # same model will be used to build redundant tf graphs + if not isinstance(other, _ClassAndProbFactory): + return False + return self.properties_to_hash == other.properties_to_hash + + def __call__(self): + x_batch = self.model.make_input_placeholder() + inputs = [x_batch] + + if LooseVersion(tf.__version__) < LooseVersion("1.0.0"): + raise NotImplementedError() + + if self.attack is None: + x_input = x_batch + else: + y_batch = self.model.make_label_placeholder() + inputs.append(y_batch) + attack_params = self.attack_params + if attack_params is None: + attack_params = {} + x_input = self.attack.generate(x_batch, y=y_batch, **attack_params) + + predictions = self.model.get_probs(x_input) + classes = tf.argmax(predictions, axis=-1) + max_probs = tf.reduce_max(predictions, axis=1) + + return tuple(inputs), (classes, max_probs) + + +class _CorrectAndProbFactory(object): + """ + A factory for an expression for the following tuple per (optionally + adversarial) example: + - bool per indicating whether each the example was classified correctly + - probability assigned to that prediction + """ + + def __init__(self, model, attack=None, attack_params=None): + if attack_params is None: + attack_params = {} + self.model = model + self.attack = attack + self.attack_params = attack_params + hashable_attack_params = tuple( + (key, attack_params[key]) for key in sorted(attack_params.keys()) + ) + self.properties_to_hash = (model, attack, hashable_attack_params) + + def __hash__(self): + # Make factory hashable so that no two factories for the + # same model will be used to build redundant tf graphs + return self.properties_to_hash.__hash__() + + def __eq__(self, other): + # Make factory hashable so that no two factories for the + # same model will be used to build redundant tf graphs + if not isinstance(other, _CorrectAndProbFactory): + return False + return self.properties_to_hash == other.properties_to_hash + + def __call__(self): + x_batch = self.model.make_input_placeholder() + y_batch = self.model.make_label_placeholder() + + if LooseVersion(tf.__version__) < LooseVersion("1.0.0"): + raise NotImplementedError() + + if self.attack is None: + x_input = x_batch + else: + attack_params = self.attack_params + if attack_params is None: + attack_params = {} + x_input = self.attack.generate(x_batch, y=y_batch, **attack_params) + + predictions = self.model.get_probs(x_input) + correct = tf.equal(tf.argmax(y_batch, axis=-1), tf.argmax(predictions, axis=-1)) + max_probs = tf.reduce_max(predictions, axis=1) + + return (x_batch, y_batch), (correct, max_probs) + + +class _AttackFactory(object): + """ + A factory for an expression that runs an adversarial attack + + :param model: cleverhans.model.Model + :param attack: cleverhans.attack.Attack + :param attack_params: dict of arguments to pass to attack.generate + :param pass_y: bool. If True, pass y to the attack. + (Some untargeted attacks prefer to infer y to avoid label leaking. + Targeted attacks require that y not be passed) + """ + + def __init__(self, model, attack, attack_params=None, pass_y=False): + assert isinstance(model, cleverhans.model.Model) + if not isinstance(attack, cleverhans.attacks.Attack): + raise TypeError( + "`attack` must be an instance of cleverhans.attacks." + "attack. Got %s with type %s " % (str(attack), str(type(attack))) + ) + + if attack_params is None: + attack_params = {} + self.model = model + self.attack = attack + self.attack_params = attack_params + self.pass_y = pass_y + hashable_attack_params = tuple( + (key, attack_params[key]) for key in sorted(attack_params.keys()) + ) + self.properties_to_hash = (model, attack, hashable_attack_params) + + def __hash__(self): + # Make factory hashable so that no two factories for the + # same model will be used to build redundant tf graphs + return self.properties_to_hash.__hash__() + + def __eq__(self, other): + # Make factory hashable so that no two factories for the + # same model will be used to build redundant tf graphs + if not isinstance(other, _AttackFactory): + return False + return self.properties_to_hash == other.properties_to_hash + + def __call__(self): + x_batch = self.model.make_input_placeholder() + y_batch = self.model.make_label_placeholder() + + attack_params = self.attack_params + if attack_params is None: + attack_params = {} + if self.pass_y: + x_adv = self.attack.generate(x_batch, y=y_batch, **attack_params) + else: + # Some code checks the keys of kwargs, rather than checking if + # y is None, so we need to truly not pass y at all, rather than + # just passing a None value for it. + x_adv = self.attack.generate(x_batch, **attack_params) + + return (x_batch, y_batch), tuple([x_adv]) + + +_logger = create_logger("cleverhans.evaluation") + +# Cache for storing output of `batch_eval_multi_worker`'s calls to +# `graph_factory`, to avoid making the tf graph too big +_batch_eval_multi_worker_cache = {} + + +def _check_x(x): + """ + Makes sure an `x` argument is a valid numpy dataset. + """ + if not isinstance(x, np.ndarray): + raise TypeError( + "x must be a numpy array. Typically x contains " + "the entire test set inputs." + ) + + +def _check_y(y): + """ + Makes sure a `y` argument is a vliad numpy dataset. + """ + if not isinstance(y, np.ndarray): + raise TypeError( + "y must be numpy array. Typically y contains " + "the entire test set labels. Got " + str(y) + " of type " + str(type(y)) + ) diff --git a/cleverhans_v3.1.0/cleverhans/experimental/README.md b/cleverhans_v3.1.0/cleverhans/experimental/README.md new file mode 100644 index 000000000..f4499ecd3 --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans/experimental/README.md @@ -0,0 +1,2 @@ +This directory contains experimental features of cleverhans, which are not +integrated into the main API yet. diff --git a/cleverhans_v3.1.0/cleverhans/experimental/__init__.py b/cleverhans_v3.1.0/cleverhans/experimental/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/cleverhans_v3.1.0/cleverhans/experimental/certification/README.md b/cleverhans_v3.1.0/cleverhans/experimental/certification/README.md new file mode 100644 index 000000000..4fbfdf336 --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans/experimental/certification/README.md @@ -0,0 +1,55 @@ +# Certification of adversarial robustness + +This code performs certification of adversarial robustness of given network on +given example. + +Original author: [Aditi Raghunathan](https://github.com/RAditi) + +## Dependencies + +TensorFlow 1.9.0 or higher (to have support of necessary +[autograph](https://www.tensorflow.org/guide/autograph) features) + +## Usage + +Example usage for two-layer network trained on MNIST: + +```bash +# Meaning of the arguments: +# checkpoint - TensorFlow checkpoint of the model parameters +# model_json - JSON file which describes the model (see below) +# test_input - numpy file with input to certify +# true_class - true class of the input +# adv_class - adversarial class for which we're checking certificate, +# -1 means that all adversarial classes will be checked +# epsilon - maximum size of adversarial perturbation +# init_nu and small_eig_num_steps - optimization parameters +python cleverhans/experimental/certification/certify.py \ + --checkpoint "${MODEL_DIR}/model.ckpt" \ + --model_json "${MODEL_DIR}/model.json" \ + --test_input "${INPUTS_DIR}/image.npy" \ + --true_class 4 \ + --adv_class 5 \ + --epsilon 0.2 \ + --init_nu 100.0 \ + --small_eig_num_steps 100 +``` + +JSON file which accompany checkpoint and describes layers of the network: + +```json +[ + { + "weight_var": "Model/layer0/logits/kernel", + "bias_var": "Model/layer0/logits/bias", + "type": "ff_relu", + "is_transpose": true + }, + { + "weight_var": "Model/logits/kernel", + "bias_var": "Model/logits/bias", + "type": "ff", + "is_transpose": true + } +] +``` diff --git a/cleverhans_v3.1.0/cleverhans/experimental/certification/__init__.py b/cleverhans_v3.1.0/cleverhans/experimental/certification/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/cleverhans_v3.1.0/cleverhans/experimental/certification/certify.py b/cleverhans_v3.1.0/cleverhans/experimental/certification/certify.py new file mode 100644 index 000000000..08c873c16 --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans/experimental/certification/certify.py @@ -0,0 +1,166 @@ +"""Code for running the certification problem.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import time +import numpy as np +import tensorflow as tf + +from cleverhans.experimental.certification import dual_formulation +from cleverhans.experimental.certification import nn +from cleverhans.experimental.certification import optimization +from cleverhans.experimental.certification import utils + +flags = tf.app.flags +FLAGS = flags.FLAGS +flags.DEFINE_string( + "checkpoint", None, "Path of checkpoint with trained model to verify" +) +flags.DEFINE_string("model_json", None, "Path of json file with model description") +flags.DEFINE_string( + "init_dual_file", None, "Path of numpy file with dual variables to initialize" +) +flags.DEFINE_string("test_input", None, "Path of numpy file with test input to certify") +flags.DEFINE_integer("true_class", 0, "True class of the test input") +flags.DEFINE_integer( + "adv_class", -1, "target class of adversarial example; all classes if -1" +) +flags.DEFINE_float("input_minval", -1, "Minimum value of valid input") +flags.DEFINE_float("input_maxval", 1, "Maximum value of valid input") +flags.DEFINE_float("epsilon", 0.2, "Size of perturbation") +# Nu might need tuning based on the network +flags.DEFINE_float("init_nu", 300.0, "Initialization of nu variable.") +flags.DEFINE_float("init_penalty", 100.0, "Initial penalty") +flags.DEFINE_integer( + "small_eig_num_steps", 500, "Number of eigen value steps in intermediate iterations" +) +flags.DEFINE_integer( + "large_eig_num_steps", 5000, "Number of eigen value steps in each outer iteration" +) +flags.DEFINE_integer("inner_num_steps", 600, "Number of steps to run in inner loop") +flags.DEFINE_float("outer_num_steps", 10, "Number of steps to run in outer loop") +flags.DEFINE_float("beta", 2, "Multiplicative factor to increase penalty by") +flags.DEFINE_float( + "smoothness_parameter", 0.001, "Smoothness parameter if using eigen decomposition" +) +flags.DEFINE_float( + "eig_learning_rate", 0.001, "Learning rate for computing min eigen value" +) +flags.DEFINE_string("optimizer", "adam", "Optimizer to use for entire optimization") +flags.DEFINE_float("init_learning_rate", 0.1, "Initial learning rate") +flags.DEFINE_float("learning_rate_decay", 0.1, "Decay of learning rate") +flags.DEFINE_float( + "momentum_parameter", 0.9, "Momentum parameter if using momentum optimizer" +) +flags.DEFINE_integer("print_stats_steps", 50, "Number of steps to print stats after") +flags.DEFINE_string("stats_folder", None, "Folder to save stats of the iterations") +flags.DEFINE_integer( + "projection_steps", 200, "Number of steps to compute projection after" +) +flags.DEFINE_integer("num_classes", 10, "Total number of classes") +flags.DEFINE_enum( + "verbosity", + "INFO", + ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"], + "Logging verbosity level.", +) +flags.DEFINE_string( + "eig_type", "LZS", "Method to compute eigenvalues (TF, SCIPY, or LZS), LZS" +) +flags.DEFINE_integer( + "lanczos_steps", 20, "Number of steps to perform in Lanczos method." +) +flags.DEFINE_integer("num_rows", 28, "Number of rows in image") +flags.DEFINE_integer("num_columns", 28, "Number of columns in image") +flags.DEFINE_integer("num_channels", 1, "Number of channels in image") + +MIN_LANCZOS_ITER = 5 + + +def main(_): + # pylint: disable=missing-docstring + tf.logging.set_verbosity(FLAGS.verbosity) + + start_time = time.time() + + # Initialize neural network based on config files + input_shape = [FLAGS.num_rows, FLAGS.num_columns, FLAGS.num_channels] + nn_params = nn.load_network_from_checkpoint( + FLAGS.checkpoint, FLAGS.model_json, input_shape + ) + tf.logging.info("Loaded neural network with size of layers: %s", nn_params.sizes) + tf.logging.info( + "Loaded neural network with input shapes: %s", nn_params.input_shapes + ) + tf.logging.info( + "Loaded neural network with output shapes: %s", nn_params.output_shapes + ) + dual_var = utils.initialize_dual( + nn_params, FLAGS.init_dual_file, init_nu=FLAGS.init_nu + ) + + # Reading test input and reshaping + with tf.gfile.Open(FLAGS.test_input) as f: + test_input = np.load(f) + test_input = np.reshape(test_input, [np.size(test_input), 1]) + + if FLAGS.adv_class == -1: + start_class = 0 + end_class = FLAGS.num_classes + else: + start_class = FLAGS.adv_class + end_class = FLAGS.adv_class + 1 + for adv_class in range(start_class, end_class): + tf.logging.info("Running certification for adversarial class %d", adv_class) + if adv_class == FLAGS.true_class: + continue + + optimization_params = { + "init_penalty": FLAGS.init_penalty, + "large_eig_num_steps": FLAGS.large_eig_num_steps, + "small_eig_num_steps": FLAGS.small_eig_num_steps, + "inner_num_steps": FLAGS.inner_num_steps, + "outer_num_steps": FLAGS.outer_num_steps, + "beta": FLAGS.beta, + "smoothness_parameter": FLAGS.smoothness_parameter, + "eig_learning_rate": FLAGS.eig_learning_rate, + "optimizer": FLAGS.optimizer, + "init_learning_rate": FLAGS.init_learning_rate, + "learning_rate_decay": FLAGS.learning_rate_decay, + "momentum_parameter": FLAGS.momentum_parameter, + "print_stats_steps": FLAGS.print_stats_steps, + "stats_folder": FLAGS.stats_folder, + "projection_steps": FLAGS.projection_steps, + "eig_type": FLAGS.eig_type, + "has_conv": nn_params.has_conv, + "lanczos_steps": FLAGS.lanczos_steps, + } + lzs_params = {"min_iter": MIN_LANCZOS_ITER, "max_iter": FLAGS.lanczos_steps} + with tf.Session() as sess: + dual = dual_formulation.DualFormulation( + sess, + dual_var, + nn_params, + test_input, + FLAGS.true_class, + adv_class, + FLAGS.input_minval, + FLAGS.input_maxval, + FLAGS.epsilon, + lzs_params, + ) + optimization_object = optimization.Optimization( + dual, sess, optimization_params + ) + is_cert_found = optimization_object.run_optimization() + if not is_cert_found: + print("Example could not be verified") + exit() + print("Example successfully verified") + print("Elapsed time: " + str(time.time() - start_time)) + + +if __name__ == "__main__": + tf.app.run(main) diff --git a/cleverhans_v3.1.0/cleverhans/experimental/certification/dual_formulation.py b/cleverhans_v3.1.0/cleverhans/experimental/certification/dual_formulation.py new file mode 100644 index 000000000..71ac6236c --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans/experimental/certification/dual_formulation.py @@ -0,0 +1,608 @@ +"""Code with dual formulation for certification problem.""" +# pylint: disable=missing-docstring +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from scipy.sparse.linalg import eigs, LinearOperator +import tensorflow as tf +from tensorflow.contrib import autograph +import numpy as np + +from cleverhans.experimental.certification import utils + +flags = tf.app.flags +FLAGS = flags.FLAGS + +# Tolerance value for eigenvalue computation +TOL = 1e-5 + +# Binary search constants +MAX_BINARY_SEARCH_ITER = 10 +NU_UPDATE_CONSTANT = 1.3 + +# Bound on lowest value of certificate to check for numerical errors +LOWER_CERT_BOUND = -5.0 +DEFAULT_LZS_PARAMS = {"min_iter": 5, "max_iter": 50} + + +class DualFormulation(object): + """DualFormulation is a class that creates the dual objective function + and access to matrix vector products for the matrix that is constrained + to be Positive semidefinite + """ + + def __init__( + self, + sess, + dual_var, + neural_net_param_object, + test_input, + true_class, + adv_class, + input_minval, + input_maxval, + epsilon, + lzs_params=None, + project_dual=True, + ): + """Initializes dual formulation class. + + Args: + sess: Tensorflow session + dual_var: dictionary of dual variables containing a) lambda_pos + b) lambda_neg, c) lambda_quad, d) lambda_lu + neural_net_param_object: NeuralNetParam object created for the network + under consideration + test_input: clean example to certify around + true_class: the class label of the test input + adv_class: the label that the adversary tried to perturb input to + input_minval: minimum value of valid input range + input_maxval: maximum value of valid input range + epsilon: Size of the perturbation (scaled for [0, 1] input) + lzs_params: Parameters for Lanczos algorithm (dictionary) in the form: + { + 'min_iter': 5 + 'max_iter': 50 + } + project_dual: Whether we should create a projected dual object + """ + self.sess = sess + self.nn_params = neural_net_param_object + self.test_input = tf.convert_to_tensor(test_input, dtype=tf.float32) + self.true_class = true_class + self.adv_class = adv_class + self.input_minval = tf.convert_to_tensor(input_minval, dtype=tf.float32) + self.input_maxval = tf.convert_to_tensor(input_maxval, dtype=tf.float32) + self.epsilon = tf.convert_to_tensor(epsilon, dtype=tf.float32) + self.lzs_params = lzs_params or DEFAULT_LZS_PARAMS.copy() + self.final_linear = ( + self.nn_params.final_weights[adv_class, :] + - self.nn_params.final_weights[true_class, :] + ) + self.final_linear = tf.reshape( + self.final_linear, shape=[tf.size(self.final_linear), 1] + ) + self.final_constant = ( + self.nn_params.final_bias[adv_class] - self.nn_params.final_bias[true_class] + ) + self.lanczos_dtype = tf.float64 + self.nn_dtype = tf.float32 + + # Computing lower and upper bounds + # Note that lower and upper are of size nn_params.num_hidden_layers + 1 + self.lower = [] + self.upper = [] + + # Also computing pre activation lower and upper bounds + # to compute always-off and always-on units + self.pre_lower = [] + self.pre_upper = [] + + # Initializing at the input layer with \ell_\infty constraints + self.lower.append(tf.maximum(self.test_input - self.epsilon, self.input_minval)) + self.upper.append(tf.minimum(self.test_input + self.epsilon, self.input_maxval)) + self.pre_lower.append(self.lower[0]) + self.pre_upper.append(self.upper[0]) + + for i in range(0, self.nn_params.num_hidden_layers): + lo_plus_up = self.nn_params.forward_pass(self.lower[i] + self.upper[i], i) + lo_minus_up = self.nn_params.forward_pass( + self.lower[i] - self.upper[i], i, is_abs=True + ) + up_minus_lo = self.nn_params.forward_pass( + self.upper[i] - self.lower[i], i, is_abs=True + ) + current_lower = 0.5 * (lo_plus_up + lo_minus_up) + self.nn_params.biases[i] + current_upper = 0.5 * (lo_plus_up + up_minus_lo) + self.nn_params.biases[i] + self.pre_lower.append(current_lower) + self.pre_upper.append(current_upper) + self.lower.append(tf.nn.relu(current_lower)) + self.upper.append(tf.nn.relu(current_upper)) + + # Run lower and upper because they don't change + self.pre_lower = self.sess.run(self.pre_lower) + self.pre_upper = self.sess.run(self.pre_upper) + self.lower = self.sess.run(self.lower) + self.upper = self.sess.run(self.upper) + + # Using the preactivation lower and upper bounds + # to compute the linear regions + self.positive_indices = [] + self.negative_indices = [] + self.switch_indices = [] + + for i in range(0, self.nn_params.num_hidden_layers + 1): + # Positive index = 1 if the ReLU is always "on" + self.positive_indices.append( + np.asarray(self.pre_lower[i] >= 0, dtype=np.float32) + ) + # Negative index = 1 if the ReLU is always off + self.negative_indices.append( + np.asarray(self.pre_upper[i] <= 0, dtype=np.float32) + ) + # Switch index = 1 if the ReLU could be either on or off + self.switch_indices.append( + np.asarray( + np.multiply(self.pre_lower[i], self.pre_upper[i]) < 0, + dtype=np.float32, + ) + ) + + # Computing the optimization terms + self.lambda_pos = [x for x in dual_var["lambda_pos"]] + self.lambda_neg = [x for x in dual_var["lambda_neg"]] + self.lambda_quad = [x for x in dual_var["lambda_quad"]] + self.lambda_lu = [x for x in dual_var["lambda_lu"]] + self.nu = dual_var["nu"] + self.vector_g = None + self.scalar_f = None + self.matrix_h = None + self.matrix_m = None + self.matrix_m_dimension = 1 + np.sum(self.nn_params.sizes) + + # The primal vector in the SDP can be thought of as [layer_1, layer_2..] + # In this concatenated version, dual_index[i] that marks the start + # of layer_i + # This is useful while computing implicit products with matrix H + self.dual_index = [0] + for i in range(self.nn_params.num_hidden_layers + 1): + self.dual_index.append(self.dual_index[-1] + self.nn_params.sizes[i]) + + # Construct objectives, matrices, and certificate + self.set_differentiable_objective() + if not self.nn_params.has_conv: + self.get_full_psd_matrix() + + # Setup Lanczos functionality for compute certificate + self.construct_lanczos_params() + + # Create projected dual object + if project_dual: + self.projected_dual = self.create_projected_dual() + + def create_projected_dual(self): + """Function to create variables for the projected dual object. + Function that projects the input dual variables onto the feasible set. + Returns: + projected_dual: Feasible dual solution corresponding to current dual + """ + # TODO: consider whether we can use shallow copy of the lists without + # using tf.identity + projected_nu = tf.placeholder(tf.float32, shape=[]) + min_eig_h = tf.placeholder(tf.float32, shape=[]) + projected_lambda_pos = [tf.identity(x) for x in self.lambda_pos] + projected_lambda_neg = [tf.identity(x) for x in self.lambda_neg] + projected_lambda_quad = [tf.identity(x) for x in self.lambda_quad] + projected_lambda_lu = [tf.identity(x) for x in self.lambda_lu] + + for i in range(self.nn_params.num_hidden_layers + 1): + # Making H PSD + projected_lambda_lu[i] = ( + self.lambda_lu[i] + 0.5 * tf.maximum(-min_eig_h, 0) + TOL + ) + # Adjusting the value of \lambda_neg to make change in g small + projected_lambda_neg[i] = self.lambda_neg[i] + tf.multiply( + (self.lower[i] + self.upper[i]), + (self.lambda_lu[i] - projected_lambda_lu[i]), + ) + projected_lambda_neg[i] = tf.multiply( + self.negative_indices[i], projected_lambda_neg[i] + ) + tf.multiply( + self.switch_indices[i], tf.maximum(projected_lambda_neg[i], 0) + ) + + projected_dual_var = { + "lambda_pos": projected_lambda_pos, + "lambda_neg": projected_lambda_neg, + "lambda_lu": projected_lambda_lu, + "lambda_quad": projected_lambda_quad, + "nu": projected_nu, + } + projected_dual_object = DualFormulation( + self.sess, + projected_dual_var, + self.nn_params, + self.test_input, + self.true_class, + self.adv_class, + self.input_minval, + self.input_maxval, + self.epsilon, + self.lzs_params, + project_dual=False, + ) + projected_dual_object.min_eig_val_h = min_eig_h + return projected_dual_object + + def construct_lanczos_params(self): + """Computes matrices T and V using the Lanczos algorithm. + + Args: + k: number of iterations and dimensionality of the tridiagonal matrix + Returns: + eig_vec: eigen vector corresponding to min eigenvalue + """ + # Using autograph to automatically handle + # the control flow of minimum_eigen_vector + self.min_eigen_vec = autograph.to_graph(utils.tf_lanczos_smallest_eigval) + + def _m_vector_prod_fn(x): + return self.get_psd_product(x, dtype=self.lanczos_dtype) + + def _h_vector_prod_fn(x): + return self.get_h_product(x, dtype=self.lanczos_dtype) + + # Construct nodes for computing eigenvalue of M + self.m_min_vec_estimate = np.zeros( + shape=(self.matrix_m_dimension, 1), dtype=np.float64 + ) + zeros_m = tf.zeros(shape=(self.matrix_m_dimension, 1), dtype=tf.float64) + self.m_min_vec_ph = tf.placeholder_with_default( + input=zeros_m, shape=(self.matrix_m_dimension, 1), name="m_min_vec_ph" + ) + self.m_min_eig, self.m_min_vec = self.min_eigen_vec( + _m_vector_prod_fn, + self.matrix_m_dimension, + self.m_min_vec_ph, + self.lzs_params["max_iter"], + dtype=self.lanczos_dtype, + ) + self.m_min_eig = tf.cast(self.m_min_eig, self.nn_dtype) + self.m_min_vec = tf.cast(self.m_min_vec, self.nn_dtype) + + self.h_min_vec_estimate = np.zeros( + shape=(self.matrix_m_dimension - 1, 1), dtype=np.float64 + ) + zeros_h = tf.zeros(shape=(self.matrix_m_dimension - 1, 1), dtype=tf.float64) + self.h_min_vec_ph = tf.placeholder_with_default( + input=zeros_h, shape=(self.matrix_m_dimension - 1, 1), name="h_min_vec_ph" + ) + self.h_min_eig, self.h_min_vec = self.min_eigen_vec( + _h_vector_prod_fn, + self.matrix_m_dimension - 1, + self.h_min_vec_ph, + self.lzs_params["max_iter"], + dtype=self.lanczos_dtype, + ) + self.h_min_eig = tf.cast(self.h_min_eig, self.nn_dtype) + self.h_min_vec = tf.cast(self.h_min_vec, self.nn_dtype) + + def set_differentiable_objective(self): + """Function that constructs minimization objective from dual variables.""" + # Checking if graphs are already created + if self.vector_g is not None: + return + + # Computing the scalar term + bias_sum = 0 + for i in range(0, self.nn_params.num_hidden_layers): + bias_sum = bias_sum + tf.reduce_sum( + tf.multiply(self.nn_params.biases[i], self.lambda_pos[i + 1]) + ) + lu_sum = 0 + for i in range(0, self.nn_params.num_hidden_layers + 1): + lu_sum = lu_sum + tf.reduce_sum( + tf.multiply( + tf.multiply(self.lower[i], self.upper[i]), self.lambda_lu[i] + ) + ) + + self.scalar_f = -bias_sum - lu_sum + self.final_constant + + # Computing the vector term + g_rows = [] + for i in range(0, self.nn_params.num_hidden_layers): + if i > 0: + current_row = ( + self.lambda_neg[i] + + self.lambda_pos[i] + - self.nn_params.forward_pass( + self.lambda_pos[i + 1], i, is_transpose=True + ) + + tf.multiply(self.lower[i] + self.upper[i], self.lambda_lu[i]) + + tf.multiply(self.lambda_quad[i], self.nn_params.biases[i - 1]) + ) + else: + current_row = -self.nn_params.forward_pass( + self.lambda_pos[i + 1], i, is_transpose=True + ) + tf.multiply(self.lower[i] + self.upper[i], self.lambda_lu[i]) + g_rows.append(current_row) + + # Term for final linear term + g_rows.append( + ( + self.lambda_pos[self.nn_params.num_hidden_layers] + + self.lambda_neg[self.nn_params.num_hidden_layers] + + self.final_linear + + tf.multiply( + ( + self.lower[self.nn_params.num_hidden_layers] + + self.upper[self.nn_params.num_hidden_layers] + ), + self.lambda_lu[self.nn_params.num_hidden_layers], + ) + + tf.multiply( + self.lambda_quad[self.nn_params.num_hidden_layers], + self.nn_params.biases[self.nn_params.num_hidden_layers - 1], + ) + ) + ) + self.vector_g = tf.concat(g_rows, axis=0) + self.unconstrained_objective = self.scalar_f + 0.5 * self.nu + + def get_h_product(self, vector, dtype=None): + """Function that provides matrix product interface with PSD matrix. + + Args: + vector: the vector to be multiplied with matrix H + + Returns: + result_product: Matrix product of H and vector + """ + # Computing the product of matrix_h with beta (input vector) + # At first layer, h is simply diagonal + if dtype is None: + dtype = self.nn_dtype + beta = tf.cast(vector, self.nn_dtype) + h_beta_rows = [] + for i in range(self.nn_params.num_hidden_layers): + # Split beta of this block into [gamma, delta] + gamma = beta[self.dual_index[i] : self.dual_index[i + 1]] + delta = beta[self.dual_index[i + 1] : self.dual_index[i + 2]] + + # Expanding the product with diagonal matrices + if i == 0: + h_beta_rows.append( + tf.multiply(2 * self.lambda_lu[i], gamma) + - self.nn_params.forward_pass( + tf.multiply(self.lambda_quad[i + 1], delta), + i, + is_transpose=True, + ) + ) + else: + h_beta_rows[i] = ( + h_beta_rows[i] + + tf.multiply(self.lambda_quad[i] + self.lambda_lu[i], gamma) + - self.nn_params.forward_pass( + tf.multiply(self.lambda_quad[i + 1], delta), + i, + is_transpose=True, + ) + ) + + new_row = tf.multiply( + self.lambda_quad[i + 1] + self.lambda_lu[i + 1], delta + ) - tf.multiply( + self.lambda_quad[i + 1], self.nn_params.forward_pass(gamma, i) + ) + h_beta_rows.append(new_row) + + # Last boundary case + h_beta_rows[self.nn_params.num_hidden_layers] = h_beta_rows[ + self.nn_params.num_hidden_layers + ] + tf.multiply( + ( + self.lambda_quad[self.nn_params.num_hidden_layers] + + self.lambda_lu[self.nn_params.num_hidden_layers] + ), + delta, + ) + + h_beta = tf.concat(h_beta_rows, axis=0) + return tf.cast(h_beta, dtype) + + def get_psd_product(self, vector, dtype=None): + """Function that provides matrix product interface with PSD matrix. + + Args: + vector: the vector to be multiplied with matrix M + + Returns: + result_product: Matrix product of M and vector + """ + # For convenience, think of x as [\alpha, \beta] + if dtype is None: + dtype = self.nn_dtype + vector = tf.cast(vector, self.nn_dtype) + alpha = tf.reshape(vector[0], shape=[1, 1]) + beta = vector[1:] + # Computing the product of matrix_h with beta part of vector + # At first layer, h is simply diagonal + h_beta = self.get_h_product(beta) + + # Constructing final result using vector_g + result = tf.concat( + [ + alpha * self.nu + tf.reduce_sum(tf.multiply(beta, self.vector_g)), + tf.multiply(alpha, self.vector_g) + h_beta, + ], + axis=0, + ) + return tf.cast(result, dtype) + + def get_full_psd_matrix(self): + """Function that returns the tf graph corresponding to the entire matrix M. + + Returns: + matrix_h: unrolled version of tf matrix corresponding to H + matrix_m: unrolled tf matrix corresponding to M + """ + if self.matrix_m is not None: + return self.matrix_h, self.matrix_m + + # Computing the matrix term + h_columns = [] + for i in range(self.nn_params.num_hidden_layers + 1): + current_col_elems = [] + for j in range(i): + current_col_elems.append( + tf.zeros([self.nn_params.sizes[j], self.nn_params.sizes[i]]) + ) + + # For the first layer, there is no relu constraint + if i == 0: + current_col_elems.append(utils.diag(self.lambda_lu[i])) + else: + current_col_elems.append( + utils.diag(self.lambda_lu[i] + self.lambda_quad[i]) + ) + if i < self.nn_params.num_hidden_layers: + current_col_elems.append( + tf.matmul( + utils.diag(-1 * self.lambda_quad[i + 1]), + self.nn_params.weights[i], + ) + ) + for j in range(i + 2, self.nn_params.num_hidden_layers + 1): + current_col_elems.append( + tf.zeros([self.nn_params.sizes[j], self.nn_params.sizes[i]]) + ) + current_column = tf.concat(current_col_elems, 0) + h_columns.append(current_column) + + self.matrix_h = tf.concat(h_columns, 1) + self.matrix_h = self.matrix_h + tf.transpose(self.matrix_h) + + self.matrix_m = tf.concat( + [ + tf.concat( + [tf.reshape(self.nu, (1, 1)), tf.transpose(self.vector_g)], axis=1 + ), + tf.concat([self.vector_g, self.matrix_h], axis=1), + ], + axis=0, + ) + return self.matrix_h, self.matrix_m + + def make_m_psd(self, original_nu, feed_dictionary): + """Run binary search to find a value for nu that makes M PSD + Args: + original_nu: starting value of nu to do binary search on + feed_dictionary: dictionary of updated lambda variables to feed into M + Returns: + new_nu: new value of nu + """ + feed_dict = feed_dictionary.copy() + _, min_eig_val_m = self.get_lanczos_eig(compute_m=True, feed_dict=feed_dict) + + lower_nu = original_nu + upper_nu = original_nu + num_iter = 0 + + # Find an upper bound on nu + while min_eig_val_m - TOL < 0 and num_iter < (MAX_BINARY_SEARCH_ITER / 2): + num_iter += 1 + upper_nu *= NU_UPDATE_CONSTANT + feed_dict.update({self.nu: upper_nu}) + _, min_eig_val_m = self.get_lanczos_eig(compute_m=True, feed_dict=feed_dict) + + final_nu = upper_nu + + # Perform binary search to find best value of nu + while lower_nu <= upper_nu and num_iter < MAX_BINARY_SEARCH_ITER: + num_iter += 1 + mid_nu = (lower_nu + upper_nu) / 2 + feed_dict.update({self.nu: mid_nu}) + _, min_eig_val_m = self.get_lanczos_eig(compute_m=True, feed_dict=feed_dict) + if min_eig_val_m - TOL < 0: + lower_nu = mid_nu + else: + upper_nu = mid_nu + + final_nu = upper_nu + + return final_nu + + def get_lanczos_eig(self, compute_m=True, feed_dict=None): + """Computes the min eigen value and corresponding vector of matrix M or H + using the Lanczos algorithm. + Args: + compute_m: boolean to determine whether we should compute eig val/vec + for M or for H. True for M; False for H. + feed_dict: dictionary mapping from TF placeholders to values (optional) + Returns: + min_eig_vec: Corresponding eigen vector to min eig val + eig_val: Minimum eigen value + """ + if compute_m: + min_eig, min_vec = self.sess.run( + [self.m_min_eig, self.m_min_vec], feed_dict=feed_dict + ) + + else: + min_eig, min_vec = self.sess.run( + [self.h_min_eig, self.h_min_vec], feed_dict=feed_dict + ) + + return min_vec, min_eig + + def compute_certificate(self, current_step, feed_dictionary): + """Function to compute the certificate based either current value + or dual variables loaded from dual folder""" + feed_dict = feed_dictionary.copy() + nu = feed_dict[self.nu] + second_term = self.make_m_psd(nu, feed_dict) + tf.logging.info("Nu after modifying: " + str(second_term)) + feed_dict.update({self.nu: second_term}) + computed_certificate = self.sess.run( + self.unconstrained_objective, feed_dict=feed_dict + ) + + tf.logging.info( + "Inner step: %d, current value of certificate: %f", + current_step, + computed_certificate, + ) + + # Sometimes due to either overflow or instability in inverses, + # the returned certificate is large and negative -- keeping a check + if LOWER_CERT_BOUND < computed_certificate < 0: + _, min_eig_val_m = self.get_lanczos_eig(feed_dict=feed_dict) + tf.logging.info("min eig val from lanczos: " + str(min_eig_val_m)) + input_vector_m = tf.placeholder( + tf.float32, shape=(self.matrix_m_dimension, 1) + ) + output_vector_m = self.get_psd_product(input_vector_m) + + def np_vector_prod_fn_m(np_vector): + np_vector = np.reshape(np_vector, [-1, 1]) + feed_dict.update({input_vector_m: np_vector}) + output_np_vector = self.sess.run(output_vector_m, feed_dict=feed_dict) + return output_np_vector + + linear_operator_m = LinearOperator( + (self.matrix_m_dimension, self.matrix_m_dimension), + matvec=np_vector_prod_fn_m, + ) + # Performing shift invert scipy operation when eig val estimate is available + min_eig_val_m_scipy, _ = eigs(linear_operator_m, k=1, which="SR", tol=TOL) + + tf.logging.info("min eig val m from scipy: " + str(min_eig_val_m_scipy)) + + if min_eig_val_m - TOL > 0: + tf.logging.info("Found certificate of robustness!") + return True + + return False diff --git a/cleverhans_v3.1.0/cleverhans/experimental/certification/nn.py b/cleverhans_v3.1.0/cleverhans/experimental/certification/nn.py new file mode 100644 index 000000000..585a15773 --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans/experimental/certification/nn.py @@ -0,0 +1,269 @@ +"""This file defines the neural network class, where a network is reinitialized from configuration files. + +The class also has a forward propagation method. +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import json +import numpy as np +import tensorflow as tf + + +class NeuralNetwork(object): + """NeuralNetwork is a class that interfaces the verification code with + the neural net parameters (weights). + """ + + def __init__( + self, + net_weights, + net_biases, + net_layer_types, + input_shape=None, + cnn_params=None, + ): + """Function to initialize NeuralNetParams class. + + Args: + net_weights: list of numpy matrices of weights of each layer + [convention: x[i+1] = W[i] x[i] + net_biases: list of numpy arrays of biases of each layer + net_layer_types: type of each layer ['ff' or 'ff_relu' or 'ff_conv' or + 'ff_conv_relu'] + + 'ff': Simple feedforward layer with no activations + 'ff_relu': Simple feedforward layer with ReLU activations + 'ff_conv': Convolution layer with no activation + 'ff_conv_relu': Convolution layer with ReLU activation + input_shape: [num_rows, num_columns, num_channels] at the input layer + cnn_params: list of dictionaries containing stride and padding for + each layer + + Raises: + ValueError: the input lists of net params are not of the same length + """ + if (len(net_weights) != len(net_biases)) or len(net_biases) != len( + net_layer_types + ): + raise ValueError("Inputs of net params are not of same length ....") + if net_layer_types[len(net_layer_types) - 1] != "ff": + raise ValueError("Final layer is not linear") + self.num_hidden_layers = len(net_weights) - 1 + self.weights = [] + self.biases = [] + self.layer_types = [] + self.sizes = [] + self.input_shapes = [] + self.output_shapes = [] + self.has_conv = False + if input_shape is not None: + current_num_rows = input_shape[0] + current_num_columns = input_shape[1] + current_num_channels = input_shape[2] + self.cnn_params = cnn_params + + # Setting the sizes of the layers of the network + # sizes[i] contains the size of x_i + for i in range(self.num_hidden_layers): + shape = np.shape(net_weights[i]) + self.weights.append(tf.convert_to_tensor(net_weights[i], dtype=tf.float32)) + self.layer_types.append(net_layer_types[i]) + + if self.layer_types[i] in {"ff", "ff_relu"}: + self.sizes.append(int(shape[1])) + # For feedforward networks, no unraveling the bias terms + + small_bias = tf.convert_to_tensor(net_biases[i], dtype=tf.float32) + self.biases.append(tf.reshape(small_bias, [-1, 1])) + # Assumes that x^{i+1} = W_i x^i + self.input_shapes.append([int(shape[1]), 1]) + self.output_shapes.append([int(shape[0]), 1]) + + # Convolution type + else: + self.has_conv = True + num_filters = shape[3] + self.input_shapes.append( + [1, current_num_rows, current_num_columns, current_num_channels] + ) + self.sizes.append( + current_num_rows * current_num_columns * current_num_channels + ) + current_num_channels = num_filters + # For propagating across multiple conv layers + if self.cnn_params[i]["padding"] == "SAME": + current_num_rows = int( + current_num_rows / self.cnn_params[i]["stride"] + ) + current_num_columns = int( + current_num_columns / self.cnn_params[i]["stride"] + ) + self.output_shapes.append( + [1, current_num_rows, current_num_columns, current_num_channels] + ) + + # For conv networks, unraveling the bias terms + small_bias = tf.convert_to_tensor(net_biases[i], dtype=tf.float32) + large_bias = tf.tile( + tf.reshape(small_bias, [-1, 1]), + [current_num_rows * current_num_columns, 1], + ) + self.biases.append(large_bias) + + # Last layer shape: always ff + if self.has_conv: + final_dim = int(np.shape(net_weights[self.num_hidden_layers])[1]) + self.input_shapes.append([final_dim, 1]) + + else: + final_dim = int(np.shape(net_weights[self.num_hidden_layers - 1])[0]) + + self.sizes.append(final_dim) + self.final_weights = tf.convert_to_tensor( + net_weights[self.num_hidden_layers], dtype=tf.float32 + ) + self.final_bias = tf.convert_to_tensor( + net_biases[self.num_hidden_layers], dtype=tf.float32 + ) + + def forward_pass(self, vector, layer_index, is_transpose=False, is_abs=False): + """Performs forward pass through the layer weights at layer_index. + + Args: + vector: vector that has to be passed through in forward pass + layer_index: index of the layer + is_transpose: whether the weights of the layer have to be transposed + is_abs: whether to take the absolute value of the weights + + Returns: + tensor that corresponds to the forward pass through the layer + Raises: + ValueError: if the layer_index is negative or more than num hidden layers + """ + if layer_index < 0 or layer_index > self.num_hidden_layers: + raise ValueError("Invalid layer index") + + layer_type = self.layer_types[layer_index] + weight = self.weights[layer_index] + if is_abs: + weight = tf.abs(weight) + if is_transpose: + vector = tf.reshape(vector, self.output_shapes[layer_index]) + else: + vector = tf.reshape(vector, self.input_shapes[layer_index]) + + if layer_type in {"ff", "ff_relu"}: + if is_transpose: + weight = tf.transpose(weight) + return_vector = tf.matmul(weight, vector) + elif layer_type in {"conv", "conv_relu"}: + if is_transpose: + return_vector = tf.nn.conv2d_transpose( + vector, + weight, + output_shape=self.input_shapes[layer_index], + strides=[ + 1, + self.cnn_params[layer_index]["stride"], + self.cnn_params[layer_index]["stride"], + 1, + ], + padding=self.cnn_params[layer_index]["padding"], + ) + else: + return_vector = tf.nn.conv2d( + vector, + weight, + strides=[ + 1, + self.cnn_params[layer_index]["stride"], + self.cnn_params[layer_index]["stride"], + 1, + ], + padding=self.cnn_params[layer_index]["padding"], + ) + else: + raise NotImplementedError("Unsupported layer type: {0}".format(layer_type)) + if is_transpose: + return tf.reshape(return_vector, (self.sizes[layer_index], 1)) + return tf.reshape(return_vector, (self.sizes[layer_index + 1], 1)) + + +def load_network_from_checkpoint(checkpoint, model_json, input_shape=None): + """Function to read the weights from checkpoint based on json description. + + Args: + checkpoint: tensorflow checkpoint with trained model to + verify + model_json: path of json file with model description of + the network list of dictionary items for each layer + containing 'type', 'weight_var', 'bias_var' and + 'is_transpose' 'type'is one of {'ff', 'ff_relu' or + 'conv'}; 'weight_var' is the name of tf variable for + weights of layer i; 'bias_var' is the name of tf + variable for bias of layer i; 'is_transpose' is set to + True if the weights have to be transposed as per + convention Note that last layer is always feedforward + net_weights: list of numpy matrices of weights of each layer + convention: x[i+1] = W[i] x[i] + net_biases: list of numpy arrays of biases of each layer + net_layer_types: type of each layer ['ff' or 'ff_relu' or 'ff_conv' + or 'ff_conv_relu'] + 'ff': Simple feedforward layer with no activations + 'ff_relu': Simple feedforward layer with ReLU activations + 'ff_conv': Convolution layer with no activation + 'ff_conv_relu': Convolution layer with ReLU activation + + Raises: + ValueError: If layer_types are invalid or variable names + not found in checkpoint + """ + # Load checkpoint + reader = tf.train.load_checkpoint(checkpoint) + variable_map = reader.get_variable_to_shape_map() + checkpoint_variable_names = variable_map.keys() + # Parse JSON file for names + with tf.gfile.Open(model_json) as f: + list_model_var = json.load(f) + + net_layer_types = [] + net_weights = [] + net_biases = [] + cnn_params = [] + + # Checking validity of the input and adding to list + for layer_model_var in list_model_var: + if layer_model_var["type"] not in {"ff", "ff_relu", "conv"}: + raise ValueError("Invalid layer type in description") + if ( + layer_model_var["weight_var"] not in checkpoint_variable_names + or layer_model_var["bias_var"] not in checkpoint_variable_names + ): + raise ValueError("Variable names not found in checkpoint") + net_layer_types.append(layer_model_var["type"]) + layer_weight = reader.get_tensor(layer_model_var["weight_var"]) + layer_bias = reader.get_tensor(layer_model_var["bias_var"]) + # TODO(aditirag): is there a way to automatically check when to transpose + # We want weights W such that x^{i+1} = W^i x^i + b^i + # Can think of a hack involving matching shapes but if shapes are equal + # it can be ambiguous + if layer_model_var["type"] in {"ff", "ff_relu"}: + layer_weight = np.transpose(layer_weight) + cnn_params.append(None) + if layer_model_var["type"] in {"conv"}: + if "stride" not in layer_model_var or "padding" not in layer_model_var: + raise ValueError("Please define stride and padding for conv layers.") + cnn_params.append( + { + "stride": layer_model_var["stride"], + "padding": layer_model_var["padding"], + } + ) + net_weights.append(layer_weight) + net_biases.append(np.reshape(layer_bias, (np.size(layer_bias), 1))) + return NeuralNetwork( + net_weights, net_biases, net_layer_types, input_shape, cnn_params + ) diff --git a/cleverhans_v3.1.0/cleverhans/experimental/certification/optimization.py b/cleverhans_v3.1.0/cleverhans/experimental/certification/optimization.py new file mode 100644 index 000000000..edc9b87f7 --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans/experimental/certification/optimization.py @@ -0,0 +1,424 @@ +"""Code for setting up the optimization problem for certification.""" +# pylint: disable=missing-docstring +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import json +import os +import numpy as np + +from scipy.sparse.linalg import eigs, LinearOperator +import tensorflow as tf +from tensorflow.contrib import autograph +from cleverhans.experimental.certification import utils + +UPDATE_PARAM_CONSTANT = -0.1 +# Tolerance value for eigenvalue computation +TOL = 1e-5 + + +class Optimization(object): + """Class that sets up and runs the optimization of dual_formulation""" + + def __init__(self, dual_formulation_object, sess, optimization_params): + """Initialize the class variables. + + Args: + dual_formulation_object: Instance of DualFormulation that contains the + dual variables and objective + sess: tf session to be used to run + optimization_params: Dictionary with the following + eig_num_iter - Number of iterations to run for computing minimum eigen + value + eig_learning_rate - Learning rate for minimum eigen value iterations + init_smooth - Starting value of the smoothness parameter (typically + around 0.001) + smooth_decay - The factor by which to decay after every outer loop epoch + optimizer - one of gd, adam, momentum or adagrad + eig_type - The method to compute eigenvalues (TF or SCIPY) + """ + self.sess = sess + self.dual_object = dual_formulation_object + self.params = optimization_params + self.penalty_placeholder = tf.placeholder(tf.float32, shape=[]) + + # The dimensionality of matrix M is the sum of sizes of all layers + 1 + # The + 1 comes due to a row and column of M representing the linear terms + self.eig_init_vec_placeholder = tf.placeholder( + tf.float32, shape=[1 + self.dual_object.dual_index[-1], 1] + ) + self.smooth_placeholder = tf.placeholder(tf.float32, shape=[]) + self.eig_num_iter_placeholder = tf.placeholder(tf.int32, shape=[]) + self.current_eig_val_estimate = None + + # Create graph for optimization + self.prepare_for_optimization() + + def tf_min_eig_vec(self): + """Function for min eigen vector using tf's full eigen decomposition.""" + # Full eigen decomposition requires the explicit psd matrix M + _, matrix_m = self.dual_object.get_full_psd_matrix() + [eig_vals, eig_vectors] = tf.self_adjoint_eig(matrix_m) + index = tf.argmin(eig_vals) + return tf.reshape(eig_vectors[:, index], shape=[eig_vectors.shape[0].value, 1]) + + def tf_smooth_eig_vec(self): + """Function that returns smoothed version of min eigen vector.""" + _, matrix_m = self.dual_object.get_full_psd_matrix() + # Easier to think in terms of max so negating the matrix + [eig_vals, eig_vectors] = tf.self_adjoint_eig(-matrix_m) + exp_eig_vals = tf.exp(tf.divide(eig_vals, self.smooth_placeholder)) + scaling_factor = tf.reduce_sum(exp_eig_vals) + # Multiplying each eig vector by exponential of corresponding eig value + # Scaling factor normalizes the vector to be unit norm + eig_vec_smooth = tf.divide( + tf.matmul(eig_vectors, tf.diag(tf.sqrt(exp_eig_vals))), + tf.sqrt(scaling_factor), + ) + return tf.reshape( + tf.reduce_sum(eig_vec_smooth, axis=1), + shape=[eig_vec_smooth.shape[0].value, 1], + ) + + def get_min_eig_vec_proxy(self, use_tf_eig=False): + """Computes the min eigen value and corresponding vector of matrix M. + + Args: + use_tf_eig: Whether to use tf's default full eigen decomposition + Returns: + eig_vec: Minimum absolute eigen value + eig_val: Corresponding eigen vector + """ + if use_tf_eig: + # If smoothness parameter is too small, essentially no smoothing + # Just output the eigen vector corresponding to min + return tf.cond( + self.smooth_placeholder < 1e-8, + self.tf_min_eig_vec, + self.tf_smooth_eig_vec, + ) + + # Using autograph to automatically handle + # the control flow of minimum_eigen_vector + min_eigen_tf = autograph.to_graph(utils.minimum_eigen_vector) + + def _vector_prod_fn(x): + return self.dual_object.get_psd_product(x) + + estimated_eigen_vector = min_eigen_tf( + x=self.eig_init_vec_placeholder, + num_steps=self.eig_num_iter_placeholder, + learning_rate=self.params["eig_learning_rate"], + vector_prod_fn=_vector_prod_fn, + ) + return estimated_eigen_vector + + def get_scipy_eig_vec(self): + """Computes scipy estimate of min eigenvalue for matrix M. + + Returns: + eig_vec: Minimum absolute eigen value + eig_val: Corresponding eigen vector + """ + if not self.params["has_conv"]: + matrix_m = self.sess.run(self.dual_object.matrix_m) + min_eig_vec_val, estimated_eigen_vector = eigs( + matrix_m, k=1, which="SR", tol=1e-4 + ) + min_eig_vec_val = np.reshape(np.real(min_eig_vec_val), [1, 1]) + return np.reshape(estimated_eigen_vector, [-1, 1]), min_eig_vec_val + else: + dim = self.dual_object.matrix_m_dimension + input_vector = tf.placeholder(tf.float32, shape=(dim, 1)) + output_vector = self.dual_object.get_psd_product(input_vector) + + def np_vector_prod_fn(np_vector): + np_vector = np.reshape(np_vector, [-1, 1]) + output_np_vector = self.sess.run( + output_vector, feed_dict={input_vector: np_vector} + ) + return output_np_vector + + linear_operator = LinearOperator((dim, dim), matvec=np_vector_prod_fn) + # Performing shift invert scipy operation when eig val estimate is available + min_eig_vec_val, estimated_eigen_vector = eigs( + linear_operator, k=1, which="SR", tol=1e-4 + ) + min_eig_vec_val = np.reshape(np.real(min_eig_vec_val), [1, 1]) + return np.reshape(estimated_eigen_vector, [-1, 1]), min_eig_vec_val + + def prepare_for_optimization(self): + """Create tensorflow op for running one step of descent.""" + if self.params["eig_type"] == "TF": + self.eig_vec_estimate = self.get_min_eig_vec_proxy() + elif self.params["eig_type"] == "LZS": + self.eig_vec_estimate = self.dual_object.m_min_vec + else: + self.eig_vec_estimate = tf.placeholder( + tf.float32, shape=(self.dual_object.matrix_m_dimension, 1) + ) + self.stopped_eig_vec_estimate = tf.stop_gradient(self.eig_vec_estimate) + # Eig value is v^\top M v, where v is eigen vector + self.eig_val_estimate = tf.matmul( + tf.transpose(self.stopped_eig_vec_estimate), + self.dual_object.get_psd_product(self.stopped_eig_vec_estimate), + ) + # Penalizing negative of min eigen value because we want min eig value + # to be positive + self.total_objective = ( + self.dual_object.unconstrained_objective + + 0.5 + * tf.square( + tf.maximum(-self.penalty_placeholder * self.eig_val_estimate, 0) + ) + ) + global_step = tf.Variable(0, trainable=False) + # Set up learning rate as a placeholder + self.learning_rate = tf.placeholder(tf.float32, shape=[]) + + # Set up the optimizer + if self.params["optimizer"] == "adam": + self.optimizer = tf.train.AdamOptimizer(learning_rate=self.learning_rate) + elif self.params["optimizer"] == "adagrad": + self.optimizer = tf.train.AdagradOptimizer(learning_rate=self.learning_rate) + elif self.params["optimizer"] == "momentum": + self.optimizer = tf.train.MomentumOptimizer( + learning_rate=self.learning_rate, + momentum=self.params["momentum_parameter"], + use_nesterov=True, + ) + else: + self.optimizer = tf.train.GradientDescentOptimizer( + learning_rate=self.learning_rate + ) + + # Write out the projection step + self.train_step = self.optimizer.minimize( + self.total_objective, global_step=global_step + ) + + self.sess.run(tf.global_variables_initializer()) + + # Projecting the dual variables + proj_ops = [] + for i in range(self.dual_object.nn_params.num_hidden_layers + 1): + # Lambda_pos is non negative for switch indices, + # Unconstrained for positive indices + # Zero for negative indices + proj_ops.append( + self.dual_object.lambda_pos[i].assign( + tf.multiply( + self.dual_object.positive_indices[i], + self.dual_object.lambda_pos[i], + ) + + tf.multiply( + self.dual_object.switch_indices[i], + tf.nn.relu(self.dual_object.lambda_pos[i]), + ) + ) + ) + proj_ops.append( + self.dual_object.lambda_neg[i].assign( + tf.multiply( + self.dual_object.negative_indices[i], + self.dual_object.lambda_neg[i], + ) + + tf.multiply( + self.dual_object.switch_indices[i], + tf.nn.relu(self.dual_object.lambda_neg[i]), + ) + ) + ) + # Lambda_quad is only non zero and positive for switch + proj_ops.append( + self.dual_object.lambda_quad[i].assign( + tf.multiply( + self.dual_object.switch_indices[i], + tf.nn.relu(self.dual_object.lambda_quad[i]), + ) + ) + ) + # Lambda_lu is always non negative + proj_ops.append( + self.dual_object.lambda_lu[i].assign( + tf.nn.relu(self.dual_object.lambda_lu[i]) + ) + ) + + self.proj_step = tf.group(proj_ops) + + # Create folder for saving stats if the folder is not None + if self.params.get("stats_folder") and not tf.gfile.IsDirectory( + self.params["stats_folder"] + ): + tf.gfile.MkDir(self.params["stats_folder"]) + + def run_one_step( + self, + eig_init_vec_val, + eig_num_iter_val, + smooth_val, + penalty_val, + learning_rate_val, + ): + """Run one step of gradient descent for optimization. + + Args: + eig_init_vec_val: Start value for eigen value computations + eig_num_iter_val: Number of iterations to run for eigen computations + smooth_val: Value of smoothness parameter + penalty_val: Value of penalty for the current step + learning_rate_val: Value of learning rate + Returns: + found_cert: True is negative certificate is found, False otherwise + """ + # Running step + step_feed_dict = { + self.eig_init_vec_placeholder: eig_init_vec_val, + self.eig_num_iter_placeholder: eig_num_iter_val, + self.smooth_placeholder: smooth_val, + self.penalty_placeholder: penalty_val, + self.learning_rate: learning_rate_val, + } + + if self.params["eig_type"] == "SCIPY": + current_eig_vector, self.current_eig_val_estimate = self.get_scipy_eig_vec() + step_feed_dict.update({self.eig_vec_estimate: current_eig_vector}) + elif self.params["eig_type"] == "LZS": + step_feed_dict.update( + {self.dual_object.m_min_vec_ph: self.dual_object.m_min_vec_estimate} + ) + + self.sess.run(self.train_step, feed_dict=step_feed_dict) + + [ + _, + self.dual_object.m_min_vec_estimate, + self.current_eig_val_estimate, + ] = self.sess.run( + [self.proj_step, self.eig_vec_estimate, self.eig_val_estimate], + feed_dict=step_feed_dict, + ) + + if self.current_step % self.params["print_stats_steps"] == 0: + [ + self.current_total_objective, + self.current_unconstrained_objective, + self.dual_object.m_min_vec_estimate, + self.current_eig_val_estimate, + self.current_nu, + ] = self.sess.run( + [ + self.total_objective, + self.dual_object.unconstrained_objective, + self.eig_vec_estimate, + self.eig_val_estimate, + self.dual_object.nu, + ], + feed_dict=step_feed_dict, + ) + + stats = { + "total_objective": float(self.current_total_objective), + "unconstrained_objective": float(self.current_unconstrained_objective), + "min_eig_val_estimate": float(self.current_eig_val_estimate), + } + tf.logging.info( + "Current inner step: %d, optimization stats: %s", + self.current_step, + stats, + ) + if self.params["stats_folder"] is not None: + stats = json.dumps(stats) + filename = os.path.join( + self.params["stats_folder"], str(self.current_step) + ".json" + ) + with tf.gfile.Open(filename) as file_f: + file_f.write(stats) + + # Project onto feasible set of dual variables + if ( + self.current_step % self.params["projection_steps"] == 0 + and self.current_unconstrained_objective < 0 + ): + nu = self.sess.run(self.dual_object.nu) + dual_feed_dict = { + self.dual_object.h_min_vec_ph: self.dual_object.h_min_vec_estimate + } + _, min_eig_val_h_lz = self.dual_object.get_lanczos_eig( + compute_m=False, feed_dict=dual_feed_dict + ) + projected_dual_feed_dict = { + self.dual_object.projected_dual.nu: nu, + self.dual_object.projected_dual.min_eig_val_h: min_eig_val_h_lz, + } + if self.dual_object.projected_dual.compute_certificate( + self.current_step, projected_dual_feed_dict + ): + return True + + return False + + def run_optimization(self): + """Run the optimization, call run_one_step with suitable placeholders. + + Returns: + True if certificate is found + False otherwise + """ + penalty_val = self.params["init_penalty"] + # Don't use smoothing initially - very inaccurate for large dimension + self.smooth_on = False + smooth_val = 0 + learning_rate_val = self.params["init_learning_rate"] + self.current_outer_step = 1 + + while self.current_outer_step <= self.params["outer_num_steps"]: + tf.logging.info( + "Running outer step %d with penalty %f", + self.current_outer_step, + penalty_val, + ) + # Running inner loop of optimization with current_smooth_val, + # current_penalty as smoothness parameters and penalty respectively + self.current_step = 0 + # Run first step with random eig initialization and large number of steps + found_cert = self.run_one_step( + self.dual_object.m_min_vec_estimate, + self.params["large_eig_num_steps"], + smooth_val, + penalty_val, + learning_rate_val, + ) + if found_cert: + return True + while self.current_step < self.params["inner_num_steps"]: + self.current_step = self.current_step + 1 + found_cert = self.run_one_step( + self.dual_object.m_min_vec_estimate, + self.params["small_eig_num_steps"], + smooth_val, + penalty_val, + learning_rate_val, + ) + if found_cert: + return True + # Update penalty only if it looks like current objective is optimizes + if self.current_total_objective < UPDATE_PARAM_CONSTANT: + penalty_val = penalty_val * self.params["beta"] + learning_rate_val = ( + learning_rate_val * self.params["learning_rate_decay"] + ) + else: + # To get more accurate gradient estimate + self.params["small_eig_num_steps"] = ( + 1.5 * self.params["small_eig_num_steps"] + ) + + # If eigen values seem small enough, turn on smoothing + # useful only when performing full eigen decomposition + if np.abs(self.current_eig_val_estimate) < 0.01: + smooth_val = self.params["smoothness_parameter"] + self.current_outer_step = self.current_outer_step + 1 + return False diff --git a/cleverhans_v3.1.0/cleverhans/experimental/certification/tests/dual_formulation_test.py b/cleverhans_v3.1.0/cleverhans/experimental/certification/tests/dual_formulation_test.py new file mode 100644 index 000000000..fcaed1a5b --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans/experimental/certification/tests/dual_formulation_test.py @@ -0,0 +1,220 @@ +"""Tests for cleverhans.experimental.certification.dual_formulation.""" +# pylint: disable=missing-docstring + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import unittest +import numpy as np +import tensorflow as tf + +from cleverhans.experimental.certification import dual_formulation +from cleverhans.experimental.certification import nn + + +class DualFormulationTest(unittest.TestCase): + def test_init(self): + # Function to test initialization of dual formulation class. + net_weights = [[[2, 2], [3, 3], [4, 4]], [[1, 1, 1], [-1, -1, -1]]] + net_biases = [ + np.transpose(np.matrix([0, 0, 0])), + np.transpose(np.matrix([0, 0])), + ] + net_layer_types = ["ff_relu", "ff"] + nn_params1 = nn.NeuralNetwork(net_weights, net_biases, net_layer_types) + + test_input = np.transpose(np.matrix([0, 0])) + true_class = 0 + adv_class = 1 + input_minval = 0 + input_maxval = 0 + epsilon = 0.1 + three_dim_tensor = tf.random_uniform(shape=(3, 1), dtype=tf.float32) + two_dim_tensor = tf.random_uniform(shape=(2, 1), dtype=tf.float32) + scalar = tf.random_uniform(shape=(1, 1), dtype=tf.float32) + lambda_pos = [two_dim_tensor, three_dim_tensor] + lambda_neg = lambda_pos + lambda_quad = lambda_pos + lambda_lu = lambda_pos + nu = scalar + dual_var = { + "lambda_pos": lambda_pos, + "lambda_neg": lambda_neg, + "lambda_quad": lambda_quad, + "lambda_lu": lambda_lu, + "nu": nu, + } + with tf.Session() as sess: + dual_formulation_object = dual_formulation.DualFormulation( + sess, + dual_var, + nn_params1, + test_input, + true_class, + adv_class, + input_minval, + input_maxval, + epsilon, + ) + self.assertIsNotNone(dual_formulation_object) + + def test_set_differentiable_objective(self): + # Function to test the function that sets the differentiable objective. + net_weights = [[[2, 2], [3, 3], [4, 4]], [[1, 1, 1], [-1, -1, -1]]] + net_biases = [ + np.transpose(np.matrix([0, 0, 0])), + np.transpose(np.matrix([0, 0])), + ] + net_layer_types = ["ff_relu", "ff"] + nn_params1 = nn.NeuralNetwork(net_weights, net_biases, net_layer_types) + + test_input = np.transpose(np.matrix([0, 0])) + true_class = 0 + adv_class = 1 + input_minval = 0 + input_maxval = 0 + epsilon = 0.1 + three_dim_tensor = tf.random_uniform(shape=(3, 1), dtype=tf.float32) + two_dim_tensor = tf.random_uniform(shape=(2, 1), dtype=tf.float32) + scalar = tf.random_uniform(shape=(1, 1), dtype=tf.float32) + lambda_pos = [two_dim_tensor, three_dim_tensor] + lambda_neg = lambda_pos + lambda_quad = lambda_pos + lambda_lu = lambda_pos + nu = scalar + dual_var = { + "lambda_pos": lambda_pos, + "lambda_neg": lambda_neg, + "lambda_quad": lambda_quad, + "lambda_lu": lambda_lu, + "nu": nu, + } + with tf.Session() as sess: + dual_formulation_object = dual_formulation.DualFormulation( + sess, + dual_var, + nn_params1, + test_input, + true_class, + adv_class, + input_minval, + input_maxval, + epsilon, + ) + dual_formulation_object.set_differentiable_objective() + self.assertEqual(dual_formulation_object.scalar_f.shape.as_list(), [1]) + self.assertEqual( + dual_formulation_object.unconstrained_objective.shape.as_list(), [1, 1] + ) + self.assertEqual(dual_formulation_object.vector_g.shape.as_list(), [5, 1]) + + def test_get_full_psd_matrix(self): + # Function to test product with PSD matrix. + net_weights = [[[2, 2], [3, 3], [4, 4]], [[1, 1, 1], [-1, -1, -1]]] + net_biases = [ + np.transpose(np.matrix([0, 0, 0])), + np.transpose(np.matrix([0, 0])), + ] + net_layer_types = ["ff_relu", "ff"] + nn_params1 = nn.NeuralNetwork(net_weights, net_biases, net_layer_types) + + test_input = np.transpose(np.matrix([0, 0])) + true_class = 0 + adv_class = 1 + input_minval = 0 + input_maxval = 0 + epsilon = 0.1 + three_dim_tensor = tf.random_uniform(shape=(3, 1), dtype=tf.float32) + two_dim_tensor = tf.random_uniform(shape=(2, 1), dtype=tf.float32) + scalar = tf.random_uniform(shape=(1, 1), dtype=tf.float32) + lambda_pos = [two_dim_tensor, three_dim_tensor] + lambda_neg = lambda_pos + lambda_quad = lambda_pos + lambda_lu = lambda_pos + nu = scalar + dual_var = { + "lambda_pos": lambda_pos, + "lambda_neg": lambda_neg, + "lambda_quad": lambda_quad, + "lambda_lu": lambda_lu, + "nu": nu, + } + with tf.Session() as sess: + dual_formulation_object = dual_formulation.DualFormulation( + sess, + dual_var, + nn_params1, + test_input, + true_class, + adv_class, + input_minval, + input_maxval, + epsilon, + ) + matrix_h, matrix_m = dual_formulation_object.get_full_psd_matrix() + self.assertEqual(matrix_h.shape.as_list(), [5, 5]) + self.assertEqual(matrix_m.shape.as_list(), [6, 6]) + + def test_get_psd_product(self): + # Function to test implicit product with PSD matrix. + net_weights = [[[2, 2], [3, 3], [4, 4]], [[1, 1, 1], [-1, -1, -1]]] + net_biases = [ + np.transpose(np.matrix([0, 0, 0])), + np.transpose(np.matrix([0, 0])), + ] + net_layer_types = ["ff_relu", "ff"] + nn_params1 = nn.NeuralNetwork(net_weights, net_biases, net_layer_types) + + test_input = np.transpose(np.matrix([0, 0])) + true_class = 0 + adv_class = 1 + input_minval = 0 + input_maxval = 0 + epsilon = 0.1 + three_dim_tensor = tf.random_uniform(shape=(3, 1), dtype=tf.float32) + two_dim_tensor = tf.random_uniform(shape=(2, 1), dtype=tf.float32) + scalar = tf.random_uniform(shape=(1, 1), dtype=tf.float32) + lambda_pos = [two_dim_tensor, three_dim_tensor] + lambda_neg = lambda_pos + lambda_quad = lambda_pos + lambda_lu = lambda_pos + nu = scalar + dual_var = { + "lambda_pos": lambda_pos, + "lambda_neg": lambda_neg, + "lambda_quad": lambda_quad, + "lambda_lu": lambda_lu, + "nu": nu, + } + with tf.Session() as sess: + dual_formulation_object = dual_formulation.DualFormulation( + sess, + dual_var, + nn_params1, + test_input, + true_class, + adv_class, + input_minval, + input_maxval, + epsilon, + ) + _, matrix_m = dual_formulation_object.get_full_psd_matrix() + + # Testing if the values match + six_dim_tensor = tf.random_uniform(shape=(6, 1), dtype=tf.float32) + implicit_product = dual_formulation_object.get_psd_product(six_dim_tensor) + explicit_product = tf.matmul(matrix_m, six_dim_tensor) + [implicit_product_value, explicit_product_value] = sess.run( + [implicit_product, explicit_product] + ) + self.assertEqual( + np.shape(implicit_product_value), np.shape(explicit_product_value) + ) + self.assertLess( + np.max(np.abs(implicit_product_value - explicit_product_value)), 1e-5 + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/cleverhans_v3.1.0/cleverhans/experimental/certification/tests/nn_test.py b/cleverhans_v3.1.0/cleverhans/experimental/certification/tests/nn_test.py new file mode 100644 index 000000000..6d4f5c746 --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans/experimental/certification/tests/nn_test.py @@ -0,0 +1,61 @@ +"""Tests for cleverhans.experimental.certification.nn.""" +# pylint: disable=missing-docstring + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import unittest +import numpy as np +import tensorflow as tf + +from cleverhans.experimental.certification import nn + + +class NeuralNetworkTest(unittest.TestCase): + def test_init(self): + # Function to test initialization of NeuralNetParams object. + # Valid params + net_weights = [[[2, 2], [3, 3], [4, 4]], [1, 1, 1]] + net_biases = [ + np.transpose(np.matrix([0, 0, 0])), + np.transpose(np.matrix([0, 0])), + ] + net_layer_types = ["ff_relu", "ff"] + nn_params1 = nn.NeuralNetwork(net_weights, net_biases, net_layer_types) + + self.assertIsNotNone(nn_params1) + # Invalid params : list length + net_biases = [0] + with self.assertRaises(ValueError): + nn.NeuralNetwork(net_weights, net_biases, net_layer_types) + + # Invalid params: layer types + with self.assertRaises(ValueError): + net_layer_types = ["ff_relu", "ff_relu"] + nn.NeuralNetwork(net_weights, net_biases, net_layer_types) + + def test_forward_pass(self): + # Function to test forward pass of nn_params. + net_weights = [[[2, 2], [3, 3], [4, 4]], [1, 1, 1]] + net_biases = [ + np.transpose(np.matrix([0, 0, 0])), + np.transpose(np.matrix([0, 0])), + ] + net_layer_types = ["ff_relu", "ff"] + nn_params = nn.NeuralNetwork(net_weights, net_biases, net_layer_types) + + input_vector = tf.random_uniform(shape=(2, 1), dtype=tf.float32) + output_vector = nn_params.forward_pass(input_vector, 0) + self.assertEqual(output_vector.shape.as_list(), [3, 1]) + output_vector_2 = nn_params.forward_pass(input_vector, 0, is_abs=True) + self.assertEqual(output_vector_2.shape.as_list(), [3, 1]) + input_vector_trans = tf.random_uniform(shape=(3, 1), dtype=tf.float32) + output_vector_3 = nn_params.forward_pass( + input_vector_trans, 0, is_transpose=True + ) + self.assertEqual(output_vector_3.shape.as_list(), [2, 1]) + + +if __name__ == "__main__": + unittest.main() diff --git a/cleverhans_v3.1.0/cleverhans/experimental/certification/tests/optimization_test.py b/cleverhans_v3.1.0/cleverhans/experimental/certification/tests/optimization_test.py new file mode 100644 index 000000000..f1193e318 --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans/experimental/certification/tests/optimization_test.py @@ -0,0 +1,228 @@ +"""Tests for cleverhans.experimental.certification.optimization.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import numpy as np +import tensorflow as tf + +from cleverhans.experimental.certification import dual_formulation +from cleverhans.experimental.certification import nn +from cleverhans.experimental.certification import optimization + + +class OptimizationTest(tf.test.TestCase): + # pylint: disable=missing-docstring + + def prepare_dual_object(self): + # Function to prepare dual object to be used for testing optimization. + net_weights = [[[2, 2], [3, 3], [4, 4]], [[1, 1, 1], [-1, -1, -1]]] + net_biases = [ + np.transpose(np.matrix([0, 0, 0])), + np.transpose(np.matrix([0, 0])), + ] + net_layer_types = ["ff_relu", "ff"] + nn_params1 = nn.NeuralNetwork(net_weights, net_biases, net_layer_types) + + test_input = np.transpose(np.matrix([0, 0])) + true_class = 0 + adv_class = 1 + input_minval = 0 + input_maxval = 0 + epsilon = 0.1 + + # Creating dual variables to use for optimization + lambda_pos = [ + tf.get_variable( + "lambda_pos0", + initializer=np.random.uniform(0, 0.1, size=(2, 1)).astype(np.float32), + ), + tf.get_variable( + "lambda_pos1", + initializer=np.random.uniform(0, 0.1, size=(3, 1)).astype(np.float32), + ), + ] + lambda_neg = [ + tf.get_variable( + "lambda_neg0", + initializer=np.random.uniform(0, 0.1, size=(2, 1)).astype(np.float32), + ), + tf.get_variable( + "lambda_neg1", + initializer=np.random.uniform(0, 0.1, size=(3, 1)).astype(np.float32), + ), + ] + lambda_quad = [ + tf.get_variable( + "lambda_quad0", + initializer=np.random.uniform(0, 0.1, size=(2, 1)).astype(np.float32), + ), + tf.get_variable( + "lambda_quad1", + initializer=np.random.uniform(0, 0.1, size=(3, 1)).astype(np.float32), + ), + ] + lambda_lu = [ + tf.get_variable( + "lambda_lu0", + initializer=np.random.uniform(0, 0.1, size=(2, 1)).astype(np.float32), + ), + tf.get_variable( + "lambda_lu1", + initializer=np.random.uniform(0, 0.1, size=(3, 1)).astype(np.float32), + ), + ] + nu = tf.reshape( + tf.get_variable("nu", initializer=200.0, dtype=tf.float32), shape=(1, 1) + ) + dual_var = { + "lambda_pos": lambda_pos, + "lambda_neg": lambda_neg, + "lambda_quad": lambda_quad, + "lambda_lu": lambda_lu, + "nu": nu, + } + sess = tf.Session() + dual_formulation_object = dual_formulation.DualFormulation( + sess, + dual_var, + nn_params1, + test_input, + true_class, + adv_class, + input_minval, + input_maxval, + epsilon, + ) + return sess, dual_formulation_object + + def test_init(self): + """ Function to test initialization of OptimizationTest. """ + sess, dual_formulation_object = self.prepare_dual_object() + dual_formulation_object.set_differentiable_objective() + sess.run(tf.global_variables_initializer()) + optimization_params = { + "init_learning_rate": 0.1, + "learning_rate_decay": 0.9, + "eig_num_iter": 10, + "eig_learning_rate": 0.01, + "init_smooth": 0.5, + "smooth_decay": 0.9, + "inner_num_steps": 10, + "optimizer": "adam", + "momentum_parameter": 0.9, + "eig_type": "TF", + } + optimization_object = optimization.Optimization( + dual_formulation_object, sess, optimization_params + ) + self.assertIsNotNone(optimization_object) + + def test_get_min_eig_vec_proxy(self): + """ Function test computing min eigen value using matrix vector products.""" + sess, dual_formulation_object = self.prepare_dual_object() + _, matrix_m = dual_formulation_object.get_full_psd_matrix() + optimization_params = { + "init_learning_rate": 0.1, + "learning_rate_decay": 0.9, + "eig_num_iter": 2000, + "eig_learning_rate": 0.01, + "init_smooth": 0.0, + "smooth_decay": 0.9, + "inner_num_steps": 10, + "optimizer": "adam", + "momentum_parameter": 0.9, + "eig_type": "TF", + } + sess.run(tf.global_variables_initializer()) + optimization_object = optimization.Optimization( + dual_formulation_object, sess, optimization_params + ) + eig_vec = optimization_object.get_min_eig_vec_proxy() + tf_eig_vec = optimization_object.get_min_eig_vec_proxy(use_tf_eig=True) + self.assertIsNotNone(eig_vec) + + # Running the graphs and checking that minimum eigen value is correct + # ** No smoothing + tf_eig_vec_val, eig_vec_val, matrix_m_val = sess.run( + [tf_eig_vec, eig_vec, matrix_m], + feed_dict={ + optimization_object.eig_init_vec_placeholder: np.random.rand(6, 1), + optimization_object.eig_num_iter_placeholder: 2000, + optimization_object.smooth_placeholder: 0.0, + }, + ) + + # Eigen value corresponding to v is v^\top M v + eig_val = np.matmul( + np.transpose(eig_vec_val), np.matmul(matrix_m_val, eig_vec_val) + ) + tf_eig_val = np.matmul( + np.transpose(tf_eig_vec_val), np.matmul(matrix_m_val, tf_eig_vec_val) + ) + [np_eig_values, _] = np.linalg.eig(matrix_m_val) + self.assertLess(np.abs(np.min(np_eig_values) - eig_val), 1e-5) + self.assertLess(np.abs(np.min(np_eig_values) - tf_eig_val), 1e-5) + + # Running the graphs and checking that minimum eigen value is correct + # **Smoothing + optimization_params["init_smooth"] = 0.0001 + optimization_object = optimization.Optimization( + dual_formulation_object, sess, optimization_params + ) + eig_vec = optimization_object.get_min_eig_vec_proxy() + tf_eig_vec = optimization_object.get_min_eig_vec_proxy(use_tf_eig=True) + + tf_eig_vec_val, eig_vec_val, matrix_m_val = sess.run( + [tf_eig_vec, eig_vec, matrix_m], + feed_dict={ + optimization_object.eig_init_vec_placeholder: np.random.rand(6, 1), + optimization_object.smooth_placeholder: 0.1, + optimization_object.eig_num_iter_placeholder: 2000, + }, + ) + + # Eigen value corresponding to v is v^\top M v + eig_val = np.matmul( + np.transpose(eig_vec_val), np.matmul(matrix_m_val, eig_vec_val) + ) + tf_eig_val = np.matmul( + np.transpose(tf_eig_vec_val), np.matmul(matrix_m_val, tf_eig_vec_val) + ) + [np_eig_values, _] = np.linalg.eig(matrix_m_val) + self.assertLess(np.abs(np.min(np_eig_values) - eig_val), 1e-5) + # In general, smoothed version can be far off + self.assertLess(np.abs(np.min(np_eig_values) - tf_eig_val), 1e-1) + + def test_optimization(self): + """Function to test optimization.""" + sess, dual_formulation_object = self.prepare_dual_object() + optimization_params = { + "init_penalty": 10000, + "large_eig_num_steps": 1000, + "small_eig_num_steps": 500, + "inner_num_steps": 10, + "outer_num_steps": 2, + "beta": 2, + "smoothness_parameter": 0.001, + "eig_learning_rate": 0.01, + "optimizer": "adam", + "init_learning_rate": 0.1, + "learning_rate_decay": 0.9, + "momentum_parameter": 0.9, + "print_stats_steps": 1, + "stats_folder": None, + "projection_steps": 200, + "eig_type": "TF", + } + sess.run(tf.global_variables_initializer()) + optimization_object = optimization.Optimization( + dual_formulation_object, sess, optimization_params + ) + is_cert_found = optimization_object.run_optimization() + self.assertFalse(is_cert_found) + + +if __name__ == "__main__": + tf.test.main() diff --git a/cleverhans_v3.1.0/cleverhans/experimental/certification/tests/utils_test.py b/cleverhans_v3.1.0/cleverhans/experimental/certification/tests/utils_test.py new file mode 100644 index 000000000..dc70a59b5 --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans/experimental/certification/tests/utils_test.py @@ -0,0 +1,81 @@ +"""Tests for cleverhans.experimental.certification.utils.""" +# pylint: disable=missing-docstring + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import numpy as np +from scipy.sparse.linalg import eigs +import tensorflow as tf +from tensorflow.contrib import autograph + +from cleverhans.experimental.certification import utils + +MATRIX_DIMENTION = 100 +NUM_LZS_ITERATIONS = 100 +NUM_RANDOM_MATRICES = 10 + + +class UtilsTest(tf.test.TestCase): + def test_minimum_eigen_vector(self): + matrix = np.array([[1.0, 2.0], [2.0, 5.0]], dtype=np.float32) + initial_vec = np.array([[1.0], [-1.0]], dtype=np.float32) + + def _vector_prod_fn(x): + return tf.matmul(matrix, x) + + min_eigen_fn = autograph.to_graph(utils.minimum_eigen_vector) + x = tf.placeholder(tf.float32, shape=(2, 1)) + min_eig_vec = min_eigen_fn(x, 10, 0.1, _vector_prod_fn) + with self.test_session() as sess: + v = sess.run(min_eig_vec, feed_dict={x: initial_vec}) + if v.flatten()[0] < 0: + v = -v + np.testing.assert_almost_equal(v, [[0.9239], [-0.3827]], decimal=4) + + def test_tf_lanczos_smallest_eigval(self): + tf_num_iter = tf.placeholder(dtype=tf.int32, shape=()) + tf_matrix = tf.placeholder(dtype=tf.float32) + + def _vector_prod_fn(x): + return tf.matmul(tf_matrix, tf.reshape(x, [-1, 1])) + + min_eigen_fn = autograph.to_graph(utils.tf_lanczos_smallest_eigval) + init_vec_ph = tf.placeholder(shape=(MATRIX_DIMENTION, 1), dtype=tf.float32) + tf_eigval, tf_eigvec = min_eigen_fn( + _vector_prod_fn, + MATRIX_DIMENTION, + init_vec_ph, + tf_num_iter, + dtype=tf.float32, + ) + eigvec = np.zeros((MATRIX_DIMENTION, 1), dtype=np.float32) + + with self.test_session() as sess: + # run this test for a few random matrices + for _ in range(NUM_RANDOM_MATRICES): + matrix = np.random.random((MATRIX_DIMENTION, MATRIX_DIMENTION)) + matrix = matrix + matrix.T # symmetrizing matrix + eigval, eigvec = sess.run( + [tf_eigval, tf_eigvec], + feed_dict={ + tf_num_iter: NUM_LZS_ITERATIONS, + tf_matrix: matrix, + init_vec_ph: eigvec, + }, + ) + + scipy_min_eigval, scipy_min_eigvec = eigs(matrix, k=1, which="SR") + scipy_min_eigval = np.real(scipy_min_eigval) + scipy_min_eigvec = np.real(scipy_min_eigvec) + scipy_min_eigvec = scipy_min_eigvec / np.linalg.norm(scipy_min_eigvec) + + np.testing.assert_almost_equal(eigval, scipy_min_eigval, decimal=3) + np.testing.assert_almost_equal(np.linalg.norm(eigvec), 1.0, decimal=3) + abs_dot_prod = abs(np.dot(eigvec.flatten(), scipy_min_eigvec.flatten())) + np.testing.assert_almost_equal(abs_dot_prod, 1.0, decimal=3) + + +if __name__ == "__main__": + tf.test.main() diff --git a/cleverhans_v3.1.0/cleverhans/experimental/certification/utils.py b/cleverhans_v3.1.0/cleverhans/experimental/certification/utils.py new file mode 100644 index 000000000..e931b7cb2 --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans/experimental/certification/utils.py @@ -0,0 +1,326 @@ +"""File containing some simple helper functions.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import numpy as np +import tensorflow as tf + + +def diag(diag_elements): + """Function to create tensorflow diagonal matrix with input diagonal entries. + + Args: + diag_elements: tensor with diagonal elements + + Returns: + tf matrix with diagonal entries as diag_elements + """ + return tf.diag(tf.reshape(diag_elements, [-1])) + + +def initialize_dual( + neural_net_params_object, + init_dual_file=None, + random_init_variance=0.01, + init_nu=200.0, +): + """Function to initialize the dual variables of the class. + + Args: + neural_net_params_object: Object with the neural net weights, biases + and types + init_dual_file: Path to file containing dual variables, if the path + is empty, perform random initialization + Expects numpy dictionary with + lambda_pos_0, lambda_pos_1, .. + lambda_neg_0, lambda_neg_1, .. + lambda_quad_0, lambda_quad_1, .. + lambda_lu_0, lambda_lu_1, .. + random_init_variance: variance for random initialization + init_nu: Value to initialize nu variable with + + Returns: + dual_var: dual variables initialized appropriately. + """ + lambda_pos = [] + lambda_neg = [] + lambda_quad = [] + lambda_lu = [] + + if init_dual_file is None: + for i in range(0, neural_net_params_object.num_hidden_layers + 1): + initializer = ( + np.random.uniform( + 0, random_init_variance, size=(neural_net_params_object.sizes[i], 1) + ) + ).astype(np.float32) + lambda_pos.append( + tf.get_variable( + "lambda_pos_" + str(i), initializer=initializer, dtype=tf.float32 + ) + ) + initializer = ( + np.random.uniform( + 0, random_init_variance, size=(neural_net_params_object.sizes[i], 1) + ) + ).astype(np.float32) + lambda_neg.append( + tf.get_variable( + "lambda_neg_" + str(i), initializer=initializer, dtype=tf.float32 + ) + ) + initializer = ( + np.random.uniform( + 0, random_init_variance, size=(neural_net_params_object.sizes[i], 1) + ) + ).astype(np.float32) + lambda_quad.append( + tf.get_variable( + "lambda_quad_" + str(i), initializer=initializer, dtype=tf.float32 + ) + ) + initializer = ( + np.random.uniform( + 0, random_init_variance, size=(neural_net_params_object.sizes[i], 1) + ) + ).astype(np.float32) + lambda_lu.append( + tf.get_variable( + "lambda_lu_" + str(i), initializer=initializer, dtype=tf.float32 + ) + ) + nu = tf.get_variable("nu", initializer=init_nu) + else: + # Loading from file + dual_var_init_val = np.load(init_dual_file).item() + for i in range(0, neural_net_params_object.num_hidden_layers + 1): + lambda_pos.append( + tf.get_variable( + "lambda_pos_" + str(i), + initializer=dual_var_init_val["lambda_pos"][i], + dtype=tf.float32, + ) + ) + lambda_neg.append( + tf.get_variable( + "lambda_neg_" + str(i), + initializer=dual_var_init_val["lambda_neg"][i], + dtype=tf.float32, + ) + ) + lambda_quad.append( + tf.get_variable( + "lambda_quad_" + str(i), + initializer=dual_var_init_val["lambda_quad"][i], + dtype=tf.float32, + ) + ) + lambda_lu.append( + tf.get_variable( + "lambda_lu_" + str(i), + initializer=dual_var_init_val["lambda_lu"][i], + dtype=tf.float32, + ) + ) + nu = tf.get_variable("nu", initializer=1.0 * dual_var_init_val["nu"]) + dual_var = { + "lambda_pos": lambda_pos, + "lambda_neg": lambda_neg, + "lambda_quad": lambda_quad, + "lambda_lu": lambda_lu, + "nu": nu, + } + return dual_var + + +def eig_one_step(current_vector, learning_rate, vector_prod_fn): + """Function that performs one step of gd (variant) for min eigen value. + + Args: + current_vector: current estimate of the eigen vector with minimum eigen + value. + learning_rate: learning rate. + vector_prod_fn: function which returns product H*x, where H is a matrix for + which we computing eigenvector. + + Returns: + updated vector after one step + """ + grad = 2 * vector_prod_fn(current_vector) + # Current objective = (1/2)*v^T (2*M*v); v = current_vector + # grad = 2*M*v + current_objective = tf.reshape( + tf.matmul(tf.transpose(current_vector), grad) / 2.0, shape=() + ) + + # Project the gradient into the tangent space of the constraint region. + # This way we do not waste time taking steps that try to change the + # norm of current_vector + grad = grad - current_vector * tf.matmul(tf.transpose(current_vector), grad) + grad_norm = tf.norm(grad) + grad_norm_sq = tf.square(grad_norm) + + # Computing normalized gradient of unit norm + norm_grad = grad / grad_norm + + # Computing directional second derivative (dsd) + # dsd = 2*g^T M g, where g is normalized gradient + directional_second_derivative = tf.reshape( + 2 * tf.matmul(tf.transpose(norm_grad), vector_prod_fn(norm_grad)), shape=() + ) + + # Computing grad^\top M grad [useful to compute step size later] + # Just a rescaling of the directional_second_derivative (which uses + # normalized gradient + grad_m_grad = directional_second_derivative * grad_norm_sq / 2 + + # Directional_second_derivative/2 = objective when vector is norm_grad + # If this is smaller than current objective, simply return that + if directional_second_derivative / 2.0 < current_objective: + return norm_grad + + # If curvature is positive, jump to the bottom of the bowl + if directional_second_derivative > 0.0: + step = -1.0 * grad_norm / directional_second_derivative + else: + # If the gradient is very small, do not move + if grad_norm_sq <= 1e-16: + step = 0.0 + else: + # Make a heuristic guess of the step size + step = -2.0 * tf.reduce_sum(current_vector * grad) / grad_norm_sq + # Computing gain using the gradient and second derivative + gain = -( + 2 * tf.reduce_sum(current_vector * grad) + (step * step) * grad_m_grad + ) + + # Fall back to pre-determined learning rate if no gain + if gain < 0.0: + step = -learning_rate * grad_norm + current_vector = current_vector + step * norm_grad + return tf.nn.l2_normalize(current_vector) + + +def minimum_eigen_vector(x, num_steps, learning_rate, vector_prod_fn): + """Computes eigenvector which corresponds to minimum eigenvalue. + + Args: + x: initial value of eigenvector. + num_steps: number of optimization steps. + learning_rate: learning rate. + vector_prod_fn: function which takes x and returns product H*x. + + Returns: + approximate value of eigenvector. + + This function finds approximate value of eigenvector of matrix H which + corresponds to smallest (by absolute value) eigenvalue of H. + It works by solving optimization problem x^{T}*H*x -> min. + """ + x = tf.nn.l2_normalize(x) + for _ in range(num_steps): + x = eig_one_step(x, learning_rate, vector_prod_fn) + return x + + +def tf_lanczos_smallest_eigval( + vector_prod_fn, + matrix_dim, + initial_vector, + num_iter=1000, + max_iter=1000, + collapse_tol=1e-9, + dtype=tf.float32, +): + """Computes smallest eigenvector and eigenvalue using Lanczos in pure TF. + + This function computes smallest eigenvector and eigenvalue of the matrix + which is implicitly specified by `vector_prod_fn`. + `vector_prod_fn` is a function which takes `x` and returns a product of matrix + in consideration and `x`. + Computation is done using Lanczos algorithm, see + https://en.wikipedia.org/wiki/Lanczos_algorithm#The_algorithm + + Args: + vector_prod_fn: function which takes a vector as an input and returns + matrix vector product. + matrix_dim: dimentionality of the matrix. + initial_vector: guess vector to start the algorithm with + num_iter: user-defined number of iterations for the algorithm + max_iter: maximum number of iterations. + collapse_tol: tolerance to determine collapse of the Krylov subspace + dtype: type of data + + Returns: + tuple of (eigenvalue, eigenvector) of smallest eigenvalue and corresponding + eigenvector. + """ + + # alpha will store diagonal elements + alpha = tf.TensorArray(dtype, size=1, dynamic_size=True, element_shape=()) + # beta will store off diagonal elements + beta = tf.TensorArray(dtype, size=0, dynamic_size=True, element_shape=()) + # q will store Krylov space basis + q_vectors = tf.TensorArray( + dtype, size=1, dynamic_size=True, element_shape=(matrix_dim, 1) + ) + + # If start vector is all zeros, make it a random normal vector and run for max_iter + if tf.norm(initial_vector) < collapse_tol: + initial_vector = tf.random_normal(shape=(matrix_dim, 1), dtype=dtype) + num_iter = max_iter + + w = initial_vector / tf.norm(initial_vector) + + # Iteration 0 of Lanczos + q_vectors = q_vectors.write(0, w) + w_ = vector_prod_fn(w) + cur_alpha = tf.reduce_sum(w_ * w) + alpha = alpha.write(0, cur_alpha) + w_ = w_ - tf.scalar_mul(cur_alpha, w) + w_prev = w + w = w_ + + # Subsequent iterations of Lanczos + for i in tf.range(1, num_iter): + cur_beta = tf.norm(w) + if cur_beta < collapse_tol: + # return early if Krylov subspace collapsed + break + + # cur_beta is larger than collapse_tol, + # so division will return finite result. + w = w / cur_beta + + w_ = vector_prod_fn(w) + cur_alpha = tf.reduce_sum(w_ * w) + + q_vectors = q_vectors.write(i, w) + alpha = alpha.write(i, cur_alpha) + beta = beta.write(i - 1, cur_beta) + + w_ = w_ - tf.scalar_mul(cur_alpha, w) - tf.scalar_mul(cur_beta, w_prev) + w_prev = w + w = w_ + + alpha = alpha.stack() + beta = beta.stack() + q_vectors = tf.reshape(q_vectors.stack(), (-1, matrix_dim)) + + offdiag_submatrix = tf.linalg.diag(beta) + tridiag_matrix = ( + tf.linalg.diag(alpha) + + tf.pad(offdiag_submatrix, [[0, 1], [1, 0]]) + + tf.pad(offdiag_submatrix, [[1, 0], [0, 1]]) + ) + + eigvals, eigvecs = tf.linalg.eigh(tridiag_matrix) + + smallest_eigval = eigvals[0] + smallest_eigvec = tf.matmul(tf.reshape(eigvecs[:, 0], (1, -1)), q_vectors) + smallest_eigvec = smallest_eigvec / tf.norm(smallest_eigvec) + smallest_eigvec = tf.reshape(smallest_eigvec, (matrix_dim, 1)) + + return smallest_eigval, smallest_eigvec diff --git a/cleverhans_v3.1.0/cleverhans/initializers.py b/cleverhans_v3.1.0/cleverhans/initializers.py new file mode 100644 index 000000000..a750313d8 --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans/initializers.py @@ -0,0 +1,23 @@ +""" +Initializers. +""" + +import tensorflow as tf + + +class HeReLuNormalInitializer(tf.initializers.random_normal): + """ + The initializer from He et al 2015 + """ + + def __init__(self, dtype=tf.float32): + super(HeReLuNormalInitializer, self).__init__(dtype=dtype) + + def get_config(self): + return dict(dtype=self.dtype.name) + + def __call__(self, shape, dtype=None, partition_info=None): + del partition_info + dtype = self.dtype if dtype is None else dtype + std = tf.rsqrt(tf.cast(tf.reduce_prod(shape[:-1]), tf.float32) + 1e-7) + return tf.random_normal(shape, stddev=std, dtype=dtype) diff --git a/cleverhans_v3.1.0/cleverhans/loss.py b/cleverhans_v3.1.0/cleverhans/loss.py new file mode 100644 index 000000000..aa498769b --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans/loss.py @@ -0,0 +1,551 @@ +"""Loss functions for training models.""" +import copy +import json +import os +import warnings + +import numpy as np +import tensorflow as tf + +from cleverhans.attacks import Attack +from cleverhans.compat import softmax_cross_entropy_with_logits +from cleverhans.model import Model +from cleverhans.utils import safe_zip + +try: + import tensorflow_probability as tfp + + tf_distributions = tfp.distributions +except ImportError: + tf_distributions = tf.distributions + + +class Loss(object): + """ + An abstract interface for loss wrappers that allows flexible control of + real examples, adversarial examples and labels. These losses are used + for defenses (during model training). + """ + + def __init__(self, model, hparams=None, attack=None): + """ + :param model: Model instance, the model on which to apply the loss. + :param hparams: dict, hyper-parameters for the loss. + :param attack: cleverhans.attacks.Attack instance + """ + assert isinstance(model, Model) + standard = attack is None or isinstance(attack, Attack) + deprecated = callable(attack) + if not standard and not deprecated: + raise TypeError("`attack` must be `None` or `Attack` subclass instance") + if deprecated: + warnings.warn( + "callable attacks are deprecated, switch to an Attack " + "subclass. callable attacks will not be supported after " + "2019-05-05." + ) + + class Wrapper(Attack): + """ + Temporary wrapper class to be removed when deprecated callable + arguments are removed. + + :param f: a callable object implementing the attack + """ + + def __init__(self, f): + dummy_model = Model() + super(Wrapper, self).__init__(model=dummy_model) + self.f = f + + def generate(self, x): + return self.f(x) + + attack = Wrapper(attack) + self.model = model + self.hparams = hparams + self.attack = attack + + def save(self, path): + """Save loss in json format""" + json.dump( + dict(loss=self.__class__.__name__, params=self.hparams), + open(os.path.join(path, "loss.json"), "wb"), + ) + + def fprop(self, x, y): + """Forward propagate the loss. + Loss should be a scalar value, independent of batch size (i.e. use + reduce_mean over batch axis, don't use reduce_sum or return a tensor). + Scalar losses are easier to add together, e.g. through `WeightedSum`. + Mean losses are easier to redistribute across multiple replicas without + needing to change learning rates, etc. + :param x: tensor, a batch of inputs. + :param y: tensor, a batch of outputs (1-hot labels typically). + """ + raise NotImplementedError + + +class WeightedSum(Loss): + """ + A Loss that adds up a weighted sum of other losses. + """ + + def __init__(self, model, terms): + self.terms = terms + + Loss.__init__(self, model, locals()) + + def fprop(self, x, y, **kwargs): + weights, loss_objects = safe_zip(*self.terms) + for weight in weights: + if isinstance(weight, float): + continue + if hasattr(weight, "ndim"): + assert weight.ndim == 0 + continue + raise TypeError( + "weight of %s is not a type that this function " + "knows it can accept yet" % str(weight) + ) + losses = [loss.fprop(x, y, **kwargs) for loss in loss_objects] + for loss, loss_object in safe_zip(losses, loss_objects): + if len(loss.get_shape()) > 0: + raise ValueError( + "%s.fprop returned a non-scalar value" % str(loss_object) + ) + terms = [weight * loss for weight, loss in safe_zip(weights, losses)] + + return tf.add_n(terms) + + +class CrossEntropy(Loss): + """Cross-entropy loss for a multiclass softmax classifier. + :param model: Model instance, the model on which to apply the loss. + :param smoothing: float, amount of label smoothing for cross-entropy. + :param attack: function, given an input x, return an attacked x'. + :param pass_y: bool, if True pass y to the attack + :param adv_coeff: Coefficient to put on the cross-entropy for + adversarial examples, if adversarial examples are used. + The coefficient on the cross-entropy for clean examples is + 1. - adv_coeff. + :param attack_params: dict, keyword arguments passed to `attack.generate` + """ + + def __init__( + self, + model, + smoothing=0.0, + attack=None, + pass_y=False, + adv_coeff=0.5, + attack_params=None, + **kwargs + ): + if smoothing < 0 or smoothing > 1: + raise ValueError("Smoothing must be in [0, 1]", smoothing) + self.kwargs = kwargs + Loss.__init__(self, model, locals(), attack) + self.smoothing = smoothing + self.adv_coeff = adv_coeff + self.pass_y = pass_y + self.attack_params = attack_params + + def fprop(self, x, y, **kwargs): + kwargs.update(self.kwargs) + if self.attack is not None: + attack_params = copy.copy(self.attack_params) + if attack_params is None: + attack_params = {} + if self.pass_y: + attack_params["y"] = y + x = x, self.attack.generate(x, **attack_params) + coeffs = [1.0 - self.adv_coeff, self.adv_coeff] + if self.adv_coeff == 1.0: + x = (x[1],) + coeffs = (coeffs[1],) + else: + x = tuple([x]) + coeffs = [1.0] + assert np.allclose(sum(coeffs), 1.0) + + # Catching RuntimeError: Variable -= value not supported by tf.eager. + try: + y -= self.smoothing * (y - 1.0 / tf.cast(y.shape[-1], y.dtype)) + except RuntimeError: + y.assign_sub(self.smoothing * (y - 1.0 / tf.cast(y.shape[-1], y.dtype))) + + logits = [self.model.get_logits(x, **kwargs) for x in x] + loss = sum( + coeff + * tf.reduce_mean(softmax_cross_entropy_with_logits(labels=y, logits=logit)) + for coeff, logit in safe_zip(coeffs, logits) + ) + return loss + + +class MixUp(Loss): + """Mixup ( https://arxiv.org/abs/1710.09412 ) + :param model: Model instance, the model on which to apply the loss. + :param beta: float, beta distribution parameter for MixUp. + """ + + def __init__(self, model, beta, **kwargs): + del kwargs + Loss.__init__(self, model, locals()) + self.beta = beta + + def fprop(self, x, y, **kwargs): + with tf.device("/CPU:0"): + # Prevent error complaining GPU kernels unavailable for this. + mix = tf_distributions.Beta(self.beta, self.beta) + mix = mix.sample([tf.shape(x)[0]] + [1] * (len(x.shape) - 1)) + mix = tf.maximum(mix, 1 - mix) + mix_label = tf.reshape(mix, [-1, 1]) + xm = x + mix * (x[::-1] - x) + ym = y + mix_label * (y[::-1] - y) + logits = self.model.get_logits(xm, **kwargs) + loss = tf.reduce_mean( + softmax_cross_entropy_with_logits(labels=ym, logits=logits) + ) + return loss + + +class FeaturePairing(Loss): + """Feature pairing loss. + :param model: Model instance, the model on which to apply the loss. + :param weight: float, with of logic pairing loss. + :param attack: function, given an input x, return an attacked x'. + """ + + def __init__(self, model, weight, attack, **kwargs): + del kwargs + Loss.__init__(self, model, locals(), attack) + self.weight = weight + + def fprop(self, x, y, **kwargs): + x_adv = self.attack.generate(x) + d1 = self.model.fprop(x, **kwargs) + d2 = self.model.fprop(x_adv, **kwargs) + pairing_loss = [ + tf.reduce_mean(tf.square(a - b)) + for a, b in zip(d1[Model.O_FEATURES], d2[Model.O_FEATURES]) + ] + pairing_loss = tf.reduce_mean(pairing_loss) + loss = tf.reduce_mean( + softmax_cross_entropy_with_logits(labels=y, logits=d1[Model.O_LOGITS]) + ) + loss += tf.reduce_mean( + softmax_cross_entropy_with_logits(labels=y, logits=d2[Model.O_LOGITS]) + ) + return loss + self.weight * pairing_loss + + +class WeightDecay(Loss): + """Weight decay""" + + def fprop(self, x, y, **kwargs): + terms = [ + tf.nn.l2_loss(param) + for param in self.model.get_params() + if len(param.get_shape()) > 1 + ] + out = tf.add_n(terms) + assert len(out.get_shape()) == 0 + return out + + +class LossCrossEntropy(Loss): + """ + Deprecated version of `CrossEntropy` that returns per-example loss rather + than mean loss. + """ + + def __init__(self, model, smoothing=0.0, attack=None, **kwargs): + """Constructor. + :param model: Model instance, the model on which to apply the loss. + :param smoothing: float, amount of label smoothing for cross-entropy. + :param attack: function, given an input x, return an attacked x'. + """ + if smoothing < 0 or smoothing > 1: + raise ValueError("Smoothing must be in [0, 1]", smoothing) + del kwargs + Loss.__init__(self, model, locals(), attack) + self.smoothing = smoothing + + def fprop(self, x, y, **kwargs): + if self.attack is not None: + x = x, self.attack(x) + else: + x = tuple([x]) + + # Catching RuntimeError: Variable -= value not supported by tf.eager. + try: + y -= self.smoothing * (y - 1.0 / tf.cast(y.shape[-1], tf.float32)) + except RuntimeError: + y.assign_sub(self.smoothing * (y - 1.0 / tf.cast(y.shape[-1], tf.float32))) + + logits = [self.model.get_logits(x, **kwargs) for x in x] + loss = sum( + softmax_cross_entropy_with_logits(labels=y, logits=logit) + for logit in logits + ) + warnings.warn( + "LossCrossEntropy is deprecated, switch to " + "CrossEntropy. LossCrossEntropy may be removed on " + "or after 2019-03-06." + ) + return loss + + +class LossFeaturePairing(Loss): + """Deprecated version of `FeaturePairing` that returns per-example loss + rather than mean loss.""" + + def __init__(self, model, weight, attack, **kwargs): + """Constructor. + :param model: Model instance, the model on which to apply the loss. + :param weight: float, with of logic pairing loss. + :param attack: function, given an input x, return an attacked x'. + """ + del kwargs + Loss.__init__(self, model, locals(), attack) + self.weight = weight + + def fprop(self, x, y, **kwargs): + x_adv = self.attack(x) + d1 = self.model.fprop(x, **kwargs) + d2 = self.model.fprop(x_adv, **kwargs) + pairing_loss = [ + tf.reduce_mean(tf.square(a - b)) + for a, b in zip(d1[Model.O_FEATURES], d2[Model.O_FEATURES]) + ] + pairing_loss = tf.reduce_mean(pairing_loss) + loss = softmax_cross_entropy_with_logits(labels=y, logits=d1[Model.O_LOGITS]) + loss += softmax_cross_entropy_with_logits(labels=y, logits=d2[Model.O_LOGITS]) + warnings.warn( + "LossFeaturePairing is deprecated, switch to " + "FeaturePairing. LossFeaturePairing may be removed " + "on or after 2019-03-06." + ) + return loss + self.weight * pairing_loss + + +class LossMixUp(Loss): + """Deprecated version of `MixUp` that returns per-example loss + rather than mean loss.""" + + def __init__(self, model, beta, **kwargs): + """Constructor. + :param model: Model instance, the model on which to apply the loss. + :param beta: float, beta distribution parameter for MixUp. + """ + del kwargs + Loss.__init__(self, model, locals()) + self.beta = beta + + def fprop(self, x, y, **kwargs): + mix = tf_distributions.Beta(self.beta, self.beta) + mix = mix.sample([tf.shape(x)[0]] + [1] * (len(x.shape) - 1)) + xm = x + mix * (x[::-1] - x) + ym = y + mix * (y[::-1] - y) + logits = self.model.get_logits(xm, **kwargs) + loss = softmax_cross_entropy_with_logits(labels=ym, logits=logits) + warnings.warn( + "LossMixUp is deprecated, switch to " + "MixUp. LossFeaturePairing may be removed " + "on or after 2019-03-06." + ) + return loss + + +class SNNLCrossEntropy(CrossEntropy): + """A combination loss of Soft Nearest Neighbor Loss calculated at every layer + in the network, and standard cross entropy of the logits. Presented in + "Analyzing and Improving Representations with the Soft Nearest Neighbor Loss" + by Nicholas Frosst, Nicolas Papernot, and Geoffrey Hinton. + arXiv preprint arXiv:1902.01889 (2019).""" + + STABILITY_EPS = 0.00001 # used to make the calculation of SNNL more stable + + def __init__( + self, + model, + temperature=100.0, + layer_names=None, + factor=-10.0, + optimize_temperature=True, + cos_distance=False, + ): + """Constructor. + :param model: Model instance, the model on which to apply the loss. + :param temperature: Temperature used for SNNL. + :layer_names: The names of the layers at which to calculate SNNL. + If not provided, then SNNL is applied to each internal layer. + :factor: The balance factor between SNNL and ross Entropy. If factor is + negative, then SNNL will be maximized. + :optimize_temperature: Optimize temperature at each calculation to minimize + the loss. This makes the loss more stable. + :cos_distance: Use cosine distance when calculating SNNL. + """ + CrossEntropy.__init__(self, model, smoothing=0.0) + self.temperature = temperature + self.factor = factor + self.optimize_temperature = optimize_temperature + self.cos_distance = cos_distance + self.layer_names = layer_names + if not layer_names: + # omit the final layer, the classification layer + self.layer_names = model.get_layer_names()[:-1] + + @staticmethod + def pairwise_euclid_distance(A, B): + """Pairwise Euclidean distance between two matrices. + :param A: a matrix. + :param B: a matrix. + + :returns: A tensor for the pairwise Euclidean between A and B. + """ + batchA = tf.shape(A)[0] + batchB = tf.shape(B)[0] + + sqr_norm_A = tf.reshape(tf.reduce_sum(tf.pow(A, 2), 1), [1, batchA]) + sqr_norm_B = tf.reshape(tf.reduce_sum(tf.pow(B, 2), 1), [batchB, 1]) + inner_prod = tf.matmul(B, A, transpose_b=True) + + tile_1 = tf.tile(sqr_norm_A, [batchB, 1]) + tile_2 = tf.tile(sqr_norm_B, [1, batchA]) + return tile_1 + tile_2 - 2 * inner_prod + + @staticmethod + def pairwise_cos_distance(A, B): + """Pairwise cosine distance between two matrices. + :param A: a matrix. + :param B: a matrix. + + :returns: A tensor for the pairwise cosine between A and B. + """ + normalized_A = tf.nn.l2_normalize(A, dim=1) + normalized_B = tf.nn.l2_normalize(B, dim=1) + prod = tf.matmul(normalized_A, normalized_B, adjoint_b=True) + return 1 - prod + + @staticmethod + def fits(A, B, temp, cos_distance): + """Exponentiated pairwise distance between each element of A and + all those of B. + :param A: a matrix. + :param B: a matrix. + :param temp: Temperature + :cos_distance: Boolean for using cosine or Euclidean distance. + + :returns: A tensor for the exponentiated pairwise distance between + each element and A and all those of B. + """ + if cos_distance: + distance_matrix = SNNLCrossEntropy.pairwise_cos_distance(A, B) + else: + distance_matrix = SNNLCrossEntropy.pairwise_euclid_distance(A, B) + return tf.exp(-(distance_matrix / temp)) + + @staticmethod + def pick_probability(x, temp, cos_distance): + """Row normalized exponentiated pairwise distance between all the elements + of x. Conceptualized as the probability of sampling a neighbor point for + every element of x, proportional to the distance between the points. + :param x: a matrix + :param temp: Temperature + :cos_distance: Boolean for using cosine or euclidean distance + + :returns: A tensor for the row normalized exponentiated pairwise distance + between all the elements of x. + """ + f = SNNLCrossEntropy.fits(x, x, temp, cos_distance) - tf.eye(tf.shape(x)[0]) + return f / ( + SNNLCrossEntropy.STABILITY_EPS + tf.expand_dims(tf.reduce_sum(f, 1), 1) + ) + + @staticmethod + def same_label_mask(y, y2): + """Masking matrix such that element i,j is 1 iff y[i] == y2[i]. + :param y: a list of labels + :param y2: a list of labels + + :returns: A tensor for the masking matrix. + """ + return tf.cast(tf.squeeze(tf.equal(y, tf.expand_dims(y2, 1))), tf.float32) + + @staticmethod + def masked_pick_probability(x, y, temp, cos_distance): + """The pairwise sampling probabilities for the elements of x for neighbor + points which share labels. + :param x: a matrix + :param y: a list of labels for each element of x + :param temp: Temperature + :cos_distance: Boolean for using cosine or Euclidean distance + + :returns: A tensor for the pairwise sampling probabilities. + """ + return SNNLCrossEntropy.pick_probability( + x, temp, cos_distance + ) * SNNLCrossEntropy.same_label_mask(y, y) + + @staticmethod + def SNNL(x, y, temp, cos_distance): + """Soft Nearest Neighbor Loss + :param x: a matrix. + :param y: a list of labels for each element of x. + :param temp: Temperature. + :cos_distance: Boolean for using cosine or Euclidean distance. + + :returns: A tensor for the Soft Nearest Neighbor Loss of the points + in x with labels y. + """ + summed_masked_pick_prob = tf.reduce_sum( + SNNLCrossEntropy.masked_pick_probability(x, y, temp, cos_distance), 1 + ) + return tf.reduce_mean( + -tf.log(SNNLCrossEntropy.STABILITY_EPS + summed_masked_pick_prob) + ) + + @staticmethod + def optimized_temp_SNNL(x, y, initial_temp, cos_distance): + """The optimized variant of Soft Nearest Neighbor Loss. Every time this + tensor is evaluated, the temperature is optimized to minimize the loss + value, this results in more numerically stable calculations of the SNNL. + :param x: a matrix. + :param y: a list of labels for each element of x. + :param initial_temp: Temperature. + :cos_distance: Boolean for using cosine or Euclidean distance. + + :returns: A tensor for the Soft Nearest Neighbor Loss of the points + in x with labels y, optimized for temperature. + """ + t = tf.Variable(1, dtype=tf.float32, trainable=False, name="temp") + + def inverse_temp(t): + # pylint: disable=missing-docstring + # we use inverse_temp because it was observed to be more stable when optimizing. + return tf.div(initial_temp, t) + + ent_loss = SNNLCrossEntropy.SNNL(x, y, inverse_temp(t), cos_distance) + updated_t = tf.assign(t, tf.subtract(t, 0.1 * tf.gradients(ent_loss, t)[0])) + inverse_t = inverse_temp(updated_t) + return SNNLCrossEntropy.SNNL(x, y, inverse_t, cos_distance) + + def fprop(self, x, y, **kwargs): + cross_entropy = CrossEntropy.fprop(self, x, y, **kwargs) + self.layers = [self.model.get_layer(x, name) for name in self.layer_names] + loss_fn = self.SNNL + if self.optimize_temperature: + loss_fn = self.optimized_temp_SNNL + layers_SNNL = [ + loss_fn( + tf.layers.flatten(layer), + tf.argmax(y, axis=1), + self.temperature, + self.cos_distance, + ) + for layer in self.layers + ] + return cross_entropy + self.factor * tf.add_n(layers_SNNL) diff --git a/cleverhans_v3.1.0/cleverhans/model.py b/cleverhans_v3.1.0/cleverhans/model.py new file mode 100644 index 000000000..6db36af94 --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans/model.py @@ -0,0 +1,304 @@ +""" +The Model class and related functionality. +""" +from abc import ABCMeta +import warnings + +import tensorflow as tf + +from cleverhans import utils_tf + + +class Model(object): + """ + An abstract interface for model wrappers that exposes model symbols + needed for making an attack. This abstraction removes the dependency on + any specific neural network package (e.g. Keras) from the core + code of CleverHans. It can also simplify exposing the hidden features of a + model when a specific package does not directly expose them. + """ + + __metaclass__ = ABCMeta + O_LOGITS, O_PROBS, O_FEATURES = "logits probs features".split() + + def __init__( + self, scope=None, nb_classes=None, hparams=None, needs_dummy_fprop=False + ): + """ + Constructor. + :param scope: str, the name of model. + :param nb_classes: integer, the number of classes. + :param hparams: dict, hyper-parameters for the model. + :needs_dummy_fprop: bool, if True the model's parameters are not + created until fprop is called. + """ + self.scope = scope or self.__class__.__name__ + self.nb_classes = nb_classes + self.hparams = hparams or {} + self.needs_dummy_fprop = needs_dummy_fprop + + def __call__(self, *args, **kwargs): + """ + For compatibility with functions used as model definitions (taking + an input tensor and returning the tensor giving the output + of the model on that input). + """ + + warnings.warn( + "Model.__call__ is deprecated. " + "The call is ambiguous as to whether the output should " + "be logits or probabilities, and getting the wrong one " + "can cause serious problems. " + "The output actually is probabilities, which are a very " + "dangerous thing to use as part of any interface for " + "cleverhans, because softmax probabilities are prone " + "to gradient masking." + "On or after 2019-04-24, this method will change to raise " + "an exception explaining why Model.__call__ should not be " + "used." + ) + + return self.get_probs(*args, **kwargs) + + def get_logits(self, x, **kwargs): + """ + :param x: A symbolic representation (Tensor) of the network input + :return: A symbolic representation (Tensor) of the output logits + (i.e., the values fed as inputs to the softmax layer). + """ + outputs = self.fprop(x, **kwargs) + if self.O_LOGITS in outputs: + return outputs[self.O_LOGITS] + raise NotImplementedError( + str(type(self)) + "must implement `get_logits`" + " or must define a " + self.O_LOGITS + " output in `fprop`" + ) + + def get_predicted_class(self, x, **kwargs): + """ + :param x: A symbolic representation (Tensor) of the network input + :return: A symbolic representation (Tensor) of the predicted label + """ + return tf.argmax(self.get_logits(x, **kwargs), axis=1) + + def get_probs(self, x, **kwargs): + """ + :param x: A symbolic representation (Tensor) of the network input + :return: A symbolic representation (Tensor) of the output + probabilities (i.e., the output values produced by the softmax layer). + """ + d = self.fprop(x, **kwargs) + if self.O_PROBS in d: + output = d[self.O_PROBS] + min_prob = tf.reduce_min(output) + max_prob = tf.reduce_max(output) + asserts = [ + utils_tf.assert_greater_equal(min_prob, tf.cast(0.0, min_prob.dtype)), + utils_tf.assert_less_equal(max_prob, tf.cast(1.0, min_prob.dtype)), + ] + with tf.control_dependencies(asserts): + output = tf.identity(output) + return output + elif self.O_LOGITS in d: + return tf.nn.softmax(logits=d[self.O_LOGITS]) + else: + raise ValueError("Cannot find probs or logits.") + + def fprop(self, x, **kwargs): + """ + Forward propagation to compute the model outputs. + :param x: A symbolic representation of the network input + :return: A dictionary mapping layer names to the symbolic + representation of their output. + """ + raise NotImplementedError("`fprop` not implemented.") + + def get_params(self): + """ + Provides access to the model's parameters. + :return: A list of all Variables defining the model parameters. + """ + + if hasattr(self, "params"): + return list(self.params) + + # Catch eager execution and assert function overload. + try: + if tf.executing_eagerly(): + raise NotImplementedError( + "For Eager execution - get_params " "must be overridden." + ) + except AttributeError: + pass + + # For graph-based execution + scope_vars = tf.get_collection( + tf.GraphKeys.TRAINABLE_VARIABLES, self.scope + "/" + ) + + if len(scope_vars) == 0: + self.make_params() + scope_vars = tf.get_collection( + tf.GraphKeys.TRAINABLE_VARIABLES, self.scope + "/" + ) + assert len(scope_vars) > 0 + + # Make sure no parameters have been added or removed + if hasattr(self, "num_params"): + if self.num_params != len(scope_vars): + print("Scope: ", self.scope) + print("Expected " + str(self.num_params) + " variables") + print("Got " + str(len(scope_vars))) + for var in scope_vars: + print("\t" + str(var)) + assert False + else: + self.num_params = len(scope_vars) + + return scope_vars + + def make_params(self): + """ + Create all Variables to be returned later by get_params. + By default this is a no-op. + Models that need their fprop to be called for their params to be + created can set `needs_dummy_fprop=True` in the constructor. + """ + + if self.needs_dummy_fprop: + if hasattr(self, "_dummy_input"): + return + self._dummy_input = self.make_input_placeholder() + self.fprop(self._dummy_input) + + def get_layer_names(self): + """Return the list of exposed layers for this model.""" + raise NotImplementedError + + def get_layer(self, x, layer, **kwargs): + """Return a layer output. + :param x: tensor, the input to the network. + :param layer: str, the name of the layer to compute. + :param **kwargs: dict, extra optional params to pass to self.fprop. + :return: the content of layer `layer` + """ + return self.fprop(x, **kwargs)[layer] + + def make_input_placeholder(self): + """Create and return a placeholder representing an input to the model. + + This method should respect context managers (e.g. "with tf.device") + and should not just return a reference to a single pre-created + placeholder. + """ + + raise NotImplementedError( + str(type(self)) + " does not implement " "make_input_placeholder" + ) + + def make_label_placeholder(self): + """Create and return a placeholder representing class labels. + + This method should respect context managers (e.g. "with tf.device") + and should not just return a reference to a single pre-created + placeholder. + """ + + raise NotImplementedError( + str(type(self)) + " does not implement " "make_label_placeholder" + ) + + def __hash__(self): + return hash(id(self)) + + def __eq__(self, other): + return self is other + + +class CallableModelWrapper(Model): + """A wrapper that turns a callable into a valid Model""" + + def __init__(self, callable_fn, output_layer): + """ + Wrap a callable function that takes a tensor as input and returns + a tensor as output with the given layer name. + :param callable_fn: The callable function taking a tensor and + returning a given layer as output. + :param output_layer: A string of the output layer returned by the + function. (Usually either "probs" or "logits".) + """ + + super(CallableModelWrapper, self).__init__() + self.output_layer = output_layer + self.callable_fn = callable_fn + + def fprop(self, x, **kwargs): + output = self.callable_fn(x, **kwargs) + + # Do some sanity checking to reduce the chance that probs are used + # as logits accidentally or vice versa + if self.output_layer == "probs": + assert output.op.type == "Softmax" + min_prob = tf.reduce_min(output) + max_prob = tf.reduce_max(output) + asserts = [ + utils_tf.assert_greater_equal(min_prob, tf.cast(0.0, min_prob.dtype)), + utils_tf.assert_less_equal(max_prob, tf.cast(1.0, max_prob.dtype)), + ] + with tf.control_dependencies(asserts): + output = tf.identity(output) + elif self.output_layer == "logits": + assert output.op.type != "Softmax" + + return {self.output_layer: output} + + +def wrapper_warning(): + """ + Issue a deprecation warning. Used in multiple places that implemented + attacks by automatically wrapping a user-supplied callable with a + CallableModelWrapper with output_layer="probs". + Using "probs" as any part of the attack interface is dangerous. + We can't just change output_layer to logits because: + - that would be a silent interface change. We'd have no way of detecting + code that still means to use probs. Note that we can't just check whether + the final output op is a softmax---for example, Inception puts a reshape + after the softmax. + - automatically wrapping user-supplied callables with output_layer='logits' + is even worse, see `wrapper_warning_logits` + Note: this function will be removed at the same time as the code that + calls it. + """ + warnings.warn( + "Passing a callable is deprecated, because using" + " probabilities is dangerous. It has a high risk " + " of causing gradient masking due to loss of precision " + " in the softmax op. Passing a callable rather than a " + " Model subclass will become an error on or after " + " 2019-04-24." + ) + + +def wrapper_warning_logits(): + """ + Issue a deprecation warning. Used in multiple places that implemented + attacks by automatically wrapping a user-supplied callable with a + CallableModelWrapper with output_layer="logits". + This is dangerous because it is under-the-hood automagic that the user + may not realize has been invoked for them. If they pass a callable + that actually outputs probs, the probs will be treated as logits, + resulting in an incorrect cross-entropy loss and severe gradient + masking. + """ + warnings.warn( + "Passing a callable is deprecated, because it runs the " + "risk of accidentally using probabilities in the place " + "of logits. Please switch to passing a Model subclass " + "so that you clearly specify which values are the logits. " + "Passing a callable rather than a Model subclass will become " + "an error on or after 2019-04-24." + ) + + +class NoSuchLayerError(ValueError): + """Raised when a layer that does not exist is requested.""" diff --git a/cleverhans/model_zoo/__init__.py b/cleverhans_v3.1.0/cleverhans/model_zoo/__init__.py similarity index 100% rename from cleverhans/model_zoo/__init__.py rename to cleverhans_v3.1.0/cleverhans/model_zoo/__init__.py diff --git a/cleverhans_v3.1.0/cleverhans/model_zoo/all_convolutional.py b/cleverhans_v3.1.0/cleverhans/model_zoo/all_convolutional.py new file mode 100644 index 000000000..e26e00c37 --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans/model_zoo/all_convolutional.py @@ -0,0 +1,46 @@ +"""Extremely simple model where all parameters are from convolutions. +""" + +import math +import tensorflow as tf + +from cleverhans import initializers +from cleverhans.serial import NoRefModel + + +class ModelAllConvolutional(NoRefModel): + """ + A simple model that uses only convolution and downsampling---no batch norm or other techniques that can complicate + adversarial training. + """ + + def __init__(self, scope, nb_classes, nb_filters, input_shape, **kwargs): + del kwargs + NoRefModel.__init__(self, scope, nb_classes, locals()) + self.nb_filters = nb_filters + self.input_shape = input_shape + + # Do a dummy run of fprop to create the variables from the start + self.fprop(tf.placeholder(tf.float32, [32] + input_shape)) + # Put a reference to the params in self so that the params get pickled + self.params = self.get_params() + + def fprop(self, x, **kwargs): + del kwargs + conv_args = dict( + activation=tf.nn.leaky_relu, + kernel_initializer=initializers.HeReLuNormalInitializer, + kernel_size=3, + padding="same", + ) + y = x + + with tf.variable_scope(self.scope, reuse=tf.AUTO_REUSE): + log_resolution = int(round(math.log(self.input_shape[0]) / math.log(2))) + for scale in range(log_resolution - 2): + y = tf.layers.conv2d(y, self.nb_filters << scale, **conv_args) + y = tf.layers.conv2d(y, self.nb_filters << (scale + 1), **conv_args) + y = tf.layers.average_pooling2d(y, 2, 2) + y = tf.layers.conv2d(y, self.nb_classes, **conv_args) + logits = tf.reduce_mean(y, [1, 2]) + return {self.O_LOGITS: logits, self.O_PROBS: tf.nn.softmax(logits=logits)} diff --git a/cleverhans_v3.1.0/cleverhans/model_zoo/basic_cnn.py b/cleverhans_v3.1.0/cleverhans/model_zoo/basic_cnn.py new file mode 100644 index 000000000..4247234eb --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans/model_zoo/basic_cnn.py @@ -0,0 +1,46 @@ +""" +A pure TensorFlow implementation of a convolutional neural network. +""" +# pylint: disable=missing-docstring +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import functools + +import tensorflow as tf + +from cleverhans import initializers +from cleverhans.model import Model + + +class ModelBasicCNN(Model): + def __init__(self, scope, nb_classes, nb_filters, **kwargs): + del kwargs + Model.__init__(self, scope, nb_classes, locals()) + self.nb_filters = nb_filters + + # Do a dummy run of fprop to make sure the variables are created from + # the start + self.fprop(tf.placeholder(tf.float32, [128, 28, 28, 1])) + # Put a reference to the params in self so that the params get pickled + self.params = self.get_params() + + def fprop(self, x, **kwargs): + del kwargs + my_conv = functools.partial( + tf.layers.conv2d, + activation=tf.nn.relu, + kernel_initializer=initializers.HeReLuNormalInitializer, + ) + with tf.variable_scope(self.scope, reuse=tf.AUTO_REUSE): + y = my_conv(x, self.nb_filters, 8, strides=2, padding="same") + y = my_conv(y, 2 * self.nb_filters, 6, strides=2, padding="valid") + y = my_conv(y, 2 * self.nb_filters, 5, strides=1, padding="valid") + logits = tf.layers.dense( + tf.layers.flatten(y), + self.nb_classes, + kernel_initializer=initializers.HeReLuNormalInitializer, + ) + return {self.O_LOGITS: logits, self.O_PROBS: tf.nn.softmax(logits=logits)} diff --git a/cleverhans/model_zoo/deep_k_nearest_neighbors/README.md b/cleverhans_v3.1.0/cleverhans/model_zoo/deep_k_nearest_neighbors/README.md similarity index 100% rename from cleverhans/model_zoo/deep_k_nearest_neighbors/README.md rename to cleverhans_v3.1.0/cleverhans/model_zoo/deep_k_nearest_neighbors/README.md diff --git a/cleverhans_v3.1.0/cleverhans/model_zoo/deep_k_nearest_neighbors/__init__.py b/cleverhans_v3.1.0/cleverhans/model_zoo/deep_k_nearest_neighbors/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/cleverhans_v3.1.0/cleverhans/model_zoo/deep_k_nearest_neighbors/dknn.py b/cleverhans_v3.1.0/cleverhans/model_zoo/deep_k_nearest_neighbors/dknn.py new file mode 100644 index 000000000..45b68d17d --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans/model_zoo/deep_k_nearest_neighbors/dknn.py @@ -0,0 +1,624 @@ +""" +This code reproduces the MNIST results from the paper +Deep k-Nearest Neighbors: Towards Confident, Interpretable and Robust Deep Learning +https://arxiv.org/abs/1803.04765 + +The LSH backend used in the paper is FALCONN. This script also demonstrates +how to use an alternative backend called FAISS. +""" +# pylint: disable=missing-docstring +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import copy +import os +from bisect import bisect_left +import matplotlib +import matplotlib.pyplot as plt +import numpy as np +from six.moves import xrange +import enum +import tensorflow as tf +from cleverhans.attacks import FastGradientMethod +from cleverhans.loss import CrossEntropy +from cleverhans.dataset import MNIST +from cleverhans.model import Model +from cleverhans.picklable_model import MLP, Conv2D, ReLU, Flatten, Linear, Softmax +from cleverhans.train import train +from cleverhans.utils_tf import batch_eval, model_eval + +if "DISPLAY" not in os.environ: + matplotlib.use("Agg") + + +FLAGS = tf.flags.FLAGS + + +def make_basic_picklable_cnn( + nb_filters=64, nb_classes=10, input_shape=(None, 28, 28, 1) +): + """The model for the picklable models tutorial.""" + layers = [ + Conv2D(nb_filters, (8, 8), (2, 2), "SAME"), + ReLU(), + Conv2D(nb_filters * 2, (6, 6), (2, 2), "VALID"), + ReLU(), + Conv2D(nb_filters * 2, (5, 5), (1, 1), "VALID"), + ReLU(), + Flatten(), + Linear(nb_classes), + Softmax(), + ] + model = MLP(layers, input_shape) + return model + + +class NearestNeighbor: + class BACKEND(enum.Enum): + FALCONN = 1 + FAISS = 2 + + def __init__( + self, + backend, + dimension, + neighbors, + number_bits, + nb_tables=None, + ): + assert backend in NearestNeighbor.BACKEND + + self._NEIGHBORS = neighbors + self._BACKEND = backend + + if self._BACKEND is NearestNeighbor.BACKEND.FALCONN: + self._init_falconn(dimension, number_bits, nb_tables) + elif self._BACKEND is NearestNeighbor.BACKEND.FAISS: + self._init_faiss( + dimension, + ) + else: + raise NotImplementedError + + def _init_falconn( + self, + dimension, + number_bits, + nb_tables, + ): + import falconn + + assert nb_tables >= self._NEIGHBORS + + # LSH parameters + params_cp = falconn.LSHConstructionParameters() + params_cp.dimension = dimension + params_cp.lsh_family = falconn.LSHFamily.CrossPolytope + params_cp.distance_function = falconn.DistanceFunction.EuclideanSquared + params_cp.l = nb_tables + params_cp.num_rotations = ( + 2 # for dense set it to 1; for sparse data set it to 2 + ) + params_cp.seed = 5721840 + # we want to use all the available threads to set up + params_cp.num_setup_threads = 0 + params_cp.storage_hash_table = falconn.StorageHashTable.BitPackedFlatHashTable + + # we build number_bits-bit hashes so that each table has + # 2^number_bits bins; a rule of thumb is to have the number + # of bins be the same order of magnitude as the number of data points + falconn.compute_number_of_hash_functions(number_bits, params_cp) + self._falconn_table = falconn.LSHIndex(params_cp) + self._falconn_query_object = None + self._FALCONN_NB_TABLES = nb_tables + + def _init_faiss( + self, + dimension, + ): + import faiss + + res = faiss.StandardGpuResources() + + self._faiss_index = faiss.GpuIndexFlatL2( + res, + dimension, + ) + + def _find_knns_falconn(self, x, output): + # Late falconn query_object construction + # Since I suppose there might be an error + # if table.setup() will be called after + if self._falconn_query_object is None: + self._falconn_query_object = self._falconn_table.construct_query_object() + self._falconn_query_object.set_num_probes(self._FALCONN_NB_TABLES) + + missing_indices = np.zeros(output.shape, dtype=np.bool) + + for i in range(x.shape[0]): + query_res = self._falconn_query_object.find_k_nearest_neighbors( + x[i], self._NEIGHBORS + ) + try: + output[i, :] = query_res + except: # pylint: disable-msg=W0702 + # mark missing indices + missing_indices[i, len(query_res) :] = True + + output[i, : len(query_res)] = query_res + + return missing_indices + + def _find_knns_faiss(self, x, output): + neighbor_distance, neighbor_index = self._faiss_index.search(x, self._NEIGHBORS) + + missing_indices = neighbor_distance == -1 + + d1 = neighbor_index.reshape(-1) + + output.reshape(-1)[np.logical_not(missing_indices.flatten())] = d1[ + np.logical_not(missing_indices.flatten()) + ] + + return missing_indices + + def add(self, x): + if self._BACKEND is NearestNeighbor.BACKEND.FALCONN: + self._falconn_table.setup(x) + elif self._BACKEND is NearestNeighbor.BACKEND.FAISS: + self._faiss_index.add(x) + else: + raise NotImplementedError + + def find_knns(self, x, output): + if self._BACKEND is NearestNeighbor.BACKEND.FALCONN: + return self._find_knns_falconn(x, output) + elif self._BACKEND is NearestNeighbor.BACKEND.FAISS: + return self._find_knns_faiss(x, output) + else: + raise NotImplementedError + + +class DkNNModel(Model): + def __init__( + self, + neighbors, + layers, + get_activations, + train_data, + train_labels, + nb_classes, + scope=None, + nb_tables=200, + number_bits=17, + ): + """ + Implements the DkNN algorithm. See https://arxiv.org/abs/1803.04765 for more details. + + :param neighbors: number of neighbors to find per layer. + :param layers: a list of layer names to include in the DkNN. + :param get_activations: a callable that takes a np array and a layer name and returns its activations on the data. + :param train_data: a np array of training data. + :param train_labels: a np vector of training labels. + :param nb_classes: the number of classes in the task. + :param scope: a TF scope that was used to create the underlying model. + :param nb_tables: number of tables used by FALCONN to perform locality-sensitive hashing. + :param number_bits: number of hash bits used by LSH. + """ + super(DkNNModel, self).__init__(nb_classes=nb_classes, scope=scope) + self.neighbors = neighbors + self.nb_tables = nb_tables + self.layers = layers + self.get_activations = get_activations + self.nb_cali = -1 + self.calibrated = False + self.number_bits = number_bits + + # Compute training data activations + self.nb_train = train_labels.shape[0] + assert self.nb_train == train_data.shape[0] + self.train_activations = get_activations(train_data) + self.train_labels = train_labels + + # Build locality-sensitive hashing tables for training representations + self.train_activations_lsh = copy.copy(self.train_activations) + self.init_lsh() + + def init_lsh(self): + """ + Initializes locality-sensitive hashing with FALCONN to find nearest neighbors in training data. + """ + self.query_objects = ( + {} + ) # contains the object that can be queried to find nearest neighbors at each layer. + # mean of training data representation per layer (that needs to be substracted before + # NearestNeighbor). + self.centers = {} + for layer in self.layers: + # Normalize all the lenghts, since we care about the cosine similarity. + self.train_activations_lsh[layer] /= np.linalg.norm( + self.train_activations_lsh[layer], axis=1 + ).reshape(-1, 1) + + # Center the dataset and the queries: this improves the performance of LSH quite a bit. + center = np.mean(self.train_activations_lsh[layer], axis=0) + self.train_activations_lsh[layer] -= center + self.centers[layer] = center + + print("Constructing the NearestNeighbor table") + self.query_objects[layer] = NearestNeighbor( + backend=FLAGS.nearest_neighbor_backend, + dimension=self.train_activations_lsh[layer].shape[1], + number_bits=self.number_bits, + neighbors=self.neighbors, + nb_tables=self.nb_tables, + ) + + self.query_objects[layer].add(self.train_activations_lsh[layer]) + + def find_train_knns(self, data_activations): + """ + Given a data_activation dictionary that contains a np array with activations for each layer, + find the knns in the training data. + """ + knns_ind = {} + knns_labels = {} + + for layer in self.layers: + # Pre-process representations of data to normalize and remove training data mean. + data_activations_layer = copy.copy(data_activations[layer]) + nb_data = data_activations_layer.shape[0] + data_activations_layer /= np.linalg.norm( + data_activations_layer, axis=1 + ).reshape(-1, 1) + data_activations_layer -= self.centers[layer] + + # Use FALCONN to find indices of nearest neighbors in training data. + knns_ind[layer] = np.zeros( + (data_activations_layer.shape[0], self.neighbors), dtype=np.int32 + ) + knn_errors = 0 + + knn_missing_indices = self.query_objects[layer].find_knns( + data_activations_layer, + knns_ind[layer], + ) + + knn_errors += knn_missing_indices.flatten().sum() + + # Find labels of neighbors found in the training data. + knns_labels[layer] = np.zeros((nb_data, self.neighbors), dtype=np.int32) + + knns_labels[layer].reshape(-1)[ + np.logical_not(knn_missing_indices.flatten()) + ] = self.train_labels[ + knns_ind[layer].reshape(-1)[ + np.logical_not(knn_missing_indices.flatten()) + ] + ] + + return knns_ind, knns_labels + + def nonconformity(self, knns_labels): + """ + Given an dictionary of nb_data x nb_classes dimension, compute the nonconformity of + each candidate label for each data point: i.e. the number of knns whose label is + different from the candidate label. + """ + nb_data = knns_labels[self.layers[0]].shape[0] + knns_not_in_class = np.zeros((nb_data, self.nb_classes), dtype=np.int32) + for i in range(nb_data): + # Compute number of nearest neighbors per class + knns_in_class = np.zeros( + (len(self.layers), self.nb_classes), dtype=np.int32 + ) + for layer_id, layer in enumerate(self.layers): + knns_in_class[layer_id, :] = np.bincount( + knns_labels[layer][i], minlength=self.nb_classes + ) + + # Compute number of knns in other class than class_id + for class_id in range(self.nb_classes): + knns_not_in_class[i, class_id] = np.sum(knns_in_class) - np.sum( + knns_in_class[:, class_id] + ) + return knns_not_in_class + + def preds_conf_cred(self, knns_not_in_class): + """ + Given an array of nb_data x nb_classes dimensions, use conformal prediction to compute + the DkNN's prediction, confidence and credibility. + """ + nb_data = knns_not_in_class.shape[0] + preds_knn = np.zeros(nb_data, dtype=np.int32) + confs = np.zeros((nb_data, self.nb_classes), dtype=np.float32) + creds = np.zeros((nb_data, self.nb_classes), dtype=np.float32) + + for i in range(nb_data): + # p-value of test input for each class + p_value = np.zeros(self.nb_classes, dtype=np.float32) + + for class_id in range(self.nb_classes): + # p-value of (test point, candidate label) + p_value[class_id] = ( + float(self.nb_cali) + - bisect_left( + self.cali_nonconformity, knns_not_in_class[i, class_id] + ) + ) / float(self.nb_cali) + + preds_knn[i] = np.argmax(p_value) + confs[i, preds_knn[i]] = 1.0 - np.sort(p_value)[-2] + creds[i, preds_knn[i]] = p_value[preds_knn[i]] + return preds_knn, confs, creds + + def fprop_np(self, data_np): + """ + Performs a forward pass through the DkNN on an numpy array of data. + """ + if not self.calibrated: + raise ValueError( + "DkNN needs to be calibrated by calling DkNNModel.calibrate method once before inferring." + ) + data_activations = self.get_activations(data_np) + _, knns_labels = self.find_train_knns(data_activations) + knns_not_in_class = self.nonconformity(knns_labels) + _, _, creds = self.preds_conf_cred(knns_not_in_class) + return creds + + def fprop(self, x): + """ + Performs a forward pass through the DkNN on a TF tensor by wrapping + the fprop_np method. + """ + logits = tf.py_func(self.fprop_np, [x], tf.float32) + return {self.O_LOGITS: logits} + + def calibrate(self, cali_data, cali_labels): + """ + Runs the DkNN on holdout data to calibrate the credibility metric. + :param cali_data: np array of calibration data. + :param cali_labels: np vector of calibration labels. + """ + self.nb_cali = cali_labels.shape[0] + self.cali_activations = self.get_activations(cali_data) + self.cali_labels = cali_labels + + print("Starting calibration of DkNN.") + cali_knns_ind, cali_knns_labels = self.find_train_knns(self.cali_activations) + assert all( + [v.shape == (self.nb_cali, self.neighbors) for v in cali_knns_ind.values()] + ) + assert all( + [ + v.shape == (self.nb_cali, self.neighbors) + for v in cali_knns_labels.values() + ] + ) + + cali_knns_not_in_class = self.nonconformity(cali_knns_labels) + cali_knns_not_in_l = np.zeros(self.nb_cali, dtype=np.int32) + for i in range(self.nb_cali): + cali_knns_not_in_l[i] = cali_knns_not_in_class[i, cali_labels[i]] + cali_knns_not_in_l_sorted = np.sort(cali_knns_not_in_l) + self.cali_nonconformity = np.trim_zeros(cali_knns_not_in_l_sorted, trim="f") + self.nb_cali = self.cali_nonconformity.shape[0] + self.calibrated = True + print("DkNN calibration complete.") + + +def plot_reliability_diagram(confidence, labels, filepath): + """ + Takes in confidence values for predictions and correct + labels for the data, plots a reliability diagram. + :param confidence: nb_samples x nb_classes (e.g., output of softmax) + :param labels: vector of nb_samples + :param filepath: where to save the diagram + :return: + """ + assert len(confidence.shape) == 2 + assert len(labels.shape) == 1 + assert confidence.shape[0] == labels.shape[0] + print("Saving reliability diagram at: " + str(filepath)) + if confidence.max() <= 1.0: + # confidence array is output of softmax + bins_start = [b / 10.0 for b in xrange(0, 10)] + bins_end = [b / 10.0 for b in xrange(1, 11)] + bins_center = [(b + 0.5) / 10.0 for b in xrange(0, 10)] + preds_conf = np.max(confidence, axis=1) + preds_l = np.argmax(confidence, axis=1) + else: + raise ValueError("Confidence values go above 1.") + + print(preds_conf.shape, preds_l.shape) + + # Create var for reliability diagram + # Will contain mean accuracies for each bin + reliability_diag = [] + num_points = [] # keeps the number of points in each bar + + # Find average accuracy per confidence bin + for bin_start, bin_end in zip(bins_start, bins_end): + above = preds_conf >= bin_start + if bin_end == 1.0: + below = preds_conf <= bin_end + else: + below = preds_conf < bin_end + mask = np.multiply(above, below) + num_points.append(np.sum(mask)) + bin_mean_acc = max(0, np.mean(preds_l[mask] == labels[mask])) + reliability_diag.append(bin_mean_acc) + + # Plot diagram + assert len(reliability_diag) == len(bins_center) + print(reliability_diag) + print(bins_center) + print(num_points) + fig, ax1 = plt.subplots() + _ = ax1.bar(bins_center, reliability_diag, width=0.1, alpha=0.8) + plt.xlim([0, 1.0]) + ax1.set_ylim([0, 1.0]) + + ax2 = ax1.twinx() + print(sum(num_points)) + ax2.plot(bins_center, num_points, color="r", linestyle="-", linewidth=7.0) + ax2.set_ylabel("Number of points in the data", fontsize=16, color="r") + + if len(np.argwhere(confidence[0] != 0.0)) == 1: + # This is a DkNN diagram + ax1.set_xlabel("Prediction Credibility", fontsize=16) + else: + # This is a softmax diagram + ax1.set_xlabel("Prediction Confidence", fontsize=16) + ax1.set_ylabel("Prediction Accuracy", fontsize=16) + ax1.tick_params(axis="both", labelsize=14) + ax2.tick_params(axis="both", labelsize=14, colors="r") + fig.tight_layout() + plt.savefig(filepath, bbox_inches="tight") + + +def get_tensorflow_session(): + gpu_options = tf.GPUOptions() + gpu_options.per_process_gpu_memory_fraction = FLAGS.tensorflow_gpu_memory_fraction + sess = tf.Session(config=tf.ConfigProto(gpu_options=gpu_options)) + + return sess + + +def dknn_tutorial(): + # Get MNIST data. + mnist = MNIST() + x_train, y_train = mnist.get_set("train") + x_test, y_test = mnist.get_set("test") + + # Use Image Parameters. + img_rows, img_cols, nchannels = x_train.shape[1:4] + nb_classes = y_train.shape[1] + + with get_tensorflow_session() as sess: + with tf.variable_scope("dknn"): + # Define input TF placeholder. + x = tf.placeholder(tf.float32, shape=(None, img_rows, img_cols, nchannels)) + y = tf.placeholder(tf.float32, shape=(None, nb_classes)) + + # Define a model. + model = make_basic_picklable_cnn() + preds = model.get_logits(x) + loss = CrossEntropy(model, smoothing=0.0) + + # Define the test set accuracy evaluation. + def evaluate(): + acc = model_eval( + sess, + x, + y, + preds, + x_test, + y_test, + args={"batch_size": FLAGS.batch_size}, + ) + print("Test accuracy on test examples: %0.4f" % acc) + + # Train the model + train_params = { + "nb_epochs": FLAGS.nb_epochs, + "batch_size": FLAGS.batch_size, + "learning_rate": FLAGS.lr, + } + train( + sess, + loss, + x_train, + y_train, + evaluate=evaluate, + args=train_params, + var_list=model.get_params(), + ) + + # Define callable that returns a dictionary of all activations for a dataset + def get_activations(data): + data_activations = {} + for layer in layers: + layer_sym = tf.layers.flatten(model.get_layer(x, layer)) + data_activations[layer] = batch_eval( + sess, + [x], + [layer_sym], + [data], + args={"batch_size": FLAGS.batch_size}, + )[0] + return data_activations + + # Use a holdout of the test set to simulate calibration data for the DkNN. + train_data = x_train + train_labels = np.argmax(y_train, axis=1) + cali_data = x_test[: FLAGS.nb_cali] + y_cali = y_test[: FLAGS.nb_cali] + cali_labels = np.argmax(y_cali, axis=1) + test_data = x_test[FLAGS.nb_cali :] + y_test = y_test[FLAGS.nb_cali :] + + # Extract representations for the training and calibration data at each layer of interest to the DkNN. + layers = ["ReLU1", "ReLU3", "ReLU5", "logits"] + + # Wrap the model into a DkNNModel + dknn = DkNNModel( + FLAGS.neighbors, + layers, + get_activations, + train_data, + train_labels, + nb_classes, + scope="dknn", + number_bits=FLAGS.number_bits, + ) + dknn.calibrate(cali_data, cali_labels) + + # Generate adversarial examples + fgsm = FastGradientMethod(model, sess=sess) + attack_params = {"eps": 0.25, "clip_min": 0.0, "clip_max": 1.0} + adv = sess.run(fgsm.generate(x, **attack_params), feed_dict={x: test_data}) + + # Test the DkNN on clean test data and FGSM test data + for data_in, fname in zip([test_data, adv], ["test", "adv"]): + dknn_preds = dknn.fprop_np(data_in) + print(dknn_preds.shape) + print( + np.mean(np.argmax(dknn_preds, axis=1) == np.argmax(y_test, axis=1)) + ) + plot_reliability_diagram( + dknn_preds, np.argmax(y_test, axis=1), "/tmp/dknn_" + fname + ".pdf" + ) + + return True + + +def main(argv=None): + assert dknn_tutorial() + + +if __name__ == "__main__": + tf.flags.DEFINE_integer("number_bits", 17, "number of hash bits used by LSH Index") + tf.flags.DEFINE_float( + "tensorflow_gpu_memory_fraction", + 0.25, + "amount of the GPU memory to allocate for a tensorflow Session", + ) + tf.flags.DEFINE_enum_class( + "nearest_neighbor_backend", + NearestNeighbor.BACKEND.FALCONN, + NearestNeighbor.BACKEND, + "NearestNeighbor backend", + ) + tf.flags.DEFINE_integer("nb_epochs", 6, "Number of epochs to train model") + tf.flags.DEFINE_integer("batch_size", 500, "Size of training batches") + tf.flags.DEFINE_float("lr", 0.001, "Learning rate for training") + + tf.flags.DEFINE_integer("nb_cali", 750, "Number of calibration points for the DkNN") + tf.flags.DEFINE_integer( + "neighbors", 75, "Number of neighbors per layer for the DkNN" + ) + + tf.app.run() diff --git a/cleverhans/model_zoo/madry_lab_challenges/__init__.py b/cleverhans_v3.1.0/cleverhans/model_zoo/madry_lab_challenges/__init__.py similarity index 100% rename from cleverhans/model_zoo/madry_lab_challenges/__init__.py rename to cleverhans_v3.1.0/cleverhans/model_zoo/madry_lab_challenges/__init__.py diff --git a/cleverhans_v3.1.0/cleverhans/model_zoo/madry_lab_challenges/cifar10_model.py b/cleverhans_v3.1.0/cleverhans/model_zoo/madry_lab_challenges/cifar10_model.py new file mode 100644 index 000000000..434990168 --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans/model_zoo/madry_lab_challenges/cifar10_model.py @@ -0,0 +1,357 @@ +"""cleverhans.model.Model implementation of cifar10_challenge.model.Model + +This re-implementation factors variable creation apart from forward +propagation so it is possible to run forward propagation more than once +in the same model. + +based on https://github.com/tensorflow/models/tree/master/resnet +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import numpy as np +import tensorflow as tf +from cleverhans.serial import NoRefModel + + +class Layer(object): + def get_output_shape(self): + return self.output_shape + + +class ResNet(NoRefModel): + """ResNet model.""" + + def __init__(self, layers, input_shape, scope=None): + """ResNet constructor. + + :param layers: a list of layers in CleverHans format + each with set_input_shape() and fprop() methods. + :param input_shape: 4-tuple describing input shape (e.g None, 32, 32, 3) + :param scope: string name of scope for Variables + This works in two ways. + If scope is None, the variables are not put in a scope, and the + model is compatible with Saver.restore from the public downloads + for the CIFAR10 Challenge. + If the scope is a string, then Saver.restore won't work, but the + model functions as a picklable NoRefModels that finds its variables + based on the scope. + """ + super(ResNet, self).__init__(scope, 10, {}, scope is not None) + if scope is None: + before = list(tf.trainable_variables()) + before_vars = list(tf.global_variables()) + self.build(layers, input_shape) + after = list(tf.trainable_variables()) + after_vars = list(tf.global_variables()) + self.params = [param for param in after if param not in before] + self.vars = [var for var in after_vars if var not in before_vars] + else: + with tf.variable_scope(self.scope): + self.build(layers, input_shape) + + def get_vars(self): + if hasattr(self, "vars"): + return self.vars + return super(ResNet, self).get_vars() + + def build(self, layers, input_shape): + self.layer_names = [] + self.layers = layers + self.input_shape = input_shape + if isinstance(layers[-1], Softmax): + layers[-1].name = "probs" + layers[-2].name = "logits" + else: + layers[-1].name = "logits" + for i, layer in enumerate(self.layers): + if hasattr(layer, "name"): + name = layer.name + else: + name = layer.__class__.__name__ + str(i) + layer.name = name + self.layer_names.append(name) + + layer.set_input_shape(input_shape) + input_shape = layer.get_output_shape() + + def make_input_placeholder(self): + return tf.placeholder(tf.float32, (None, 32, 32, 3)) + + def make_label_placeholder(self): + return tf.placeholder(tf.float32, (None, 10)) + + def fprop(self, x, set_ref=False): + if self.scope is not None: + with tf.variable_scope(self.scope, reuse=tf.AUTO_REUSE): + return self._fprop(x, set_ref) + return self._prop(x, set_ref) + + def _fprop(self, x, set_ref=False): + states = [] + for layer in self.layers: + if set_ref: + layer.ref = x + x = layer.fprop(x) + assert x is not None + states.append(x) + states = dict(zip(self.layer_names, states)) + return states + + def add_internal_summaries(self): + pass + + +def _stride_arr(stride): + """Map a stride scalar to the stride array for tf.nn.conv2d.""" + return [1, stride, stride, 1] + + +class Input(Layer): + def __init__(self): + pass + + def set_input_shape(self, input_shape): + batch_size, rows, cols, input_channels = input_shape + # assert self.mode == 'train' or self.mode == 'eval' + """Build the core model within the graph.""" + input_shape = list(input_shape) + input_shape[0] = 1 + dummy_batch = tf.zeros(input_shape) + dummy_output = self.fprop(dummy_batch) + output_shape = [int(e) for e in dummy_output.get_shape()] + output_shape[0] = batch_size + self.output_shape = tuple(output_shape) + + def fprop(self, x): + with tf.variable_scope("input", reuse=tf.AUTO_REUSE): + input_standardized = tf.map_fn( + lambda img: tf.image.per_image_standardization(img), x + ) + return _conv("init_conv", input_standardized, 3, 3, 16, _stride_arr(1)) + + +class Conv2D(Layer): + def __init__(self): + pass + + def set_input_shape(self, input_shape): + batch_size, rows, cols, input_channels = input_shape + + # Uncomment the following codes to use w28-10 wide residual network. + # It is more memory efficient than very deep residual network and has + # comparably good performance. + # https://arxiv.org/pdf/1605.07146v1.pdf + input_shape = list(input_shape) + input_shape[0] = 1 + dummy_batch = tf.zeros(input_shape) + dummy_output = self.fprop(dummy_batch) + output_shape = [int(e) for e in dummy_output.get_shape()] + output_shape[0] = batch_size + self.output_shape = tuple(output_shape) + + def fprop(self, x): + + # Update hps.num_residual_units to 9 + strides = [1, 2, 2] + activate_before_residual = [True, False, False] + filters = [16, 160, 320, 640] + res_func = _residual + with tf.variable_scope("unit_1_0", reuse=tf.AUTO_REUSE): + x = res_func( + x, + filters[0], + filters[1], + _stride_arr(strides[0]), + activate_before_residual[0], + ) + for i in range(1, 5): + with tf.variable_scope(("unit_1_%d" % i), reuse=tf.AUTO_REUSE): + x = res_func(x, filters[1], filters[1], _stride_arr(1), False) + + with tf.variable_scope(("unit_2_0"), reuse=tf.AUTO_REUSE): + x = res_func( + x, + filters[1], + filters[2], + _stride_arr(strides[1]), + activate_before_residual[1], + ) + for i in range(1, 5): + with tf.variable_scope(("unit_2_%d" % i), reuse=tf.AUTO_REUSE): + x = res_func(x, filters[2], filters[2], _stride_arr(1), False) + + with tf.variable_scope(("unit_3_0"), reuse=tf.AUTO_REUSE): + x = res_func( + x, + filters[2], + filters[3], + _stride_arr(strides[2]), + activate_before_residual[2], + ) + for i in range(1, 5): + with tf.variable_scope(("unit_3_%d" % i), reuse=tf.AUTO_REUSE): + x = res_func(x, filters[3], filters[3], _stride_arr(1), False) + + with tf.variable_scope(("unit_last"), reuse=tf.AUTO_REUSE): + x = _batch_norm("final_bn", x) + x = _relu(x, 0.1) + x = _global_avg_pool(x) + + return x + + +class Linear(Layer): + def __init__(self, num_hid): + self.num_hid = num_hid + + def set_input_shape(self, input_shape): + batch_size, dim = input_shape + self.input_shape = [batch_size, dim] + self.dim = dim + self.output_shape = [batch_size, self.num_hid] + self.make_vars() + + def make_vars(self): + with tf.variable_scope("logit", reuse=tf.AUTO_REUSE): + w = tf.get_variable( + "DW", + [self.dim, self.num_hid], + initializer=tf.initializers.variance_scaling(distribution="uniform"), + ) + b = tf.get_variable( + "biases", [self.num_hid], initializer=tf.initializers.constant() + ) + return w, b + + def fprop(self, x): + w, b = self.make_vars() + return tf.nn.xw_plus_b(x, w, b) + + +def _batch_norm(name, x): + """Batch normalization.""" + with tf.name_scope(name): + return tf.contrib.layers.batch_norm( + inputs=x, + decay=0.9, + center=True, + scale=True, + activation_fn=None, + updates_collections=None, + is_training=False, + ) + + +def _residual(x, in_filter, out_filter, stride, activate_before_residual=False): + """Residual unit with 2 sub layers.""" + if activate_before_residual: + with tf.variable_scope("shared_activation"): + x = _batch_norm("init_bn", x) + x = _relu(x, 0.1) + orig_x = x + else: + with tf.variable_scope("residual_only_activation"): + orig_x = x + x = _batch_norm("init_bn", x) + x = _relu(x, 0.1) + + with tf.variable_scope("sub1"): + x = _conv("conv1", x, 3, in_filter, out_filter, stride) + + with tf.variable_scope("sub2"): + x = _batch_norm("bn2", x) + x = _relu(x, 0.1) + x = _conv("conv2", x, 3, out_filter, out_filter, [1, 1, 1, 1]) + + with tf.variable_scope("sub_add"): + if in_filter != out_filter: + orig_x = tf.nn.avg_pool(orig_x, stride, stride, "VALID") + orig_x = tf.pad( + orig_x, + [ + [0, 0], + [0, 0], + [0, 0], + [(out_filter - in_filter) // 2, (out_filter - in_filter) // 2], + ], + ) + x += orig_x + + tf.logging.debug("image after unit %s", x.get_shape()) + return x + + +def _decay(): + """L2 weight decay loss.""" + costs = [] + for var in tf.trainable_variables(): + if var.op.name.find("DW") > 0: + costs.append(tf.nn.l2_loss(var)) + return tf.add_n(costs) + + +def _conv(name, x, filter_size, in_filters, out_filters, strides): + """Convolution.""" + with tf.variable_scope(name, reuse=tf.AUTO_REUSE): + n = filter_size * filter_size * out_filters + kernel = tf.get_variable( + "DW", + [filter_size, filter_size, in_filters, out_filters], + tf.float32, + initializer=tf.random_normal_initializer(stddev=np.sqrt(2.0 / n)), + ) + return tf.nn.conv2d(x, kernel, strides, padding="SAME") + + +def _relu(x, leakiness=0.0): + """Relu, with optional leaky support.""" + return tf.where(tf.less(x, 0.0), leakiness * x, x, name="leaky_relu") + + +def _global_avg_pool(x): + assert x.get_shape().ndims == 4 + return tf.reduce_mean(x, [1, 2]) + + +class Softmax(Layer): + def __init__(self): + pass + + def set_input_shape(self, shape): + self.input_shape = shape + self.output_shape = shape + + def fprop(self, x): + return tf.nn.softmax(x) + + +class Flatten(Layer): + def __init__(self): + pass + + def set_input_shape(self, shape): + self.input_shape = shape + output_width = 1 + for factor in shape[1:]: + output_width *= factor + self.output_width = output_width + self.output_shape = [None, output_width] + + def fprop(self, x): + return tf.reshape(x, [-1, self.output_width]) + + +def make_wresnet(nb_classes=10, input_shape=(None, 32, 32, 3), scope=None): + layers = [ + Input(), + Conv2D(), # the whole ResNet is basically created in this layer + Flatten(), + Linear(nb_classes), + Softmax(), + ] + + model = ResNet(layers, input_shape, scope) + return model diff --git a/cleverhans_v3.1.0/cleverhans/model_zoo/madry_lab_challenges/make_cifar10_joblib.py b/cleverhans_v3.1.0/cleverhans/model_zoo/madry_lab_challenges/make_cifar10_joblib.py new file mode 100644 index 000000000..5760177d7 --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans/model_zoo/madry_lab_challenges/make_cifar10_joblib.py @@ -0,0 +1,74 @@ +"""Makes a .joblib file containing the trained model + +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import os +import sys +import time +import numpy as np +import logging + +import tensorflow as tf +from tensorflow.python.platform import app, flags +from cleverhans.utils import set_log_level, to_categorical, safe_zip +from cleverhans.utils_tf import model_eval +from cleverhans import serial +from cleverhans.dataset import CIFAR10, Factory +from cleverhans.model_zoo.madry_lab_challenges.cifar10_model import make_wresnet + + +FLAGS = flags.FLAGS + + +def main(argv): + + model_file = tf.train.latest_checkpoint(FLAGS.checkpoint_dir) + + if model_file is None: + print("No model found") + sys.exit() + + set_log_level(logging.DEBUG) + + sess = tf.Session() + with sess.as_default(): + + model = make_wresnet() + saver = tf.train.Saver() + # Restore the checkpoint + saver.restore(sess, model_file) + SCOPE = "cifar10_challenge" + model2 = make_wresnet(scope=SCOPE) + assert len(model.get_vars()) == len(model2.get_vars()) + found = [False] * len(model2.get_vars()) + for var1 in model.get_vars(): + var1_found = False + var2_name = SCOPE + "/" + var1.name + for idx, var2 in enumerate(model2.get_vars()): + if var2.name == var2_name: + var1_found = True + found[idx] = True + sess.run(tf.assign(var2, var1)) + break + assert var1_found, var1.name + assert all(found) + + model2.dataset_factory = Factory(CIFAR10, {"max_val": 255}) + + serial.save("model.joblib", model2) + + +if __name__ == "__main__": + + cifar10_root = os.environ["CIFAR10_CHALLENGE_DIR"] + default_ckpt_dir = os.path.join(cifar10_root, "models/model_0") + + flags.DEFINE_string( + "checkpoint_dir", default_ckpt_dir, "Checkpoint directory to load" + ) + + app.run(main) diff --git a/cleverhans_v3.1.0/cleverhans/model_zoo/soft_nearest_neighbor_loss/SNNL_regularized_model.py b/cleverhans_v3.1.0/cleverhans/model_zoo/soft_nearest_neighbor_loss/SNNL_regularized_model.py new file mode 100644 index 000000000..e192584ea --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans/model_zoo/soft_nearest_neighbor_loss/SNNL_regularized_model.py @@ -0,0 +1,62 @@ +""" +A Simple Neural Net to optimize with SNNL and Cross Entropy +""" +# pylint: disable=missing-docstring +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import functools + +import tensorflow as tf + +from cleverhans import initializers +from cleverhans.model import Model + + +class ModelBasicCNN(Model): + def __init__(self, scope, nb_classes, nb_filters, **kwargs): + del kwargs + Model.__init__(self, scope, nb_classes, locals()) + self.nb_filters = nb_filters + + self.fprop(self.make_input_placeholder()) + + self.params = self.get_params() + + def make_input_placeholder(self): + return tf.placeholder(tf.float32, [128, 28, 28, 1]) + + def get_layer_names(self): + return ["conv1", "conv2", "conv3", "logits"] + + def fprop(self, x, **kwargs): + del kwargs + my_conv = functools.partial( + tf.layers.conv2d, + activation=tf.nn.relu, + kernel_initializer=initializers.HeReLuNormalInitializer, + ) + with tf.variable_scope(self.scope, reuse=tf.AUTO_REUSE): + conv1 = my_conv(x, self.nb_filters, 8, strides=2, padding="same") + conv2 = my_conv(conv1, 2 * self.nb_filters, 6, strides=2, padding="valid") + conv3 = my_conv( + conv2, + 2 * self.nb_filters, + 5, + strides=1, + padding="valid", + ) + logits = tf.layers.dense( + tf.layers.flatten(conv3), + self.nb_classes, + kernel_initializer=initializers.HeReLuNormalInitializer, + ) + return { + self.O_LOGITS: logits, + self.O_PROBS: tf.nn.softmax(logits=logits), + "conv1": conv1, + "conv2": conv2, + "conv3": conv3, + } diff --git a/cleverhans_v3.1.0/cleverhans/model_zoo/soft_nearest_neighbor_loss/SNNL_regularized_train.py b/cleverhans_v3.1.0/cleverhans/model_zoo/soft_nearest_neighbor_loss/SNNL_regularized_train.py new file mode 100644 index 000000000..452778456 --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans/model_zoo/soft_nearest_neighbor_loss/SNNL_regularized_train.py @@ -0,0 +1,196 @@ +""" +This model shows how to train a model with Soft Nearest Neighbor Loss +regularization. The paper which presents this method can be found at +https://arxiv.org/abs/1902.01889 +""" +# pylint: disable=missing-docstring +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import logging +import numpy as np +import tensorflow as tf +import matplotlib.pyplot as plt +from matplotlib.offsetbox import OffsetImage, AnnotationBbox +from sklearn.manifold import TSNE + +from cleverhans.compat import flags +from cleverhans.loss import SNNLCrossEntropy, CrossEntropy +from cleverhans.dataset import MNIST +from cleverhans.utils_tf import model_eval +from cleverhans.train import train +from cleverhans.utils import AccuracyReport, set_log_level +from cleverhans.model_zoo.soft_nearest_neighbor_loss.SNNL_regularized_model import ( + ModelBasicCNN, +) + +FLAGS = flags.FLAGS + +NB_EPOCHS = 6 +BATCH_SIZE = 128 +LEARNING_RATE = 0.001 +NB_FILTERS = 64 +SNNL_FACTOR = -10.0 +OUTPUT_DIR = "/tmp/" + + +def SNNL_example( + train_start=0, + train_end=60000, + test_start=0, + test_end=10000, + nb_epochs=NB_EPOCHS, + batch_size=BATCH_SIZE, + learning_rate=LEARNING_RATE, + nb_filters=NB_FILTERS, + SNNL_factor=SNNL_FACTOR, + output_dir=OUTPUT_DIR, +): + """ + A simple model trained to minimize Cross Entropy and Maximize Soft Nearest + Neighbor Loss at each internal layer. This outputs a TSNE of the sign of + the adversarial gradients of a trained model. A model with a negative + SNNL_factor will show little or no class clusters, while a model with a + 0 SNNL_factor will have class clusters in the adversarial gradient direction. + :param train_start: index of first training set example + :param train_end: index of last training set example + :param test_start: index of first test set example + :param test_end: index of last test set example + :param nb_epochs: number of epochs to train model + :param batch_size: size of training batches + :param learning_rate: learning rate for training + :param SNNL_factor: multiplier for Soft Nearest Neighbor Loss + :return: an AccuracyReport object + """ + + # Object used to keep track of (and return) key accuracies + report = AccuracyReport() + + # Set TF random seed to improve reproducibility + tf.set_random_seed(1234) + + # Set logging level to see debug information + set_log_level(logging.DEBUG) + + # Create TF session + sess = tf.Session() + + # Get MNIST data + mnist = MNIST( + train_start=train_start, + train_end=train_end, + test_start=test_start, + test_end=test_end, + ) + x_train, y_train = mnist.get_set("train") + x_test, y_test = mnist.get_set("test") + + # Use Image Parameters + img_rows, img_cols, nchannels = x_train.shape[1:4] + nb_classes = y_train.shape[1] + + # Define input TF placeholder + x = tf.placeholder(tf.float32, shape=(None, img_rows, img_cols, nchannels)) + y = tf.placeholder(tf.float32, shape=(None, nb_classes)) + + # Train an MNIST model + train_params = { + "nb_epochs": nb_epochs, + "batch_size": batch_size, + "learning_rate": learning_rate, + } + eval_params = {"batch_size": batch_size} + rng = np.random.RandomState([2017, 8, 30]) + + def do_eval(preds, x_set, y_set, report_key): + acc = model_eval(sess, x, y, preds, x_set, y_set, args=eval_params) + setattr(report, report_key, acc) + print("Test accuracy on legitimate examples: %0.4f" % (acc)) + + model = ModelBasicCNN("model", nb_classes, nb_filters) + preds = model.get_logits(x) + cross_entropy_loss = CrossEntropy(model) + if not SNNL_factor: + loss = cross_entropy_loss + else: + loss = SNNLCrossEntropy(model, factor=SNNL_factor, optimize_temperature=False) + + def evaluate(): + do_eval(preds, x_test, y_test, "clean_train_clean_eval") + + train( + sess, + loss, + x_train, + y_train, + evaluate=evaluate, + args=train_params, + rng=rng, + var_list=model.get_params(), + ) + + do_eval(preds, x_train, y_train, "train_clean_train_clean_eval") + + def imscatter(points, images, ax=None, zoom=1, cmap="hot"): + if ax is None: + ax = plt.gca() + artists = [] + i = 0 + if not isinstance(cmap, list): + cmap = [cmap] * len(points) + for x0, y0 in points: + transformed = (images[i] - np.min(images[i])) / ( + np.max(images[i]) - np.min(images[i]) + ) + im = OffsetImage(transformed[:, :, 0], zoom=zoom, cmap=cmap[i]) + ab = AnnotationBbox(im, (x0, y0), xycoords="data", frameon=False) + artists.append(ax.add_artist(ab)) + i += 1 + ax.update_datalim(np.column_stack(np.transpose(points))) + ax.autoscale() + ax.get_xaxis().set_ticks([]) + ax.get_yaxis().set_ticks([]) + return artists + + adv_grads = tf.sign(tf.gradients(cross_entropy_loss.fprop(x, y), x)) + feed_dict = {x: x_test[:batch_size], y: y_test[:batch_size]} + adv_grads_val = sess.run(adv_grads, feed_dict=feed_dict) + adv_grads_val = np.reshape(adv_grads_val, (batch_size, img_rows * img_cols)) + + X_embedded = TSNE(n_components=2, verbose=0).fit_transform(adv_grads_val) + plt.figure(num=None, figsize=(50, 50), dpi=40, facecolor="w", edgecolor="k") + plt.title( + "TSNE of Sign of Adv Gradients, SNNLCrossEntropy Model, factor:" + + str(FLAGS.SNNL_factor), + fontsize=42, + ) + imscatter(X_embedded, x_test[:batch_size], zoom=2, cmap="Purples") + plt.savefig( + output_dir + "adversarial_gradients_SNNL_factor_" + str(SNNL_factor) + ".png" + ) + + +def main(argv=None): + SNNL_example( + nb_epochs=FLAGS.nb_epochs, + batch_size=FLAGS.batch_size, + learning_rate=FLAGS.learning_rate, + nb_filters=FLAGS.nb_filters, + SNNL_factor=FLAGS.SNNL_factor, + output_dir=FLAGS.output_dir, + ) + + +if __name__ == "__main__": + flags.DEFINE_integer("nb_filters", NB_FILTERS, "Model size multiplier") + flags.DEFINE_integer("nb_epochs", NB_EPOCHS, "Number of epochs to train model") + flags.DEFINE_integer("batch_size", BATCH_SIZE, "Size of training batches") + flags.DEFINE_float( + "SNNL_factor", SNNL_FACTOR, "Multiplier for Soft Nearest Neighbor Loss" + ) + flags.DEFINE_float("learning_rate", LEARNING_RATE, "Learning rate for training") + flags.DEFINE_string("output_dir", OUTPUT_DIR, "output directory for saving figures") + + tf.app.run() diff --git a/cleverhans/model_zoo/soft_nearest_neighbor_loss/__init__.py b/cleverhans_v3.1.0/cleverhans/model_zoo/soft_nearest_neighbor_loss/__init__.py similarity index 100% rename from cleverhans/model_zoo/soft_nearest_neighbor_loss/__init__.py rename to cleverhans_v3.1.0/cleverhans/model_zoo/soft_nearest_neighbor_loss/__init__.py diff --git a/cleverhans_v3.1.0/cleverhans/picklable_model.py b/cleverhans_v3.1.0/cleverhans/picklable_model.py new file mode 100644 index 000000000..1298f0a6b --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans/picklable_model.py @@ -0,0 +1,893 @@ +"""Models that support pickling. + +NOTE: This module is much more free to change than many other modules +in CleverHans. CleverHans is very conservative about changes to any +code that affects the output of benchmark tests (attacks, evaluation +methods, etc.). This module provides *models* not *benchmarks* and +thus is free to change rapidly to provide better speed, accuracy, +etc. +""" +# pylint: disable=missing-docstring + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import tensorflow as tf +import numpy as np + +from cleverhans.compat import reduce_mean, reduce_prod +from cleverhans.model import Model +from cleverhans.serial import PicklableVariable as PV +from cleverhans.utils import ordered_union + + +class PicklableModel(Model): + """ + A Model that supports pickling. + + Subclasses of this model must use only PicklableVariable and must refer + to their variables only by referencing the Python objects, not using + TensorFlow names (so no variable scopes). Pickle cannot find variables + referenced only by name and thus may fail to save them. Pickle may not + be able to get the original name back when restoring the variable so the + names should not be relied on. + """ + + def __init__(self): + super(PicklableModel, self).__init__() + del self.scope # Must not use Variable scopes / names for anything + + def get_params(self): + raise NotImplementedError( + str(type(self)) + " does not implement" " get_params." + ) + + +class MLP(PicklableModel): + """ + A picklable multilayer perceptron + """ + + def __hash__(self): + return hash(id(self)) + + def __init__(self, layers, input_shape): + super(MLP, self).__init__() + + if not isinstance(layers, list): + raise ValueError("`layers` must be a list.") + + self.layer_names = [] + self.layers = layers + self.input_shape = input_shape + + if isinstance(layers[-1], Softmax): + if not hasattr(layers[-1], "name"): + layers[-1].name = "probs" + if not hasattr(layers[-2], "name"): + layers[-2].name = "logits" + else: + if not hasattr(layers[-1], "name"): + layers[-1].name = "logits" + for i, layer in enumerate(self.layers): + if layer.parent is None: + if i == 0: + layer.parent = "input" + else: + layer.parent = layers[i - 1].name + if hasattr(layer, "name"): + name = layer.name + else: + name = layer.__class__.__name__ + str(i) + layer.name = name + self.layer_names.append(name) + + layer.set_input_shape(input_shape) + input_shape = layer.get_output_shape() + + def get_params(self): + out = [] + for layer in self.layers: + out = ordered_union(out, layer.get_params()) + return out + + def fprop(self, x=None, given=None, **kwargs): + + # Note: this currently isn't great. + # A layer can have any parent it wants, but the parent + # must come earlier in the list. + # There's no way to have > 1 parent. + # This means we can support branched structures that split, + # e.g. for multiple output heads, but not structures + # that converge. + # We can feed a value in the middle using "given" but + # only layers after the given one are run using the current + # implementation, so the feed must happen before any branch + # point. + + if x is None: + if given is None: + raise ValueError("One of `x` or `given` must be specified") + else: + assert given is None + given = ("input", x) + name, value = given + out = {name: value} + x = value + + if name == "input": + layers = self.layers + else: + for i, layer in enumerate(self.layers[:-1]): + if layer.name == name: + layers = self.layers[i + 1 :] + break + + for layer in layers: + x = out[layer.parent] + try: + x = layer.fprop(x, **kwargs) + except TypeError as e: + msg = "TypeError in fprop for %s of type %s: %s" + msg = msg % (layer.name, str(type(layer)), str(e)) + raise TypeError(msg) + assert x is not None + out[layer.name] = x + return out + + def make_input_placeholder(self): + return tf.placeholder(tf.float32, tuple(self.input_shape)) + + def make_label_placeholder(self): + try: + return self.layers[-1].make_label_placeholder() + except NotImplementedError: + return tf.placeholder(tf.float32, self.layers[-1].get_output_shape()) + + +class Layer(PicklableModel): + def __init__(self, name=None, parent=None): + super(Layer, self).__init__() + if name is not None: + self.name = name + self.parent = parent + + def get_output_shape(self): + return self.output_shape + + +class Linear(Layer): + """ + Linear, fully connected layer. + :param init_mode: string + "norm" : the weight vector for each output is initialized to have + the same norm, given by `init_scale` + "uniform_unit_scaling" : U(-sqrt(3/input_dim), sqrt(3/input_dim)) + from https://arxiv.org/abs/1412.6558 + """ + + def __init__( + self, + num_hid, + init_scale=1.0, + init_b=0.0, + use_bias=True, + init_mode="norm", + **kwargs + ): + super(Linear, self).__init__(**kwargs) + self.num_hid = num_hid + self.init_scale = init_scale + self.init_b = init_b + self.use_bias = use_bias + self.init_mode = init_mode + + def set_input_shape(self, input_shape): + batch_size, dim = input_shape + self.input_shape = [batch_size, dim] + self.output_shape = [batch_size, self.num_hid] + if self.init_mode == "norm": + init = tf.random_normal([dim, self.num_hid], dtype=tf.float32) + init = init / tf.sqrt( + 1e-7 + tf.reduce_sum(tf.square(init), axis=0, keep_dims=True) + ) + init = init * self.init_scale + elif self.init_mode == "uniform_unit_scaling": + scale = np.sqrt(3.0 / dim) + init = tf.random_uniform( + [dim, self.num_hid], dtype=tf.float32, minval=-scale, maxval=scale + ) + else: + raise ValueError(self.init_mode) + self.W = PV(init) + if self.use_bias: + self.b = PV((np.zeros((self.num_hid,)) + self.init_b).astype("float32")) + + def fprop(self, x, **kwargs): + out = tf.matmul(x, self.W.var) + if self.use_bias: + out = out + self.b.var + return out + + def get_params(self): + out = [self.W.var] + if self.use_bias: + out.append(self.b.var) + return out + + +class Conv2D(Layer): + """ + 2-D Convolution. Uses NHWC format for input and output. + :param output_channels: int + The number of channels to output + :param kernel_shape: tuple of two ints + (kernel rows, kernel columns) + Do not include input channels or output channels in kernel_shape. + :param strides: tuple of two ints + (row stride, column stride) + Do not include channel or batch strides. + :param use_bias: bool + If True (default is False) adds a per-channel bias term to the output + :param init_mode: string + "norm" : each kernel is initialized to have the same norm, + given by `init_scale` + "inv_sqrt" : Gaussian with standard devation given by sqrt(2/fan_out) + "glorot_uniform" : U(+/- sqrt(6/(fan_in+fan_out)) + """ + + def __init__( + self, + output_channels, + kernel_shape, + strides, + padding, + use_bias=False, + init_scale=1.0, + init_mode="norm", + **kwargs + ): + self.__dict__.update(locals()) + del self.self + super(Conv2D, self).__init__(**kwargs) + + def set_input_shape(self, input_shape): + _batch_size, _rows, _cols, input_channels = input_shape + assert len(self.kernel_shape) == 2 + kernel_shape = tuple(self.kernel_shape) + (input_channels, self.output_channels) + assert len(kernel_shape) == 4 + assert all(isinstance(e, int) for e in kernel_shape), kernel_shape + fan_in = self.kernel_shape[0] * self.kernel_shape[1] * input_channels + fan_out = self.kernel_shape[0] * self.kernel_shape[1] * self.output_channels + if self.init_mode == "norm": + init = tf.random_normal(kernel_shape, dtype=tf.float32) + squared_norms = tf.reduce_sum(tf.square(init), axis=(0, 1, 2)) + denom = tf.sqrt(1e-7 + squared_norms) + init = self.init_scale * init / denom + elif self.init_mode == "inv_sqrt": + init = tf.random_normal( + kernel_shape, dtype=tf.float32, stddev=np.sqrt(2.0 / fan_out) + ) + elif self.init_mode == "glorot_uniform": + scale = np.sqrt(6.0 / (fan_in + fan_out)) + init = tf.random_uniform( + kernel_shape, dtype=tf.float32, minval=-scale, maxval=scale + ) + else: + raise ValueError(self.init_mode) + self.kernels = PV(init, name=self.name + "_kernels") + if self.use_bias: + self.b = PV(np.zeros((self.output_channels,)).astype("float32")) + input_shape = list(input_shape) + orig_batch_size = input_shape[0] + input_shape[0] = 1 + dummy_batch = tf.zeros(input_shape) + dummy_output = self.fprop(dummy_batch) + output_shape = [int(e) for e in dummy_output.get_shape()] + output_shape[0] = orig_batch_size + self.output_shape = tuple(output_shape) + + def fprop(self, x, **kwargs): + assert len(self.strides) == 2 + out = tf.nn.conv2d( + x, self.kernels.var, (1,) + tuple(self.strides) + (1,), self.padding + ) + if self.use_bias: + out = out + self.b.var + return out + + def get_params(self): + out = [self.kernels.var] + if self.use_bias: + out.append(self.b.var) + return out + + +class ReLU(Layer): + def __init__(self, leak=0.0, **kwargs): + super(ReLU, self).__init__(**kwargs) + self.leak = leak + + def set_input_shape(self, shape): + self.input_shape = shape + self.output_shape = shape + + def get_output_shape(self): + return self.output_shape + + def fprop(self, x, **kwargs): + out = tf.nn.relu(x) + if self.leak != 0.0: + # The code commented below resulted in the time per epoch of + # an 8-GPU wide resnet increasing by about 5% relative to the + # code now in use. + # The two different implementations have the same forward prop + # down to machine precision on all inputs I have tested, but + # sometimes have different derivatives. + # Both obtain about the same training accuracy but the faster + # version seems to also be slightly more accurate. + # The commented code and these performance notes are included to + # aid future revision efforts. + # + # out = out - self.leak * tf.nn.relu(-x) + # + + out = tf.where(tf.less(x, 0.0), self.leak * x, x) + return out + + def get_params(self): + return [] + + +class Sigmoid(Layer): + def __init__(self, **kwargs): + super(Sigmoid, self).__init__(**kwargs) + + def set_input_shape(self, shape): + self.input_shape = shape + self.output_shape = shape + + def get_output_shape(self): + return self.output_shape + + def fprop(self, x, **kwargs): + return tf.nn.sigmoid(x) + + def get_params(self): + return [] + + +class Tanh(Layer): + def __init__(self, **kwargs): + super(Tanh, self).__init__(**kwargs) + + def set_input_shape(self, shape): + self.input_shape = shape + self.output_shape = shape + + def get_output_shape(self): + return self.output_shape + + def fprop(self, x, **kwargs): + return tf.nn.tanh(x) + + def get_params(self): + return [] + + +class LeakyReLU(ReLU): + def __init__(self, leak=0.2, **kwargs): + super(LeakyReLU, self).__init__(leak=leak, **kwargs) + + +class ELU(Layer): + def set_input_shape(self, shape): + self.input_shape = shape + self.output_shape = shape + + def get_output_shape(self): + return self.output_shape + + def fprop(self, x, **kwargs): + return tf.nn.elu(x) + + def get_params(self): + return [] + + +class SELU(Layer): + def set_input_shape(self, shape): + self.input_shape = shape + self.output_shape = shape + + def get_output_shape(self): + return self.output_shape + + def fprop(self, x, **kwargs): + alpha = 1.6732632423543772848170429916717 + scale = 1.0507009873554804934193349852946 + mask = tf.to_float(x >= 0.0) + out = mask * x + (1.0 - mask) * (alpha * tf.exp((1.0 - mask) * x) - alpha) + return scale * out + + def get_params(self): + return [] + + +class TanH(Layer): + def set_input_shape(self, shape): + self.input_shape = shape + self.output_shape = shape + + def get_output_shape(self): + return self.output_shape + + def fprop(self, x, **kwargs): + return tf.nn.tanh(x) + + +class Softmax(Layer): + def set_input_shape(self, shape): + self.input_shape = shape + self.output_shape = shape + + def fprop(self, x, **kwargs): + out = tf.nn.softmax(x) + return out + + def get_params(self): + return [] + + def make_label_placeholder(self): + return tf.placeholder(tf.float32, self.output_shape) + + +class Flatten(Layer): + def set_input_shape(self, shape): + self.input_shape = shape + output_width = 1 + for factor in shape[1:]: + output_width *= factor + self.output_width = output_width + self.output_shape = [None, output_width] + + def fprop(self, x, **kwargs): + return tf.reshape(x, [-1, self.output_width]) + + def get_params(self): + return [] + + +class Print(Layer): + def set_input_shape(self, shape): + self.input_shape = shape + self.output_shape = shape + + def get_params(self): + return [] + + def fprop(self, x, **kwargs): + mean = tf.reduce_mean(x) + std = tf.sqrt(tf.reduce_mean(tf.square(x - mean))) + return tf.Print( + x, [tf.reduce_min(x), mean, tf.reduce_max(x), std], "Print layer" + ) + + +class Add(Layer): + """ + A Layer that adds a function to its input. + The function to add is specified in terms of multiple layers, just like + in the MLP class. + The Add layer is useful for implementing residual networks. + """ + + def __hash__(self): + return hash(id(self)) + + def set_input_shape(self, shape): + self.input_shape = shape + shapes = {"input": shape} + for layer in self.layers: + layer.set_input_shape(shapes[layer.parent]) + shapes[layer.name] = layer.get_output_shape() + self.output_shape = shapes[self.layers[-1].name] + + def __init__(self, layers): + super(Add, self).__init__() + + self.layer_names = [] + self.layers = layers + + for i, layer in enumerate(self.layers): + if layer.parent is None: + if i == 0: + layer.parent = "input" + else: + layer.parent = layers[i - 1].name + if hasattr(layer, "name"): + name = layer.name + else: + name = layer.__class__.__name__ + str(i) + layer.name = name + self.layer_names.append(name) + + def get_params(self): + out = [] + for layer in self.layers: + out = ordered_union(out, layer.get_params()) + return out + + def fprop(self, x, **kwargs): + + orig_x = x + + # Note: this currently isn't great. + # A layer can have any parent it wants, but the parent + # must come earlier in the list. + # There's no way to have > 1 parent. + # This means we can support branched structures that split, + # e.g. for multiple output heads, but not structures + # that converge. + # We can feed a value in the middle using "given" but + # only layers after the given one are run using the current + # implementation, so the feed must happen before any branch + # point. + + out = {"input": x} + + for layer in self.layers: + x = out[layer.parent] + try: + x = layer.fprop(x) + except TypeError as e: + msg = "TypeError in fprop for layer %s of type %s: %s" + msg = msg % (layer.name, str(type(layer)), str(e)) + raise TypeError(msg) + assert x is not None + out[layer.name] = x + + return orig_x + out[self.layers[-1].name] + + +class PerImageStandardize(Layer): + def set_input_shape(self, shape): + self.input_shape = shape + self.output_shape = shape + + def get_params(self): + return [] + + def fprop(self, x, **kwargs): + axis = [1, 2, 3] + mean = reduce_mean(x, axis=axis, keepdims=True) + variance = reduce_mean(tf.square(x), axis=axis, keepdims=True) - tf.square(mean) + variance = tf.nn.relu(variance) + stddev = tf.sqrt(variance) + + num_pixels = reduce_prod(tf.shape(x)[1:]) + + min_stddev = tf.rsqrt(tf.to_float(num_pixels)) + pixel_value_scale = tf.maximum(stddev, min_stddev) + pixel_value_offset = mean + + x = tf.subtract(x, pixel_value_offset) + x = tf.div(x, pixel_value_scale) + return x + + +class Dropout(Layer): + """Dropout layer. + + By default, is a no-op. Activate it during training using the kwargs + + The default use case is that you never specify include_prob anywhere. + During evaluation, you don't do anything special regarding dropout, + and nothing gets dropped. During training, you pass "dropout=True" + to make units get randomly dropped. + If you've done nothing else, include_prob defaults to 0.5 in the + Dropout class constructor. + + A slightly more advanced use case is that you want to use different + include_probs for some layer. For example, people usually use + include_prob=0.8 for the input layer. To do this, you specify + include_prob in the constructor arguments for those layers. + Other than that, it's the same as the basic use case case. You do + nothing special at test time and nothing is dropped. + You pass dropout=True at train time and units in each layer are dropped + based on their include_prob specified in their layer's constructor. + + The most advanced use case is if you want to change dropout include + probabilities for a specific fprop call. In this case, you pass + dropout=True and dropout_dict for that call. Each layer uses the + include_prob specified in the dropout_dict for that call.of MLP.fprop. + """ + + def __init__(self, include_prob=0.5, **kwargs): + super(Dropout, self).__init__(**kwargs) + self.include_prob = include_prob + + def set_input_shape(self, shape): + self.input_shape = shape + self.output_shape = shape + + def get_params(self): + return [] + + def fprop(self, x, dropout=False, dropout_dict=None, **kwargs): + """ + Forward propagation as either no-op or dropping random units. + :param x: The input to the layer + :param dropout: bool specifying whether to drop units + :param dropout_dict: dict + This dictionary is usually not needed. + In rare cases, generally for research purposes, this dictionary + makes it possible to run forward propagation with a different + dropout include probability. + This dictionary should be passed as a named argument to the MLP + class, which will then pass it to *all* layers' fprop methods. + Other layers will just receive this as an ignored kwargs entry. + Each dropout layer looks up its own name in this dictionary + to read out its include probability. + """ + include_prob = self.include_prob + if dropout_dict is not None: + assert dropout + if self.name in dropout_dict: + include_prob = dropout_dict[self.name] + if dropout: + return tf.nn.dropout(x, include_prob) + return x + + +class ResidualWithGroupNorm(Layer): + """A residual network layer that uses group normalization. + + :param out_filter: Number of output filters + :param stride: int + Stride for convolutional layers. Replicated to both row and column. + """ + + def __init__( + self, out_filter, stride, activate_before_residual=False, leak=0.1, **kwargs + ): + assert isinstance(stride, int) + self.__dict__.update(locals()) + del self.self + self.lrelu = LeakyReLU(leak) + super(ResidualWithGroupNorm, self).__init__(**kwargs) + + def set_input_shape(self, shape): + self.input_shape = tuple(shape) + self.in_filter = shape[-1] + self.gn1 = GroupNorm(name=self.name + "_gn1") + self.gn1.set_input_shape(shape) + strides = (self.stride, self.stride) + self.conv1 = Conv2D( + self.out_filter, + (3, 3), + strides, + "SAME", + name=self.name + "_conv1", + init_mode="inv_sqrt", + ) + self.conv1.set_input_shape(shape) + self.gn2 = GroupNorm(name=self.name + "_gn2") + self.gn2.set_input_shape(self.conv1.get_output_shape()) + self.conv2 = Conv2D( + self.out_filter, + (3, 3), + (1, 1), + "SAME", + name=self.name + "_conv2", + init_mode="inv_sqrt", + ) + self.conv2.set_input_shape(self.conv1.get_output_shape()) + self.output_shape = self.conv2.get_output_shape() + + def get_params(self): + sublayers = [self.conv1, self.conv2, self.gn1, self.gn2] + params = [] + for sublayer in sublayers: + params = params + sublayer.get_params() + assert self.conv1.kernels.var in params + return params + + def fprop(self, x, **kwargs): + if self.activate_before_residual: + x = self.gn1.fprop(x) + x = self.lrelu.fprop(x) + orig_x = x + else: + orig_x = x + x = self.gn1.fprop(x) + x = self.lrelu.fprop(x) + x = self.conv1.fprop(x) + x = self.gn2.fprop(x) + x = self.lrelu.fprop(x) + x = self.conv2.fprop(x) + if self.stride != 1: + stride = [1, self.stride, self.stride, 1] + orig_x = tf.nn.avg_pool(orig_x, stride, stride, "VALID") + out_filter = self.out_filter + in_filter = self.in_filter + if in_filter != out_filter: + orig_x = tf.pad( + orig_x, + [ + [0, 0], + [0, 0], + [0, 0], + [(out_filter - in_filter) // 2, (out_filter - in_filter) // 2], + ], + ) + x = x + orig_x + return x + + +class GlobalAveragePool(Layer): + def set_input_shape(self, shape): + self.input_shape = shape + self.output_shape = [shape[0], shape[-1]] + + def get_params(self): + return [] + + def fprop(self, x, **kwargs): + assert len(list(x.get_shape())) == 4 + return tf.reduce_mean(x, [1, 2]) + + +class GroupNorm(Layer): + """ + Group normalization + + https://arxiv.org/abs/1803.08494 + """ + + def __init__(self, num_groups=32, eps=1e-3, init_gamma=1.0, **kwargs): + self.num_groups = num_groups + self.eps = eps + self.init_gamma = init_gamma + super(GroupNorm, self).__init__(**kwargs) + + def set_input_shape(self, shape): + self.input_shape = shape + self.output_shape = shape + channels = shape[-1] + self.channels = channels + self.actual_num_groups = min(self.channels, self.num_groups) + extra_dims = (self.channels // self.actual_num_groups, self.actual_num_groups) + self.expanded_shape = tuple(shape[1:3]) + tuple(extra_dims) + init_value = np.ones((channels,), dtype="float32") * self.init_gamma + self.gamma = PV(init_value, name=self.name + "_gamma") + self.beta = PV( + np.zeros((self.channels,), dtype="float32"), name=self.name + "_beta" + ) + + def fprop(self, x, **kwargs): + shape = tf.shape(x) + batch_size = shape[0] + x = tf.reshape(x, (batch_size,) + self.expanded_shape) + mean, var = tf.nn.moments(x, [1, 2, 3], keep_dims=True) + x = (x - mean) * tf.rsqrt(var + self.eps) + x = tf.reshape(x, shape) + x = x * self.gamma.var + self.beta.var + return x + + def get_params(self): + return [self.gamma.var, self.beta.var] + + +class BatchNorm(Layer): + """ + This BatchNorm is always run in train mode for now + """ + + def __init__(self, eps=1e-3, init_gamma=1.0, **kwargs): + self.eps = eps + self.init_gamma = init_gamma + super(BatchNorm, self).__init__(**kwargs) + + def set_input_shape(self, shape): + self.input_shape = shape + self.output_shape = shape + channels = shape[-1] + init_value = np.ones((channels,), dtype="float32") * self.init_gamma + self.gamma = PV(init_value, name=self.name + "_gamma") + self.beta = PV(np.zeros((channels,), dtype="float32"), name=self.name + "_beta") + + def fprop(self, x, **kwargs): + mean, var = tf.nn.moments(x, [0, 1, 2], keep_dims=True) + x = (x - mean) * tf.rsqrt(var + self.eps) + x = x * self.gamma.var + self.beta.var + return x + + def get_params(self): + return [self.gamma.var, self.beta.var] + + +class ResidualWithBatchNorm(Layer): + """A residual network layer that uses batch normalization. + + :param out_filter: Number of output filters + :param stride: int + Stride for convolutional layers. Replicated to both row and column. + """ + + def __init__( + self, out_filter, stride, activate_before_residual=False, leak=0.1, **kwargs + ): + assert isinstance(stride, int) + self.__dict__.update(locals()) + del self.self + self.lrelu = LeakyReLU(leak) + super(ResidualWithBatchNorm, self).__init__(**kwargs) + + def set_input_shape(self, shape): + self.input_shape = tuple(shape) + self.in_filter = shape[-1] + self.bn1 = BatchNorm(name=self.name + "_bn1") + self.bn1.set_input_shape(shape) + strides = (self.stride, self.stride) + self.conv1 = Conv2D( + self.out_filter, + (3, 3), + strides, + "SAME", + name=self.name + "_conv1", + init_mode="inv_sqrt", + ) + self.conv1.set_input_shape(shape) + self.bn2 = BatchNorm(name=self.name + "_bn2") + self.bn2.set_input_shape(self.conv1.get_output_shape()) + self.conv2 = Conv2D( + self.out_filter, + (3, 3), + (1, 1), + "SAME", + name=self.name + "_conv2", + init_mode="inv_sqrt", + ) + self.conv2.set_input_shape(self.conv1.get_output_shape()) + self.output_shape = self.conv2.get_output_shape() + + def get_params(self): + sublayers = [self.conv1, self.conv2, self.bn1, self.bn2] + params = [] + for sublayer in sublayers: + params = params + sublayer.get_params() + assert self.conv1.kernels.var in params + return params + + def fprop(self, x, **kwargs): + if self.activate_before_residual: + x = self.bn1.fprop(x) + x = self.lrelu.fprop(x) + orig_x = x + else: + orig_x = x + x = self.bn1.fprop(x) + x = self.lrelu.fprop(x) + x = self.conv1.fprop(x) + x = self.bn2.fprop(x) + x = self.lrelu.fprop(x) + x = self.conv2.fprop(x) + if self.stride != 1: + stride = [1, self.stride, self.stride, 1] + orig_x = tf.nn.avg_pool(orig_x, stride, stride, "VALID") + out_filter = self.out_filter + in_filter = self.in_filter + if in_filter != out_filter: + orig_x = tf.pad( + orig_x, + [ + [0, 0], + [0, 0], + [0, 0], + [(out_filter - in_filter) // 2, (out_filter - in_filter) // 2], + ], + ) + x = x + orig_x + return x diff --git a/cleverhans_v3.1.0/cleverhans/plot/__init__.py b/cleverhans_v3.1.0/cleverhans/plot/__init__.py new file mode 100644 index 000000000..ed0b575e9 --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans/plot/__init__.py @@ -0,0 +1,3 @@ +""" +Plotting and visualization +""" diff --git a/cleverhans_v3.1.0/cleverhans/plot/image.py b/cleverhans_v3.1.0/cleverhans/plot/image.py new file mode 100644 index 000000000..76d340d7f --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans/plot/image.py @@ -0,0 +1,148 @@ +""" +Functionality for displaying or saving images. +""" +from tempfile import mkstemp +import os +import platform + +import numpy as np +from PIL import Image + +from cleverhans.utils import shell_call + + +def show(ndarray, min_val=None, max_val=None): + """ + Display an image. + :param ndarray: The image as an ndarray + :param min_val: The minimum pixel value in the image format + :param max_val: The maximum pixel valie in the image format + If min_val and max_val are not specified, attempts to + infer whether the image is in any of the common ranges: + [0, 1], [-1, 1], [0, 255] + This can be ambiguous, so it is better to specify if known. + """ + + # Create a temporary file with the suffix '.png'. + fd, path = mkstemp(suffix=".png") + os.close(fd) + save(path, ndarray, min_val, max_val) + shell_call(VIEWER_COMMAND + [path]) + + +def save(path, ndarray, min_val=None, max_val=None): + """ + Save an image, represented as an ndarray, to the filesystem + :param path: string, filepath + :param ndarray: The image as an ndarray + :param min_val: The minimum pixel value in the image format + :param max_val: The maximum pixel valie in the image format + If min_val and max_val are not specified, attempts to + infer whether the image is in any of the common ranges: + [0, 1], [-1, 1], [0, 255] + This can be ambiguous, so it is better to specify if known. + """ + as_pil(ndarray, min_val, max_val).save(path) + + +def as_pil(ndarray, min_val=None, max_val=None): + """ + Converts an ndarray to a PIL image. + :param ndarray: The numpy ndarray to convert + :param min_val: The minimum pixel value in the image format + :param max_val: The maximum pixel valie in the image format + If min_val and max_val are not specified, attempts to + infer whether the image is in any of the common ranges: + [0, 1], [-1, 1], [0, 255] + This can be ambiguous, so it is better to specify if known. + """ + + assert isinstance(ndarray, np.ndarray) + + # rows x cols for grayscale image + # rows x cols x channels for color + assert ndarray.ndim in [2, 3] + if ndarray.ndim == 3: + channels = ndarray.shape[2] + # grayscale or RGB + assert channels in [1, 3] + + actual_min = ndarray.min() + actual_max = ndarray.max() + + if min_val is not None: + assert actual_min >= min_val + assert actual_max <= max_val + + if np.issubdtype(ndarray.dtype, np.floating): + if min_val is None: + if actual_min < -1.0: + raise ValueError("Unrecognized range") + if actual_min < 0: + min_val = -1.0 + else: + min_val = 0.0 + if max_val is None: + if actual_max > 255.0: + raise ValueError("Unrecognized range") + if actual_max > 1.0: + max_val = 255.0 + else: + max_val = 1.0 + ndarray = ndarray - min_val + value_range = max_val - min_val + ndarray *= 255.0 / value_range + ndarray = np.cast["uint8"](ndarray) + elif "int" in str(ndarray.dtype): + if min_val is not None: + assert min_val == 0 + else: + assert actual_min >= 0.0 + if max_val is not None: + assert max_val == 255 + else: + assert actual_max <= 255.0 + else: + raise ValueError("Unrecognized dtype") + + out = Image.fromarray(ndarray) + + return out + + +def make_grid(image_batch): + """ + Turns a batch of images into one big image. + :param image_batch: ndarray, shape (batch_size, rows, cols, channels) + :returns : a big image containing all `batch_size` images in a grid + """ + m, ir, ic, ch = image_batch.shape + + pad = 3 + + padded = np.zeros((m, ir + pad * 2, ic + pad * 2, ch)) + padded[:, pad:-pad, pad:-pad, :] = image_batch + + m, ir, ic, ch = padded.shape + + pr = int(np.sqrt(m)) + pc = int(np.ceil(float(m) / pr)) + extra_m = pr * pc + assert extra_m > m + + padded = np.concatenate((padded, np.zeros((extra_m - m, ir, ic, ch))), axis=0) + + row_content = np.split(padded, pr) + row_content = [np.split(content, pc) for content in row_content] + rows = [np.concatenate(content, axis=2) for content in row_content] + grid = np.concatenate(rows, axis=1) + assert grid.shape[0] == 1, grid.shape + grid = grid[0] + + return grid + + +if platform.system() == "Darwin": + VIEWER_COMMAND = ["open", "-a", "Preview"] +else: + VIEWER_COMMAND = ["eog", "--new-instance"] diff --git a/cleverhans_v3.1.0/cleverhans/plot/pyplot_defaults.py b/cleverhans_v3.1.0/cleverhans/plot/pyplot_defaults.py new file mode 100644 index 000000000..a8f9d02cb --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans/plot/pyplot_defaults.py @@ -0,0 +1,26 @@ +"""Defaults for pyplot + +Import this file to set some default parameters for pyplot. +These are designed to make the plot look good in the context of a LaTeX +document. +If you have installed the Computer Modern fonts +( ftp://canopus.iacp.dvo.ru/pub/Font/cm_unicode/cm-unicode-0.6.3a-otf.tar.gz ) +these defaults will use them, so that text in your pyplot figures will +match text in the rest of your document. +If you do not have those fonts installed, pyplot commands will still work. +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import matplotlib +from matplotlib import pyplot + +matplotlib.rcParams["text.latex.preamble"] = r"\usepackage{bm}" +matplotlib.rcParams["text.usetex"] = True +pyplot.rcParams["pdf.fonttype"] = 42 +pyplot.rcParams["font.family"] = "serif" +pyplot.rcParams["font.serif"] = "CMU Serif" +pyplot.rcParams["font.size"] = 8 +# Note: if you get an error, delete fontList.cache diff --git a/cleverhans_v3.1.0/cleverhans/plot/pyplot_image.py b/cleverhans_v3.1.0/cleverhans/plot/pyplot_image.py new file mode 100644 index 000000000..59d0ec2ec --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans/plot/pyplot_image.py @@ -0,0 +1,182 @@ +""" +Functionality for showing images in pyplot. +See also cleverhans.plot.image for loading/saving image files, showing +images in 3rd party viewers, etc. +""" +import numpy as np +from six.moves import range + + +def pair_visual(original, adversarial, figure=None): + """ + This function displays two images: the original and the adversarial sample + :param original: the original input + :param adversarial: the input after perturbations have been applied + :param figure: if we've already displayed images, use the same plot + :return: the matplot figure to reuse for future samples + """ + import matplotlib.pyplot as plt + + # Squeeze the image to remove single-dimensional entries from array shape + original = np.squeeze(original) + adversarial = np.squeeze(adversarial) + + # Ensure our inputs are of proper shape + assert len(original.shape) == 2 or len(original.shape) == 3 + + # To avoid creating figures per input sample, reuse the sample plot + if figure is None: + plt.ion() + figure = plt.figure() + figure.canvas.set_window_title("Cleverhans: Pair Visualization") + + # Add the images to the plot + perturbations = adversarial - original + for index, image in enumerate((original, perturbations, adversarial)): + figure.add_subplot(1, 3, index + 1) + plt.axis("off") + + # If the image is 2D, then we have 1 color channel + if len(image.shape) == 2: + plt.imshow(image, cmap="gray") + else: + plt.imshow(image) + + # Give the plot some time to update + plt.pause(0.01) + + # Draw the plot and return + plt.show() + return figure + + +def grid_visual(data): + """ + This function displays a grid of images to show full misclassification + :param data: grid data of the form; + [nb_classes : nb_classes : img_rows : img_cols : nb_channels] + :return: if necessary, the matplot figure to reuse + """ + import matplotlib.pyplot as plt + + # Ensure interactive mode is disabled and initialize our graph + plt.ioff() + figure = plt.figure() + figure.canvas.set_window_title("Cleverhans: Grid Visualization") + + # Add the images to the plot + num_cols = data.shape[0] + num_rows = data.shape[1] + num_channels = data.shape[4] + for y in range(num_rows): + for x in range(num_cols): + figure.add_subplot(num_rows, num_cols, (x + 1) + (y * num_cols)) + plt.axis("off") + + if num_channels == 1: + plt.imshow(data[x, y, :, :, 0], cmap="gray") + else: + plt.imshow(data[x, y, :, :, :]) + + # Draw the plot and return + plt.show() + return figure + + +def get_logits_over_interval( + sess, model, x_data, fgsm_params, min_epsilon=-10.0, max_epsilon=10.0, num_points=21 +): + """Get logits when the input is perturbed in an interval in adv direction. + + Args: + sess: Tf session + model: Model for which we wish to get logits. + x_data: Numpy array corresponding to single data. + point of shape [height, width, channels]. + fgsm_params: Parameters for generating adversarial examples. + min_epsilon: Minimum value of epsilon over the interval. + max_epsilon: Maximum value of epsilon over the interval. + num_points: Number of points used to interpolate. + + Returns: + Numpy array containing logits. + + Raises: + ValueError if min_epsilon is larger than max_epsilon. + """ + # Get the height, width and number of channels + height = x_data.shape[0] + width = x_data.shape[1] + channels = x_data.shape[2] + + x_data = np.expand_dims(x_data, axis=0) + import tensorflow as tf + from cleverhans.attacks import FastGradientMethod + + # Define the data placeholder + x = tf.placeholder(dtype=tf.float32, shape=[1, height, width, channels], name="x") + # Define adv_x + fgsm = FastGradientMethod(model, sess=sess) + adv_x = fgsm.generate(x, **fgsm_params) + + if min_epsilon > max_epsilon: + raise ValueError("Minimum epsilon is less than maximum epsilon") + + eta = tf.nn.l2_normalize(adv_x - x, dim=0) + epsilon = tf.reshape( + tf.lin_space(float(min_epsilon), float(max_epsilon), num_points), + (num_points, 1, 1, 1), + ) + lin_batch = x + epsilon * eta + logits = model.get_logits(lin_batch) + with sess.as_default(): + log_prob_adv_array = sess.run(logits, feed_dict={x: x_data}) + return log_prob_adv_array + + +def linear_extrapolation_plot( + log_prob_adv_array, y, file_name, min_epsilon=-10, max_epsilon=10, num_points=21 +): + """Generate linear extrapolation plot. + + Args: + log_prob_adv_array: Numpy array containing log probabilities + y: Tf placeholder for the labels + file_name: Plot filename + min_epsilon: Minimum value of epsilon over the interval + max_epsilon: Maximum value of epsilon over the interval + num_points: Number of points used to interpolate + """ + import matplotlib + + matplotlib.use("Agg") + import matplotlib.pyplot as plt + + figure = plt.figure() + figure.canvas.set_window_title("Cleverhans: Linear Extrapolation Plot") + + correct_idx = np.argmax(y, axis=0) + fig = plt.figure() + plt.xlabel("Epsilon") + plt.ylabel("Logits") + x_axis = np.linspace(min_epsilon, max_epsilon, num_points) + plt.xlim(min_epsilon - 1, max_epsilon + 1) + for i in range(y.shape[0]): + if i == correct_idx: + ls = "-" + linewidth = 5 + else: + ls = "--" + linewidth = 2 + plt.plot( + x_axis, + log_prob_adv_array[:, i], + ls=ls, + linewidth=linewidth, + label="{}".format(i), + ) + plt.legend(loc="best", fontsize=14) + plt.show() + fig.savefig(file_name) + plt.clf() + return figure diff --git a/cleverhans_v3.1.0/cleverhans/plot/save_pdf.py b/cleverhans_v3.1.0/cleverhans/plot/save_pdf.py new file mode 100644 index 000000000..2b8be691c --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans/plot/save_pdf.py @@ -0,0 +1,17 @@ +""" +The save_pdf function. +""" +from matplotlib.backends.backend_pdf import PdfPages +from matplotlib import pyplot + + +def save_pdf(path): + """ + Saves a pdf of the current matplotlib figure. + + :param path: str, filepath to save to + """ + + pp = PdfPages(path) + pp.savefig(pyplot.gcf()) + pp.close() diff --git a/cleverhans_v3.1.0/cleverhans/plot/success_fail.py b/cleverhans_v3.1.0/cleverhans/plot/success_fail.py new file mode 100644 index 000000000..6c36b3bd8 --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans/plot/success_fail.py @@ -0,0 +1,317 @@ +"""Functions for plotting succes-failure curves + +Reference: https://openreview.net/forum?id=H1g0piA9tQ +""" +import warnings + +import numpy as np +from matplotlib import pyplot + +from cleverhans.serial import load +from cleverhans.utils import safe_zip + +LINEWIDTH = 2 +DEFAULT_SUCCESS_NAME = "clean" +# This must be a tuple or it is not safe to use as a param default +DEFAULT_FAIL_NAMES = ("mc", "bundled") + + +def plot_report_from_path( + path, + success_name=DEFAULT_SUCCESS_NAME, + fail_names=DEFAULT_FAIL_NAMES, + label=None, + is_max_confidence=True, + linewidth=LINEWIDTH, + plot_upper_bound=True, +): + """ + Plots a success-fail curve from a confidence report stored on disk, + :param path: string filepath for the stored report. + (Should be the output of make_confidence_report*.py) + :param success_name: The name (confidence report key) of the data that + should be used to measure success rate + :param fail_names: A list of names (confidence report keys) of the data + that should be used to measure failure rate. + *Only one of these keys will be plotted*. Each key will be tried in + order until one is found in the report. This is to support both the + output of `make_confidence_report` and `make_confidence_report_bundled`. + :param label: Optional string. Name to use for this curve in the legend. + :param is_max_confidence: bool. + If True, when measuring the failure rate, treat the data as the output + of a maximum confidence attack procedure. + This means that the attack is optimal (assuming the underlying optimizer + is good enough, *which is probably false*, so interpret the plot + accordingly) for thresholds >= .5 but for lower thresholds the observed + failure rate is a lower bound on the true worst failure rate and the + observed coverage is an upper bound (assuming good enough optimization) + on the true failure rate. + The plot thus draws the threshold >= .5 portion of the curve with a solid + line and the upper and lower bounds with a dashed line. + See https://openreview.net/forum?id=H1g0piA9tQ for details. + If False, the attack procedure is regarded as an ad hoc way of obtaining + a loose lower bound, and thus the whole curve is drawn with dashed lines. + :param linewidth: thickness of the line to draw + :param plot_upper_bound: include upper bound on error rate in plot + """ + report = load(path) + plot_report( + report, + success_name, + fail_names, + label, + is_max_confidence, + linewidth, + plot_upper_bound, + ) + + +def plot_report( + report, + success_name, + fail_names, + label=None, + is_max_confidence=True, + linewidth=LINEWIDTH, + plot_upper_bound=True, +): + """ + Plot a success fail curve from a confidence report + :param report: A confidence report + (the type of object saved by make_confidence_report.py) + :param success_name: see plot_report_from_path + :param fail_names: see plot_report_from_path + :param label: see plot_report_from_path + :param is_max_confidence: see plot_report_from_path + :param linewidth: see plot_report_from_path + """ + ( + fail_optimal, + success_optimal, + fail_lower_bound, + fail_upper_bound, + success_bounded, + ) = make_curve(report, success_name, fail_names) + assert len(fail_lower_bound) == len(fail_upper_bound) + fail_optimal = np.array(fail_optimal) + fail_lower_bound = np.array(fail_lower_bound) + fail_upper_bound = np.array(fail_upper_bound) + + if is_max_confidence: + (p,) = pyplot.plot( + fail_optimal, success_optimal, label=label, linewidth=linewidth + ) + color = p.get_color() + pyplot.plot(fail_lower_bound, success_bounded, "--", color=color) + if plot_upper_bound: + pyplot.plot(fail_upper_bound, success_bounded, "--", color=color) + else: + # If the attack was not MaxConfidence, then this whole curve is just + # a loose lower bound + all_fail = np.concatenate((fail_optimal, fail_lower_bound), axis=0) + pyplot.plot( + all_fail, + success_optimal + success_bounded, + "--", + label=label, + linewidth=linewidth, + ) + + pyplot.xlabel("Failure rate on adversarial examples") + pyplot.ylabel("Success rate on clean examples") + gap = fail_upper_bound - fail_lower_bound + if gap.size > 0: + assert gap.min() >= 0.0 + print("Max gap: ", gap.max()) + + +def make_curve(report, success_name, fail_names): + """ + Make a success-failure curve. + :param report: A confidence report + (the type of object saved by make_confidence_report.py) + :param success_name: see plot_report_from_path + :param fail_names: see plot_report_from_path + :returns: + fail_optimal: list of failure rates on adversarial data for the optimal + (t >= .5) part of the curve. Each entry corresponds to a different + threshold. Thresholds are chosen to make the smoothest possible curve + from the available data, e.g. one threshold between each unique + confidence value observed in the data. To make sure that linear + interpolation between points in the curve never overestimates the + failure rate for a specific success rate, the curve also includes + extra points that increment the failure rate prior to any point + that increments the success rate, so the curve moves up and to the + right in a series of backwards "L" shapes rather than moving up + and to the right along diagonal lines. For large datasets these + maximally pessimistic points will usually not be visible and the + curve will appear smooth. + success_optimal: list of success rates on clean data on the optimal + part of the curve. Matches up with `fail_optimal`. + fail_lower_bound: list of observed failure rates on the t < .5 portion + of the curve where MaxConfidence is not optimal. + fail_upper_bound: list of upper bounds (assuming good enough optimization, + so not a true upper bound) on the failure rates on the t < .5 portion + of the curve where MaxConfidence is not optimal. Matches up with + `fail_lower_bound`. + success_bounded: success rates on the non-optimal part of the curve. + Matches up with `fail_lower_bound` and `fail_upper_bound`. + """ + success_results = report[success_name] + fail_name = None # pacify pylint + found = False + for fail_name in fail_names: + if fail_name in report: + found = True + break + if not found: + raise ValueError( + fail_name + " not in report." "Available keys: " + str(report.keys()) + ) + fail_results = report[fail_name] + + # "good" means drawn from the distribution where we measure success rate. + # "bad" means drawn from the distribution where we measure failure rate. + # From here on out we use those terms, to avoid confusion between examples + # that actually failed and examples that were drawn from the distribution + # where we measured failure rate. + + old_all_probs_version = False + if isinstance(success_results, dict): + # This dictionary key lookup will trigger a deprecation warning if `success_results` is not the old dictionary + # style of report, so we don't want to do a dictionary lookup unless we really are using the old version. + old_all_probs_version = "all_probs" in success_results + + if old_all_probs_version: + warnings.warn( + "The 'all_probs' key is included only to support " + " old files from a private development codebase. " + "Support for this key can be dropped at any time " + " without warning." + ) + good_probs = success_results["all_probs"] + bad_probs = fail_results["all_probs"] + bad_corrects = fail_results["correctness_mask"] + good_corrects = success_results["correctness_mask"] + else: + if isinstance(success_results, dict): + # Still using dict, but using newer key names + warnings.warn( + "Support for dictionary confidence reports is deprecated. Switch to using the classes in " + "cleverhans.confidence_report. Support for old dictionary-style reports may be removed " + "on or after 2019-07-19." + ) + good_probs = success_results["confidence"] + bad_probs = fail_results["confidence"] + good_corrects = success_results["correctness"] + bad_corrects = fail_results["correctness"] + else: + # current version + good_probs = success_results.confidence + bad_probs = fail_results.confidence + good_corrects = success_results.correctness + bad_corrects = fail_results.correctness + good_triplets = [ + (prob, correct, True) for prob, correct in safe_zip(good_probs, good_corrects) + ] + bad_triplets = [ + (prob, correct, False) for prob, correct in safe_zip(bad_probs, bad_corrects) + ] + total_good = len(good_triplets) + total_bad = len(bad_triplets) + if total_good != 10000: + warnings.warn( + "Not using full test set? Found " + + str(total_good) + + " examples for measuring success rate" + ) + if total_bad != 10000: + warnings.warn("Not using full test set for adversarial examples?") + all_triplets = good_triplets + bad_triplets + all_triplets = sorted(all_triplets, key=lambda x: -x[0]) + + # Start with the case for threshold t = 1. + # Examples are covered only if prob > t (strict inequality) + # So initially nothing is covered + good_covered_and_correct = 0 + bad_covered_and_incorrect = 0 + + # Number of examples that are bad, incorrect, and covered by + # a t >= 0.5, or that were merely covered by a t < 0.5 + failure_opportunities = 0 + + next_idx = 0 + + fail_optimal = [] + success_optimal = [] + fail_upper_bound = [] + fail_lower_bound = [] + success_bounded = [] + + bounded = False + + # NOTE: the loop always exits via an internal break statement. + # Copied the termination condition to the while statement for ease + # of reading. + while next_idx < len(all_triplets): + gs = float(good_covered_and_correct) / total_good + bf = float(bad_covered_and_incorrect) / total_bad + # Add results for current threshold to the list + if not bounded: + + # Sometimes when there are big jumps the failure rate it makes + # artifacts in the plot, where there's a long linear track. + # This implies the real success-fail curve is linear when + # actually it just isn't sampled by the data. + # To avoid implying that the model reaches a higher success + # rate than it actually does, we avoid these plotting artifacts + # by introducing extra points that make the graph move horizontally + # to the right first, then vertically. + if len(fail_optimal) > 0: + prev_bf = fail_optimal[-1] + prev_gs = success_optimal[-1] + + if gs > prev_gs and bf > prev_bf: + fail_optimal.append(bf) + success_optimal.append(prev_gs) + + success_optimal.append(gs) + fail_optimal.append(bf) + else: + success_bounded.append(gs) + fail_lower_bound.append(bf) + fail_upper_bound.append(float(failure_opportunities) / total_bad) + + if next_idx == len(all_triplets): + break + + # next_prob_to_include is not quite the same thing as the threshold. + # The threshold is infinitesimally smaller than this value. + next_prob_to_include = all_triplets[next_idx][0] + + # Process all ties + while next_prob_to_include == all_triplets[next_idx][0]: + _prob, correct, is_good = all_triplets[next_idx] + if is_good: + good_covered_and_correct += correct + else: + if next_prob_to_include <= 0.5: + failure_opportunities += 1 + else: + failure_opportunities += 1 - correct + bad_covered_and_incorrect += 1 - correct + next_idx += 1 + if next_idx == len(all_triplets): + break + + if next_prob_to_include <= 0.5: + bounded = True + + out = ( + fail_optimal, + success_optimal, + fail_lower_bound, + fail_upper_bound, + success_bounded, + ) + return out diff --git a/cleverhans_v3.1.0/cleverhans/serial.py b/cleverhans_v3.1.0/cleverhans/serial.py new file mode 100644 index 000000000..0ea004d75 --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans/serial.py @@ -0,0 +1,224 @@ +"""Serialization functionality. + +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import warnings + +import joblib +import tensorflow as tf + +from cleverhans.model import Model +from cleverhans.utils import ordered_union +from cleverhans.utils import safe_zip + + +class PicklableVariable(object): + """ + A wrapper around a Variable that makes it picklable. + + The name of the Variable will not be reliable, only the value. Models + intended to be picklable should identify variables by referencing + Python objects rather than by using TensorFlow's names. + + TensorFlow Variables have different values associated with each Session. + For this class, the value associated with the default Session will be used + for both saving and loading, so both operations require that a default + Session has been selected. + + Pickle is not secure. Unpickle only files you made yourself. + + See cleverhans_tutorials/mnist_tutorial_picklable.py for examples of a + complete model training, pickling, and unpickling process using + PicklableVariable. + + See cleverhans.picklable_model for models built using PicklableVariable. + """ + + def __init__(self, *args, **kwargs): + self.var = tf.Variable(*args, **kwargs) + + def __getstate__(self): + sess = tf.get_default_session() + if sess is None: + raise RuntimeError( + "PicklableVariable requires a default " "TensorFlow session" + ) + return {"var": sess.run(self.var)} + + def __setstate__(self, d): + self.var = tf.Variable(d["var"]) + sess = tf.get_default_session() + if sess is None: + raise RuntimeError( + "PicklableVariable requires a default " "TensorFlow session" + ) + sess.run(self.var.initializer) + + +class NoRefModel(Model): + """ + A Model that can be pickled because it contains no references to any + Variables (e.g. it identifies Variables only by name). + The Model must be able to find all of its Variables via get_vars + for them to be pickled. + Note that NoRefModel may have different Variable names after it is + restored, e.g. if the unpickling is run with a different enclosing + scope. NoRefModel will still work in these circumstances as long + as get_params returns the same order of Variables after unpickling + as it did before pickling. + See also cleverhans.picklable_model for a different, complementary + pickling strategy: models that can be pickled because they use *only* + references to Variables and work regardless of Variable names. + """ + + def __getstate__(self): + # Serialize everything except the Variables + out = self.__dict__.copy() + + # The base Model class adds this tf reference to self + # We mustn't pickle anything tf, this will need to be + # regenerated after the model is reloaded. + if "_dummy_input" in out: + del out["_dummy_input"] + + # Add the Variables + sess = tf.get_default_session() + if sess is None: + raise RuntimeError("NoRefModel requires a default " "TensorFlow session") + tf_variables = self.get_vars() + out[VARS] = sess.run(tf_variables) + out[VAR_NAMES] = [var.name for var in tf_variables] + return out + + def __setstate__(self, d): + tf_variables = d[VARS] + del d[VARS] + tf_variable_names = None + # older joblib files may not have "_tf_variable_names" + if VAR_NAMES in d: + tf_variable_names = d[VAR_NAMES] + del d[VAR_NAMES] + else: + warnings.warn( + "This joblib file has no " + VAR_NAMES + " field. " + "The field may become required on or after 2019-04-11." + "You can make your file compatible with the new format by" + " loading the file and re-saving it." + ) + # Deserialize everything except the Variables + self.__dict__ = d + # Deserialize the Variables + sess = tf.get_default_session() + if sess is None: + raise RuntimeError("NoRefModel requires a default " "TensorFlow session") + cur_vars = self.get_vars() + if len(cur_vars) != len(tf_variables): + print("Model format mismatch") + print("Current model has " + str(len(cur_vars)) + " variables") + print("Saved model has " + str(len(tf_variables)) + " variables") + print("Names of current vars:") + for var in cur_vars: + print("\t" + var.name) + if tf_variable_names is not None: + print("Names of saved vars:") + for name in tf_variable_names: + print("\t" + name) + else: + print("Saved vars use old format, no names available for them") + assert False + + found = [False] * len(cur_vars) + if tf_variable_names is not None: + # New version using the names to handle changes in ordering + for value, name in safe_zip(tf_variables, tf_variable_names): + value_found = False + for idx, cur_var in enumerate(cur_vars): + if cur_var.name == name: + assert not found[idx] + value_found = True + found[idx] = True + cur_var.load(value, sess) + break + assert value_found + assert all(found) + else: + # Old version that works if and only if the order doesn't change + for var, value in safe_zip(cur_vars, tf_variables): + var.load(value, sess) + + def get_vars(self): + """ + Provides access to the model's Variables. + This may include Variables that are not parameters, such as batch + norm running moments. + :return: A list of all Variables defining the model. + """ + + # Catch eager execution and assert function overload. + try: + if tf.executing_eagerly(): + raise NotImplementedError( + "For Eager execution - get_vars " "must be overridden." + ) + except AttributeError: + pass + + done = False + tried_to_make_params = False + while not done: + # Most models in cleverhans use only trainable variables and do not + # make sure the other collections are updated correctly. + trainable_vars = tf.get_collection( + tf.GraphKeys.TRAINABLE_VARIABLES, self.scope + "/" + ) + # When wrapping other code, such as the CIFAR 10 challenge models, + # we need to make sure we get the batch norm running averages as well + # as the trainable variables. + model_vars = tf.get_collection( + tf.GraphKeys.MODEL_VARIABLES, self.scope + "/" + ) + scope_vars = ordered_union(trainable_vars, model_vars) + + if len(scope_vars) > 0: + done = True + else: + assert not tried_to_make_params + tried_to_make_params = True + self.make_params() + + # Make sure no variables have been added or removed + if hasattr(self, "num_vars"): + assert self.num_vars == len(scope_vars) + else: + self.num_vars = len(scope_vars) + + return scope_vars + + +def save(filepath, obj): + """Saves an object to the specified filepath using joblib. + + joblib is like pickle but will save NumPy arrays as separate files for + greater efficiency. + + :param filepath: str, path to save to + :obj filepath: object to save + """ + + joblib.dump(obj, filepath) + + +def load(filepath): + """Returns an object stored via `save`""" + + obj = joblib.load(filepath) + + return obj + + +VARS = "_tf_variables" +VAR_NAMES = "_tf_variable_names" diff --git a/cleverhans_v3.1.0/cleverhans/train.py b/cleverhans_v3.1.0/cleverhans/train.py new file mode 100644 index 000000000..4908f1593 --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans/train.py @@ -0,0 +1,333 @@ +""" +Multi-replica synchronous training + + +NOTE: This module is much more free to change than many other modules +in CleverHans. CleverHans is very conservative about changes to any +code that affects the output of benchmark tests (attacks, evaluation +methods, etc.). This module provides *model training* functionality +not *benchmarks* and thus is free to change rapidly to provide better +speed, accuracy, etc. +""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import logging +import os +import time +import warnings + +import math +import numpy as np +from six.moves import xrange +import tensorflow as tf + +from cleverhans import canary +from cleverhans.utils import _ArgsWrapper, create_logger +from cleverhans.utils import safe_zip +from cleverhans.utils_tf import infer_devices +from cleverhans.utils_tf import initialize_uninitialized_global_variables + + +_logger = create_logger("train") +_logger.setLevel(logging.INFO) + + +def train( + sess, + loss, + x_train, + y_train, + init_all=False, + evaluate=None, + feed=None, + args=None, + rng=None, + var_list=None, + fprop_args=None, + optimizer=None, + devices=None, + x_batch_preprocessor=None, + use_ema=False, + ema_decay=0.998, + run_canary=None, + loss_threshold=1e5, + dataset_train=None, + dataset_size=None, +): + """ + Run (optionally multi-replica, synchronous) training to minimize `loss` + :param sess: TF session to use when training the graph + :param loss: tensor, the loss to minimize + :param x_train: numpy array with training inputs or tf Dataset + :param y_train: numpy array with training outputs or tf Dataset + :param init_all: (boolean) If set to true, all TF variables in the session + are (re)initialized, otherwise only previously + uninitialized variables are initialized before training. + :param evaluate: function that is run after each training iteration + (typically to display the test/validation accuracy). + :param feed: An optional dictionary that is appended to the feeding + dictionary before the session runs. Can be used to feed + the learning phase of a Keras model for instance. + :param args: dict or argparse `Namespace` object. + Should contain `nb_epochs`, `learning_rate`, + `batch_size` + :param rng: Instance of numpy.random.RandomState + :param var_list: Optional list of parameters to train. + :param fprop_args: dict, extra arguments to pass to fprop (loss and model). + :param optimizer: Optimizer to be used for training + :param devices: list of device names to use for training + If None, defaults to: all GPUs, if GPUs are available + all devices, if no GPUs are available + :param x_batch_preprocessor: callable + Takes a single tensor containing an x_train batch as input + Returns a single tensor containing an x_train batch as output + Called to preprocess the data before passing the data to the Loss + :param use_ema: bool + If true, uses an exponential moving average of the model parameters + :param ema_decay: float or callable + The decay parameter for EMA, if EMA is used + If a callable rather than a float, this is a callable that takes + the epoch and batch as arguments and returns the ema_decay for + the current batch. + :param loss_threshold: float + Raise an exception if the loss exceeds this value. + This is intended to rapidly detect numerical problems. + Sometimes the loss may legitimately be higher than this value. In + such cases, raise the value. If needed it can be np.inf. + :param dataset_train: tf Dataset instance. + Used as a replacement for x_train, y_train for faster performance. + :param dataset_size: integer, the size of the dataset_train. + :return: True if model trained + """ + + # Check whether the hardware is working correctly + canary.run_canary() + if run_canary is not None: + warnings.warn( + "The `run_canary` argument is deprecated. The canary " + "is now much cheaper and thus runs all the time. The " + "canary now uses its own loss function so it is not " + "necessary to turn off the canary when training with " + " a stochastic loss. Simply quit passing `run_canary`." + "Passing `run_canary` may become an error on or after " + "2019-10-16." + ) + + args = _ArgsWrapper(args or {}) + fprop_args = fprop_args or {} + + # Check that necessary arguments were given (see doc above) + # Be sure to support 0 epochs for debugging purposes + if args.nb_epochs is None: + raise ValueError("`args` must specify number of epochs") + if optimizer is None: + if args.learning_rate is None: + raise ValueError("Learning rate was not given in args dict") + assert args.batch_size, "Batch size was not given in args dict" + + if rng is None: + rng = np.random.RandomState() + + if optimizer is None: + optimizer = tf.train.AdamOptimizer(learning_rate=args.learning_rate) + else: + if not isinstance(optimizer, tf.train.Optimizer): + raise ValueError( + "optimizer object must be from a child class of " "tf.train.Optimizer" + ) + + grads = [] + xs = [] + preprocessed_xs = [] + ys = [] + if dataset_train is not None: + assert x_train is None and y_train is None and x_batch_preprocessor is None + if dataset_size is None: + raise ValueError("You must provide a dataset size") + data_iterator = dataset_train.make_one_shot_iterator().get_next() + x_train, y_train = sess.run(data_iterator) + + devices = infer_devices(devices) + for device in devices: + with tf.device(device): + x = tf.placeholder(x_train.dtype, (None,) + x_train.shape[1:]) + y = tf.placeholder(y_train.dtype, (None,) + y_train.shape[1:]) + xs.append(x) + ys.append(y) + + if x_batch_preprocessor is not None: + x = x_batch_preprocessor(x) + + # We need to keep track of these so that the canary can feed + # preprocessed values. If the canary had to feed raw values, + # stochastic preprocessing could make the canary fail. + preprocessed_xs.append(x) + + loss_value = loss.fprop(x, y, **fprop_args) + + grads.append(optimizer.compute_gradients(loss_value, var_list=var_list)) + num_devices = len(devices) + print("num_devices: ", num_devices) + + grad = avg_grads(grads) + # Trigger update operations within the default graph (such as batch_norm). + with tf.control_dependencies(tf.get_collection(tf.GraphKeys.UPDATE_OPS)): + train_step = optimizer.apply_gradients(grad) + + epoch_tf = tf.placeholder(tf.int32, []) + batch_tf = tf.placeholder(tf.int32, []) + + if use_ema: + if callable(ema_decay): + ema_decay = ema_decay(epoch_tf, batch_tf) + ema = tf.train.ExponentialMovingAverage(decay=ema_decay) + with tf.control_dependencies([train_step]): + train_step = ema.apply(var_list) + # Get pointers to the EMA's running average variables + avg_params = [ema.average(param) for param in var_list] + # Make temporary buffers used for swapping the live and running average + # parameters + tmp_params = [tf.Variable(param, trainable=False) for param in var_list] + # Define the swapping operation + param_to_tmp = [ + tf.assign(tmp, param) for tmp, param in safe_zip(tmp_params, var_list) + ] + with tf.control_dependencies(param_to_tmp): + avg_to_param = [ + tf.assign(param, avg) for param, avg in safe_zip(var_list, avg_params) + ] + with tf.control_dependencies(avg_to_param): + tmp_to_avg = [ + tf.assign(avg, tmp) for avg, tmp in safe_zip(avg_params, tmp_params) + ] + swap = tmp_to_avg + + batch_size = args.batch_size + + assert batch_size % num_devices == 0 + device_batch_size = batch_size // num_devices + + if init_all: + sess.run(tf.global_variables_initializer()) + else: + initialize_uninitialized_global_variables(sess) + + for epoch in xrange(args.nb_epochs): + if dataset_train is not None: + nb_batches = int(math.ceil(float(dataset_size) / batch_size)) + else: + # Indices to shuffle training set + index_shuf = list(range(len(x_train))) + # Randomly repeat a few training examples each epoch to avoid + # having a too-small batch + while len(index_shuf) % batch_size != 0: + index_shuf.append(rng.randint(len(x_train))) + nb_batches = len(index_shuf) // batch_size + rng.shuffle(index_shuf) + # Shuffling here versus inside the loop doesn't seem to affect + # timing very much, but shuffling here makes the code slightly + # easier to read + x_train_shuffled = x_train[index_shuf] + y_train_shuffled = y_train[index_shuf] + + prev = time.time() + for batch in range(nb_batches): + if dataset_train is not None: + x_train_shuffled, y_train_shuffled = sess.run(data_iterator) + start, end = 0, batch_size + else: + # Compute batch start and end indices + start = batch * batch_size + end = (batch + 1) * batch_size + # Perform one training step + diff = end - start + assert diff == batch_size + + feed_dict = {epoch_tf: epoch, batch_tf: batch} + for dev_idx in xrange(num_devices): + cur_start = start + dev_idx * device_batch_size + cur_end = start + (dev_idx + 1) * device_batch_size + feed_dict[xs[dev_idx]] = x_train_shuffled[cur_start:cur_end] + feed_dict[ys[dev_idx]] = y_train_shuffled[cur_start:cur_end] + if cur_end != end and dataset_train is None: + msg = ( + "batch_size (%d) must be a multiple of num_devices " + "(%d).\nCUDA_VISIBLE_DEVICES: %s" + "\ndevices: %s" + ) + args = ( + batch_size, + num_devices, + os.environ["CUDA_VISIBLE_DEVICES"], + str(devices), + ) + raise ValueError(msg % args) + if feed is not None: + feed_dict.update(feed) + + _, loss_numpy = sess.run([train_step, loss_value], feed_dict=feed_dict) + + if np.abs(loss_numpy) > loss_threshold: + raise ValueError("Extreme loss during training: ", loss_numpy) + if np.isnan(loss_numpy) or np.isinf(loss_numpy): + raise ValueError("NaN/Inf loss during training") + assert dataset_train is not None or end == len( + index_shuf + ) # Check that all examples were used + cur = time.time() + _logger.info("Epoch " + str(epoch) + " took " + str(cur - prev) + " seconds") + if evaluate is not None: + if use_ema: + # Before running evaluation, load the running average + # parameters into the live slot, so we can see how well + # the EMA parameters are performing + sess.run(swap) + evaluate() + if use_ema: + # Swap the parameters back, so that we continue training + # on the live parameters + sess.run(swap) + if use_ema: + # When training is done, swap the running average parameters into + # the live slot, so that we use them when we deploy the model + sess.run(swap) + + return True + + +def avg_grads(tower_grads): + """Calculate the average gradient for each shared variable across all + towers. + Note that this function provides a synchronization point across all towers. + Args: + tower_grads: List of lists of (gradient, variable) tuples. The outer list + is over individual gradients. The inner list is over the gradient + calculation for each tower. + Returns: + List of pairs of (gradient, variable) where the gradient has been + averaged across all towers. + + Modified from this tutorial: https://tinyurl.com/n3jr2vm + """ + if len(tower_grads) == 1: + return tower_grads[0] + average_grads = [] + for grad_and_vars in zip(*tower_grads): + # Note that each grad_and_vars looks like the following: + # ((grad0_gpu0, var0_gpu0), ... , (grad0_gpuN, var0_gpuN)) + grads = [g for g, _ in grad_and_vars] + + # Average over the 'tower' dimension. + grad = tf.add_n(grads) / len(grads) + + # Keep in mind that the Variables are redundant because they are shared + # across towers. So .. we will just return the first tower's pointer to + # the Variable. + v = grad_and_vars[0][1] + assert all(v is grad_and_var[1] for grad_and_var in grad_and_vars) + grad_and_var = (grad, v) + average_grads.append(grad_and_var) + return average_grads diff --git a/cleverhans_v3.1.0/cleverhans/utils.py b/cleverhans_v3.1.0/cleverhans/utils.py new file mode 100644 index 000000000..fd0561afe --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans/utils.py @@ -0,0 +1,385 @@ +""" +Generic utility functions useful for writing Python code in general +""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +from collections import OrderedDict +import warnings +import logging +import os +import re +import subprocess + +import numpy as np +from six.moves import xrange + +known_number_types = ( + int, + float, + np.float16, + np.float32, + np.float64, + np.int8, + np.int16, + np.int32, + np.int32, + np.int64, + np.uint8, + np.uint16, + np.uint32, + np.uint64, +) + + +CLEVERHANS_ROOT = os.path.dirname(os.path.dirname(__file__)) + + +class _ArgsWrapper(object): + + """ + Wrapper that allows attribute access to dictionaries + """ + + def __init__(self, args): + if not isinstance(args, dict): + args = vars(args) + self.args = args + + def __getattr__(self, name): + return self.args.get(name) + + +class AccuracyReport(object): + + """ + An object summarizing the accuracy results for experiments involving + training on clean examples or adversarial examples, then evaluating + on clean or adversarial examples. + """ + + def __init__(self): + self.clean_train_clean_eval = 0.0 + self.clean_train_adv_eval = 0.0 + self.adv_train_clean_eval = 0.0 + self.adv_train_adv_eval = 0.0 + + # Training data accuracy results to be used by tutorials + self.train_clean_train_clean_eval = 0.0 + self.train_clean_train_adv_eval = 0.0 + self.train_adv_train_clean_eval = 0.0 + self.train_adv_train_adv_eval = 0.0 + + +def batch_indices(batch_nb, data_length, batch_size): + """ + This helper function computes a batch start and end index + :param batch_nb: the batch number + :param data_length: the total length of the data being parsed by batches + :param batch_size: the number of inputs in each batch + :return: pair of (start, end) indices + """ + # Batch start and end index + start = int(batch_nb * batch_size) + end = int((batch_nb + 1) * batch_size) + + # When there are not enough inputs left, we reuse some to complete the + # batch + if end > data_length: + shift = end - data_length + start -= shift + end -= shift + + return start, end + + +def other_classes(nb_classes, class_ind): + """ + Returns a list of class indices excluding the class indexed by class_ind + :param nb_classes: number of classes in the task + :param class_ind: the class index to be omitted + :return: list of class indices excluding the class indexed by class_ind + """ + if class_ind < 0 or class_ind >= nb_classes: + error_str = "class_ind must be within the range (0, nb_classes - 1)" + raise ValueError(error_str) + + other_classes_list = list(range(nb_classes)) + other_classes_list.remove(class_ind) + + return other_classes_list + + +def to_categorical(y, nb_classes, num_classes=None): + """ + Converts a class vector (integers) to binary class matrix. + This is adapted from the Keras function with the same name. + :param y: class vector to be converted into a matrix + (integers from 0 to nb_classes). + :param nb_classes: nb_classes: total number of classes. + :param num_classses: depricated version of nb_classes + :return: A binary matrix representation of the input. + """ + if num_classes is not None: + if nb_classes is not None: + raise ValueError( + "Should not specify both nb_classes and its deprecated " + "alias, num_classes" + ) + warnings.warn( + "`num_classes` is deprecated. Switch to `nb_classes`." + " `num_classes` may be removed on or after 2019-04-23." + ) + nb_classes = num_classes + del num_classes + y = np.array(y, dtype="int").ravel() + n = y.shape[0] + categorical = np.zeros((n, nb_classes)) + categorical[np.arange(n), y] = 1 + return categorical + + +def random_targets(gt, nb_classes): + """ + Take in an array of correct labels and randomly select a different label + for each label in the array. This is typically used to randomly select a + target class in targeted adversarial examples attacks (i.e., when the + search algorithm takes in both a source class and target class to compute + the adversarial example). + :param gt: the ground truth (correct) labels. They can be provided as a + 1D vector or 2D array of one-hot encoded labels. + :param nb_classes: The number of classes for this task. The random class + will be chosen between 0 and nb_classes such that it + is different from the correct class. + :return: A numpy array holding the randomly-selected target classes + encoded as one-hot labels. + """ + # If the ground truth labels are encoded as one-hot, convert to labels. + if len(gt.shape) == 2: + gt = np.argmax(gt, axis=1) + + # This vector will hold the randomly selected labels. + result = np.zeros(gt.shape, dtype=np.int32) + + for class_ind in xrange(nb_classes): + # Compute all indices in that class. + in_cl = gt == class_ind + size = np.sum(in_cl) + + # Compute the set of potential targets for this class. + potential_targets = other_classes(nb_classes, class_ind) + + # Draw with replacement random targets among the potential targets. + result[in_cl] = np.random.choice(potential_targets, size=size) + + # Encode vector of random labels as one-hot labels. + result = to_categorical(result, nb_classes) + result = result.astype(np.int32) + + return result + + +def pair_visual(*args, **kwargs): + """Deprecation wrapper""" + warnings.warn( + "`pair_visual` has moved to `cleverhans.plot.pyplot_image`. " + "cleverhans.utils.pair_visual may be removed on or after " + "2019-04-24." + ) + from cleverhans.plot.pyplot_image import pair_visual as new_pair_visual + + return new_pair_visual(*args, **kwargs) + + +def grid_visual(*args, **kwargs): + """Deprecation wrapper""" + warnings.warn( + "`grid_visual` has moved to `cleverhans.plot.pyplot_image`. " + "cleverhans.utils.grid_visual may be removed on or after " + "2019-04-24." + ) + from cleverhans.plot.pyplot_image import grid_visual as new_grid_visual + + return new_grid_visual(*args, **kwargs) + + +def get_logits_over_interval(*args, **kwargs): + """Deprecation wrapper""" + warnings.warn( + "`get_logits_over_interval` has moved to " + "`cleverhans.plot.pyplot_image`. " + "cleverhans.utils.get_logits_over_interval may be removed on " + "or after 2019-04-24." + ) + # pylint:disable=line-too-long + from cleverhans.plot.pyplot_image import ( + get_logits_over_interval as new_get_logits_over_interval, + ) + + return new_get_logits_over_interval(*args, **kwargs) + + +def linear_extrapolation_plot(*args, **kwargs): + """Deprecation wrapper""" + warnings.warn( + "`linear_extrapolation_plot` has moved to " + "`cleverhans.plot.pyplot_image`. " + "cleverhans.utils.linear_extrapolation_plot may be removed on " + "or after 2019-04-24." + ) + # pylint:disable=line-too-long + from cleverhans.plot.pyplot_image import ( + linear_extrapolation_plot as new_linear_extrapolation_plot, + ) + + return new_linear_extrapolation_plot(*args, **kwargs) + + +def set_log_level(level, name="cleverhans"): + """ + Sets the threshold for the cleverhans logger to level + :param level: the logger threshold. You can find values here: + https://docs.python.org/2/library/logging.html#levels + :param name: the name used for the cleverhans logger + """ + logging.getLogger(name).setLevel(level) + + +def get_log_level(name="cleverhans"): + """ + Gets the current threshold for the cleverhans logger + :param name: the name used for the cleverhans logger + """ + return logging.getLogger(name).getEffectiveLevel() + + +class TemporaryLogLevel(object): + """ + A ContextManager that changes a log level temporarily. + + Note that the log level will be set back to its original value when + the context manager exits, even if the log level has been changed + again in the meantime. + """ + + def __init__(self, level, name): + self.name = name + self.level = level + + def __enter__(self): + self.old_level = get_log_level(self.name) + set_log_level(self.level, self.name) + + def __exit__(self, type, value, traceback): + set_log_level(self.old_level, self.name) + return True + + +def create_logger(name): + """ + Create a logger object with the given name. + + If this is the first time that we call this method, then initialize the + formatter. + """ + base = logging.getLogger("cleverhans") + if len(base.handlers) == 0: + ch = logging.StreamHandler() + formatter = logging.Formatter( + "[%(levelname)s %(asctime)s %(name)s] " + "%(message)s" + ) + ch.setFormatter(formatter) + base.addHandler(ch) + + return base + + +def deterministic_dict(normal_dict): + """ + Returns a version of `normal_dict` whose iteration order is always the same + """ + out = OrderedDict() + for key in sorted(normal_dict.keys()): + out[key] = normal_dict[key] + return out + + +def ordered_union(l1, l2): + """ + Return the union of l1 and l2, with a deterministic ordering. + (Union of python sets does not necessarily have a consisten iteration + order) + :param l1: list of items + :param l2: list of items + :returns: list containing one copy of each item that is in l1 or in l2 + """ + out = [] + for e in l1 + l2: + if e not in out: + out.append(e) + return out + + +def safe_zip(*args): + """like zip but with these properties: + - returns a list, rather than an iterator. This is the old Python2 zip behavior. + - a guarantee that all arguments are the same length. + (normal zip silently drops entries to make them the same length) + """ + length = len(args[0]) + if not all(len(arg) == length for arg in args): + raise ValueError( + "Lengths of arguments do not match: " + str([len(arg) for arg in args]) + ) + return list(zip(*args)) + + +def shell_call(command, **kwargs): + """Calls shell command with argument substitution. + + Args: + command: command represented as a list. Each element of the list is one + token of the command. For example "cp a b" becomes ['cp', 'a', 'b'] + If any element of the list looks like '${NAME}' then it will be replaced + by value from **kwargs with key 'NAME'. + **kwargs: dictionary with argument substitution + + Returns: + output of the command + + Raises: + subprocess.CalledProcessError if command return value is not zero + + This function is useful when you need to do variable substitution prior + running the command. Below are few examples of how it works: + + shell_call(['cp', 'a', 'b'], a='asd') calls command 'cp a b' + + shell_call(['cp', '${a}', 'b'], a='asd') calls command 'cp asd b', + '${a}; was replaced with 'asd' before calling the command + """ + # Regular expression to find instances of '${NAME}' in a string + CMD_VARIABLE_RE = re.compile("^\\$\\{(\\w+)\\}$") + command = list(command) + for i in range(len(command)): + m = CMD_VARIABLE_RE.match(command[i]) + if m: + var_id = m.group(1) + if var_id in kwargs: + command[i] = kwargs[var_id] + str_command = " ".join(command) + logging.debug("Executing shell command: %s" % str_command) + return subprocess.check_output(command) + + +def deep_copy(numpy_dict): + """ + Returns a copy of a dictionary whose values are numpy arrays. + Copies their values rather than copying references to them. + """ + out = {} + for key in numpy_dict: + out[key] = numpy_dict[key].copy() + return out diff --git a/cleverhans_v3.1.0/cleverhans/utils_keras.py b/cleverhans_v3.1.0/cleverhans/utils_keras.py new file mode 100644 index 000000000..9aadc16d2 --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans/utils_keras.py @@ -0,0 +1,272 @@ +""" +Model construction utilities based on keras +""" +from distutils.version import LooseVersion +import warnings +import tensorflow as tf + +from .model import Model, NoSuchLayerError + +# Assignment rather than import because direct import from within Keras +# doesn't work in tf 1.8 +Sequential = tf.keras.models.Sequential +Conv2D = tf.keras.layers.Conv2D +Dense = tf.keras.layers.Dense +Activation = tf.keras.layers.Activation +Flatten = tf.keras.layers.Flatten +KerasModel = tf.keras.models.Model + + +def conv_2d(filters, kernel_shape, strides, padding, input_shape=None): + """ + Defines the right convolutional layer according to the + version of Keras that is installed. + :param filters: (required integer) the dimensionality of the output + space (i.e. the number output of filters in the + convolution) + :param kernel_shape: (required tuple or list of 2 integers) specifies + the kernel shape of the convolution + :param strides: (required tuple or list of 2 integers) specifies + the strides of the convolution along the width and + height. + :param padding: (required string) can be either 'valid' (no padding around + input or feature map) or 'same' (pad to ensure that the + output feature map size is identical to the layer input) + :param input_shape: (optional) give input shape if this is the first + layer of the model + :return: the Keras layer + """ + if input_shape is not None: + return Conv2D( + filters=filters, + kernel_size=kernel_shape, + strides=strides, + padding=padding, + input_shape=input_shape, + ) + else: + return Conv2D( + filters=filters, kernel_size=kernel_shape, strides=strides, padding=padding + ) + + +def cnn_model( + logits=False, + input_ph=None, + img_rows=28, + img_cols=28, + channels=1, + nb_filters=64, + nb_classes=10, +): + """ + Defines a CNN model using Keras sequential model + :param logits: If set to False, returns a Keras model, otherwise will also + return logits tensor + :param input_ph: The TensorFlow tensor for the input + (needed if returning logits) + ("ph" stands for placeholder but it need not actually be a + placeholder) + :param img_rows: number of row in the image + :param img_cols: number of columns in the image + :param channels: number of color channels (e.g., 1 for MNIST) + :param nb_filters: number of convolutional filters per layer + :param nb_classes: the number of output classes + :return: + """ + model = Sequential() + + # Define the layers successively (convolution layers are version dependent) + if tf.keras.backend.image_data_format() == "channels_first": + input_shape = (channels, img_rows, img_cols) + else: + assert tf.keras.backend.image_data_format() == "channels_last" + input_shape = (img_rows, img_cols, channels) + + layers = [ + conv_2d(nb_filters, (8, 8), (2, 2), "same", input_shape=input_shape), + Activation("relu"), + conv_2d((nb_filters * 2), (6, 6), (2, 2), "valid"), + Activation("relu"), + conv_2d((nb_filters * 2), (5, 5), (1, 1), "valid"), + Activation("relu"), + Flatten(), + Dense(nb_classes), + ] + + for layer in layers: + model.add(layer) + + if logits: + logits_tensor = model(input_ph) + model.add(Activation("softmax")) + + if logits: + return model, logits_tensor + else: + return model + + +class KerasModelWrapper(Model): + """ + An implementation of `Model` that wraps a Keras model. It + specifically exposes the hidden features of a model by creating new models. + The symbolic graph is reused and so there is little overhead. Splitting + in-place operations can incur an overhead. + """ + + def __init__(self, model): + """ + Create a wrapper for a Keras model + :param model: A Keras model + """ + super(KerasModelWrapper, self).__init__(None, None, {}) + + if model is None: + raise ValueError("model argument must be supplied.") + + self.model = model + self.keras_model = None + + def _get_softmax_name(self): + """ + Looks for the name of the softmax layer. + :return: Softmax layer name + """ + for layer in self.model.layers: + cfg = layer.get_config() + if "activation" in cfg and cfg["activation"] == "softmax": + return layer.name + + raise Exception("No softmax layers found") + + def _get_abstract_layer_name(self): + """ + Looks for the name of abstracted layer. + Usually these layers appears when model is stacked. + :return: List of abstracted layers + """ + abstract_layers = [] + for layer in self.model.layers: + if "layers" in layer.get_config(): + abstract_layers.append(layer.name) + + return abstract_layers + + def _get_logits_name(self): + """ + Looks for the name of the layer producing the logits. + :return: name of layer producing the logits + """ + softmax_name = self._get_softmax_name() + softmax_layer = self.model.get_layer(softmax_name) + + if not isinstance(softmax_layer, Activation): + # In this case, the activation is part of another layer + return softmax_name + + if not hasattr(softmax_layer, "_inbound_nodes"): + raise RuntimeError("Please update keras to version >= 2.1.3") + + node = softmax_layer._inbound_nodes[0] + + if LooseVersion(tf.__version__) < LooseVersion("1.14.0"): + logits_name = node.inbound_layers[0].name + else: + logits_name = node.inbound_layers.name + + return logits_name + + def get_logits(self, x): + """ + :param x: A symbolic representation of the network input. + :return: A symbolic representation of the logits + """ + logits_name = self._get_logits_name() + logits_layer = self.get_layer(x, logits_name) + + # Need to deal with the case where softmax is part of the + # logits layer + if logits_name == self._get_softmax_name(): + softmax_logit_layer = self.get_layer(x, logits_name) + + # The final op is the softmax. Return its input + logits_layer = softmax_logit_layer._op.inputs[0] + + return logits_layer + + def get_probs(self, x): + """ + :param x: A symbolic representation of the network input. + :return: A symbolic representation of the probs + """ + name = self._get_softmax_name() + + return self.get_layer(x, name) + + def get_layer_names(self): + """ + :return: Names of all the layers kept by Keras + """ + layer_names = [x.name for x in self.model.layers] + return layer_names + + def fprop(self, x): + """ + Exposes all the layers of the model returned by get_layer_names. + :param x: A symbolic representation of the network input + :return: A dictionary mapping layer names to the symbolic + representation of their output. + """ + + if self.keras_model is None: + # Get the input layer + new_input = self.model.get_input_at(0) + + # Make a new model that returns each of the layers as output + abstract_layers = self._get_abstract_layer_name() + if abstract_layers: + warnings.warn( + "Abstract layer detected, picking last ouput node as default." + "This could happen due to using of stacked model." + ) + + layer_outputs = [] + # For those abstract model layers, return their last output node as + # default. + for x_layer in self.model.layers: + if x_layer.name not in abstract_layers: + layer_outputs.append(x_layer.output) + else: + layer_outputs.append(x_layer.get_output_at(-1)) + + self.keras_model = KerasModel(new_input, layer_outputs) + + # and get the outputs for that model on the input x + outputs = self.keras_model(x) + + # Keras only returns a list for outputs of length >= 1, if the model + # is only one layer, wrap a list + if len(self.model.layers) == 1: + outputs = [outputs] + + # compute the dict to return + fprop_dict = dict(zip(self.get_layer_names(), outputs)) + + return fprop_dict + + def get_layer(self, x, layer): + """ + Expose the hidden features of a model given a layer name. + :param x: A symbolic representation of the network input + :param layer: The name of the hidden layer to return features at. + :return: A symbolic representation of the hidden features + :raise: NoSuchLayerError if `layer` is not in the model. + """ + # Return the symbolic representation for this layer. + output = self.fprop(x) + try: + requested = output[layer] + except KeyError: + raise NoSuchLayerError() + return requested diff --git a/cleverhans_v3.1.0/cleverhans/utils_mnist.py b/cleverhans_v3.1.0/cleverhans/utils_mnist.py new file mode 100644 index 000000000..550616278 --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans/utils_mnist.py @@ -0,0 +1,46 @@ +# pylint: disable=missing-docstring +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import os +import tempfile +import warnings + +from cleverhans import dataset + +utils_mnist_warning = ( + "cleverhans.utils_mnist is deprecrated and will be " + "removed on or after 2019-03-26. Switch to " + "cleverhans.dataset instead." +) + + +def maybe_download_mnist_file(file_name, datadir=None, force=False): + warnings.warn(utils_mnist_warning) + url = os.path.join('https://storage.googleapis.com/cvdf-datasets/mnist/', file_name) + return dataset.maybe_download_file(url, datadir=None, force=False) + + +def download_and_parse_mnist_file(file_name, datadir=None, force=False): + warnings.warn(utils_mnist_warning) + return dataset.download_and_parse_mnist_file(file_name, datadir=None, force=False) + + +def data_mnist( + datadir=tempfile.gettempdir(), + train_start=0, + train_end=60000, + test_start=0, + test_end=10000, +): + warnings.warn(utils_mnist_warning) + mnist = dataset.MNIST( + train_start=train_start, + train_end=train_end, + test_start=test_start, + test_end=test_end, + center=False, + ) + return mnist.get_set("train") + mnist.get_set("test") diff --git a/cleverhans_v3.1.0/cleverhans/utils_pytorch.py b/cleverhans_v3.1.0/cleverhans/utils_pytorch.py new file mode 100644 index 000000000..e91e922da --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans/utils_pytorch.py @@ -0,0 +1,97 @@ +"""Basic utilities for pytorch code""" + +import warnings +from random import getrandbits + +import numpy as np +import tensorflow as tf +import torch +from torch.autograd import Variable + + +# https://gist.github.com/kingspp/3ec7d9958c13b94310c1a365759aa3f4 +# Pyfunc Gradient Function +def _py_func_with_gradient(func, inp, Tout, stateful=True, name=None, grad_func=None): + """ + PyFunc defined as given by Tensorflow + :param func: Custom Function + :param inp: Function Inputs + :param Tout: Ouput Type of out Custom Function + :param stateful: Calculate Gradients when stateful is True + :param name: Name of the PyFunction + :param grad: Custom Gradient Function + :return: + """ + # Generate random name in order to avoid conflicts with inbuilt names + rnd_name = "PyFuncGrad-" + "%0x" % getrandbits(30 * 4) + + # Register Tensorflow Gradient + tf.RegisterGradient(rnd_name)(grad_func) + + # Get current graph + g = tf.get_default_graph() + + # Add gradient override map + with g.gradient_override_map({"PyFunc": rnd_name, "PyFuncStateless": rnd_name}): + return tf.py_func(func, inp, Tout, stateful=stateful, name=name) + + +def convert_pytorch_model_to_tf(model, out_dims=None): + """ + Convert a pytorch model into a tensorflow op that allows backprop + :param model: A pytorch nn.Module object + :param out_dims: The number of output dimensions (classes) for the model + :return: A model function that maps an input (tf.Tensor) to the + output of the model (tf.Tensor) + """ + warnings.warn( + "convert_pytorch_model_to_tf is deprecated, switch to" + + " dedicated PyTorch support provided by CleverHans v4." + ) + + torch_state = { + "logits": None, + "x": None, + } + if not out_dims: + out_dims = list(model.modules())[-1].out_features + + def _fprop_fn(x_np): + """TODO: write this""" + x_tensor = torch.Tensor(x_np) + if torch.cuda.is_available(): + x_tensor = x_tensor.cuda() + torch_state["x"] = Variable(x_tensor, requires_grad=True) + torch_state["logits"] = model(torch_state["x"]) + return torch_state["logits"].data.cpu().numpy() + + def _bprop_fn(x_np, grads_in_np): + """TODO: write this""" + _fprop_fn(x_np) + + grads_in_tensor = torch.Tensor(grads_in_np) + if torch.cuda.is_available(): + grads_in_tensor = grads_in_tensor.cuda() + + # Run our backprop through our logits to our xs + loss = torch.sum(torch_state["logits"] * grads_in_tensor) + loss.backward() + return torch_state["x"].grad.cpu().data.numpy() + + def _tf_gradient_fn(op, grads_in): + """TODO: write this""" + return tf.py_func(_bprop_fn, [op.inputs[0], grads_in], Tout=[tf.float32]) + + def tf_model_fn(x_op): + """TODO: write this""" + out = _py_func_with_gradient( + _fprop_fn, + [x_op], + Tout=[tf.float32], + stateful=True, + grad_func=_tf_gradient_fn, + )[0] + out.set_shape([None, out_dims]) + return out + + return tf_model_fn diff --git a/cleverhans_v3.1.0/cleverhans/utils_tf.py b/cleverhans_v3.1.0/cleverhans/utils_tf.py new file mode 100644 index 000000000..2fd73d4ab --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans/utils_tf.py @@ -0,0 +1,903 @@ +"""Utility functions for writing TensorFlow code""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import logging +import math +import os +import time +import warnings + +import numpy as np +import six +from six.moves import xrange +import tensorflow as tf + +from cleverhans.compat import device_lib +from cleverhans.compat import reduce_sum, reduce_mean +from cleverhans.compat import reduce_max +from cleverhans.compat import softmax_cross_entropy_with_logits +from cleverhans.utils import batch_indices, _ArgsWrapper, create_logger + +_logger = create_logger("cleverhans.utils.tf") +_logger.setLevel(logging.INFO) + + +def model_loss(y, model, mean=True): + """ + Define loss of TF graph + :param y: correct labels + :param model: output of the model + :param mean: boolean indicating whether should return mean of loss + or vector of losses for each input of the batch + :return: return mean of loss if True, otherwise return vector with per + sample loss + """ + warnings.warn( + "This function is deprecated and will be removed on or after" + " 2019-04-05. Switch to cleverhans.train.train." + ) + op = model.op + if op.type == "Softmax": + (logits,) = op.inputs + else: + logits = model + + out = softmax_cross_entropy_with_logits(logits=logits, labels=y) + + if mean: + out = reduce_mean(out) + return out + + +def initialize_uninitialized_global_variables(sess): + """ + Only initializes the variables of a TensorFlow session that were not + already initialized. + :param sess: the TensorFlow session + :return: + """ + # List all global variables + global_vars = tf.global_variables() + + # Find initialized status for all variables + is_var_init = [tf.is_variable_initialized(var) for var in global_vars] + is_initialized = sess.run(is_var_init) + + # List all variables that were not initialized previously + not_initialized_vars = [ + var for (var, init) in zip(global_vars, is_initialized) if not init + ] + + # Initialize all uninitialized variables found, if any + if len(not_initialized_vars): + sess.run(tf.variables_initializer(not_initialized_vars)) + + +def train( + sess, + loss, + x, + y, + X_train, + Y_train, + save=False, + init_all=False, + evaluate=None, + feed=None, + args=None, + rng=None, + var_list=None, + fprop_args=None, + optimizer=None, +): + """ + Train a TF graph. + This function is deprecated. Prefer cleverhans.train.train when possible. + cleverhans.train.train supports multiple GPUs but this function is still + needed to support legacy models that do not support calling fprop more + than once. + + :param sess: TF session to use when training the graph + :param loss: tensor, the model training loss. + :param x: input placeholder + :param y: output placeholder (for labels) + :param X_train: numpy array with training inputs + :param Y_train: numpy array with training outputs + :param save: boolean controlling the save operation + :param init_all: (boolean) If set to true, all TF variables in the session + are (re)initialized, otherwise only previously + uninitialized variables are initialized before training. + :param evaluate: function that is run after each training iteration + (typically to display the test/validation accuracy). + :param feed: An optional dictionary that is appended to the feeding + dictionary before the session runs. Can be used to feed + the learning phase of a Keras model for instance. + :param args: dict or argparse `Namespace` object. + Should contain `nb_epochs`, `learning_rate`, + `batch_size` + If save is True, should also contain 'train_dir' + and 'filename' + :param rng: Instance of numpy.random.RandomState + :param var_list: Optional list of parameters to train. + :param fprop_args: dict, extra arguments to pass to fprop (loss and model). + :param optimizer: Optimizer to be used for training + :return: True if model trained + """ + warnings.warn( + "This function is deprecated and will be removed on or after" + " 2019-04-05. Switch to cleverhans.train.train." + ) + + args = _ArgsWrapper(args or {}) + fprop_args = fprop_args or {} + + # Check that necessary arguments were given (see doc above) + assert args.nb_epochs, "Number of epochs was not given in args dict" + if optimizer is None: + assert args.learning_rate is not None, ( + "Learning rate was not given " "in args dict" + ) + assert args.batch_size, "Batch size was not given in args dict" + + if save: + assert args.train_dir, "Directory for save was not given in args dict" + assert args.filename, "Filename for save was not given in args dict" + + if rng is None: + rng = np.random.RandomState() + + # Define optimizer + loss_value = loss.fprop(x, y, **fprop_args) + if optimizer is None: + optimizer = tf.train.AdamOptimizer(learning_rate=args.learning_rate) + else: + if not isinstance(optimizer, tf.train.Optimizer): + raise ValueError( + "optimizer object must be from a child class of " "tf.train.Optimizer" + ) + # Trigger update operations within the default graph (such as batch_norm). + with tf.control_dependencies(tf.get_collection(tf.GraphKeys.UPDATE_OPS)): + train_step = optimizer.minimize(loss_value, var_list=var_list) + + with sess.as_default(): + if hasattr(tf, "global_variables_initializer"): + if init_all: + tf.global_variables_initializer().run() + else: + initialize_uninitialized_global_variables(sess) + else: + warnings.warn( + "Update your copy of tensorflow; future versions of " + "CleverHans may drop support for this version." + ) + sess.run(tf.initialize_all_variables()) + + for epoch in xrange(args.nb_epochs): + # Compute number of batches + nb_batches = int(math.ceil(float(len(X_train)) / args.batch_size)) + assert nb_batches * args.batch_size >= len(X_train) + + # Indices to shuffle training set + index_shuf = list(range(len(X_train))) + rng.shuffle(index_shuf) + + prev = time.time() + for batch in range(nb_batches): + + # Compute batch start and end indices + start, end = batch_indices(batch, len(X_train), args.batch_size) + + # Perform one training step + feed_dict = { + x: X_train[index_shuf[start:end]], + y: Y_train[index_shuf[start:end]], + } + if feed is not None: + feed_dict.update(feed) + train_step.run(feed_dict=feed_dict) + assert end >= len(X_train) # Check that all examples were used + cur = time.time() + _logger.info( + "Epoch " + str(epoch) + " took " + str(cur - prev) + " seconds" + ) + if evaluate is not None: + evaluate() + + if save: + save_path = os.path.join(args.train_dir, args.filename) + saver = tf.train.Saver() + saver.save(sess, save_path) + _logger.info("Completed model training and saved at: " + str(save_path)) + else: + _logger.info("Completed model training.") + + return True + + +def model_eval(sess, x, y, predictions, X_test=None, Y_test=None, feed=None, args=None): + """ + Compute the accuracy of a TF model on some data + :param sess: TF session to use + :param x: input placeholder + :param y: output placeholder (for labels) + :param predictions: model output predictions + :param X_test: numpy array with training inputs + :param Y_test: numpy array with training outputs + :param feed: An optional dictionary that is appended to the feeding + dictionary before the session runs. Can be used to feed + the learning phase of a Keras model for instance. + :param args: dict or argparse `Namespace` object. + Should contain `batch_size` + :return: a float with the accuracy value + """ + global _model_eval_cache + args = _ArgsWrapper(args or {}) + + assert args.batch_size, "Batch size was not given in args dict" + if X_test is None or Y_test is None: + raise ValueError("X_test argument and Y_test argument " "must be supplied.") + + # Define accuracy symbolically + key = (y, predictions) + if key in _model_eval_cache: + correct_preds = _model_eval_cache[key] + else: + correct_preds = tf.equal(tf.argmax(y, axis=-1), tf.argmax(predictions, axis=-1)) + _model_eval_cache[key] = correct_preds + + # Init result var + accuracy = 0.0 + + with sess.as_default(): + # Compute number of batches + nb_batches = int(math.ceil(float(len(X_test)) / args.batch_size)) + assert nb_batches * args.batch_size >= len(X_test) + + X_cur = np.zeros((args.batch_size,) + X_test.shape[1:], dtype=X_test.dtype) + Y_cur = np.zeros((args.batch_size,) + Y_test.shape[1:], dtype=Y_test.dtype) + for batch in range(nb_batches): + if batch % 100 == 0 and batch > 0: + _logger.debug("Batch " + str(batch)) + + # Must not use the `batch_indices` function here, because it + # repeats some examples. + # It's acceptable to repeat during training, but not eval. + start = batch * args.batch_size + end = min(len(X_test), start + args.batch_size) + + # The last batch may be smaller than all others. This should not + # affect the accuarcy disproportionately. + cur_batch_size = end - start + X_cur[:cur_batch_size] = X_test[start:end] + Y_cur[:cur_batch_size] = Y_test[start:end] + feed_dict = {x: X_cur, y: Y_cur} + if feed is not None: + feed_dict.update(feed) + cur_corr_preds = correct_preds.eval(feed_dict=feed_dict) + + accuracy += cur_corr_preds[:cur_batch_size].sum() + + assert end >= len(X_test) + + # Divide by number of examples to get final value + accuracy /= len(X_test) + + return accuracy + + +_model_eval_cache = {} + + +def tf_model_load(sess, file_path=None): + """ + + :param sess: the session object to restore + :param file_path: path to the restored session, if None is + taken from FLAGS.train_dir and FLAGS.filename + :return: + """ + with sess.as_default(): + saver = tf.train.Saver() + if file_path is None: + error = "file_path argument is missing." + raise ValueError(error) + saver.restore(sess, file_path) + + return True + + +def batch_eval(*args, **kwargs): + """ + Wrapper around deprecated function. + """ + # Inside function to avoid circular import + from cleverhans.evaluation import batch_eval as new_batch_eval + + warnings.warn( + "batch_eval has moved to cleverhans.evaluation. " + "batch_eval will be removed from utils_tf on or after " + "2019-03-09." + ) + return new_batch_eval(*args, **kwargs) + + +def model_argmax(sess, x, predictions, samples, feed=None): + """ + Helper function that computes the current class prediction + :param sess: TF session + :param x: the input placeholder + :param predictions: the model's symbolic output + :param samples: numpy array with input samples (dims must match x) + :param feed: An optional dictionary that is appended to the feeding + dictionary before the session runs. Can be used to feed + the learning phase of a Keras model for instance. + :return: the argmax output of predictions, i.e. the current predicted class + """ + feed_dict = {x: samples} + if feed is not None: + feed_dict.update(feed) + probabilities = sess.run(predictions, feed_dict) + + if samples.shape[0] == 1: + return np.argmax(probabilities) + else: + return np.argmax(probabilities, axis=1) + + +def l2_batch_normalize(x, epsilon=1e-12, scope=None): + """ + Helper function to normalize a batch of vectors. + :param x: the input placeholder + :param epsilon: stabilizes division + :return: the batch of l2 normalized vector + """ + with tf.name_scope(scope, "l2_batch_normalize") as name_scope: + x_shape = tf.shape(x) + x = tf.contrib.layers.flatten(x) + x /= epsilon + reduce_max(tf.abs(x), 1, keepdims=True) + square_sum = reduce_sum(tf.square(x), 1, keepdims=True) + x_inv_norm = tf.rsqrt(np.sqrt(epsilon) + square_sum) + x_norm = tf.multiply(x, x_inv_norm) + return tf.reshape(x_norm, x_shape, name_scope) + + +def kl_with_logits( + p_logits, q_logits, scope=None, loss_collection=tf.GraphKeys.REGULARIZATION_LOSSES +): + """Helper function to compute kl-divergence KL(p || q)""" + with tf.name_scope(scope, "kl_divergence") as name: + p = tf.nn.softmax(p_logits) + p_log = tf.nn.log_softmax(p_logits) + q_log = tf.nn.log_softmax(q_logits) + loss = reduce_mean(reduce_sum(p * (p_log - q_log), axis=1), name=name) + tf.losses.add_loss(loss, loss_collection) + return loss + + +def clip_eta(eta, ord, eps): + """ + Helper function to clip the perturbation to epsilon norm ball. + :param eta: A tensor with the current perturbation. + :param ord: Order of the norm (mimics Numpy). + Possible values: np.inf, 1 or 2. + :param eps: Epsilon, bound of the perturbation. + """ + + # Clipping perturbation eta to self.ord norm ball + if ord not in [np.inf, 1, 2]: + raise ValueError("ord must be np.inf, 1, or 2.") + reduc_ind = list(xrange(1, len(eta.get_shape()))) + avoid_zero_div = 1e-12 + if ord == np.inf: + eta = clip_by_value(eta, -eps, eps) + elif ord == 1: + # Implements a projection algorithm onto the l1-ball from + # (Duchi et al. 2008) that runs in time O(d*log(d)) where d is the + # input dimension. + # Paper link (Duchi et al. 2008): https://dl.acm.org/citation.cfm?id=1390191 + + eps = tf.cast(eps, eta.dtype) + + dim = tf.reduce_prod(tf.shape(eta)[1:]) + eta_flat = tf.reshape(eta, (-1, dim)) + abs_eta = tf.abs(eta_flat) + + if "sort" in dir(tf): + mu = -tf.sort(-abs_eta, axis=-1) + else: + # `tf.sort` is only available in TF 1.13 onwards + mu = tf.nn.top_k(abs_eta, k=dim, sorted=True)[0] + cumsums = tf.cumsum(mu, axis=-1) + js = tf.cast(tf.divide(1, tf.range(1, dim + 1)), eta.dtype) + t = tf.cast(tf.greater(mu - js * (cumsums - eps), 0), eta.dtype) + + rho = tf.argmax(t * cumsums, axis=-1) + rho_val = tf.reduce_max(t * cumsums, axis=-1) + theta = tf.divide(rho_val - eps, tf.cast(1 + rho, eta.dtype)) + + eta_sgn = tf.sign(eta_flat) + eta_proj = eta_sgn * tf.maximum(abs_eta - theta[:, tf.newaxis], 0) + eta_proj = tf.reshape(eta_proj, tf.shape(eta)) + + norm = tf.reduce_sum(tf.abs(eta), reduc_ind) + eta = tf.where(tf.greater(norm, eps), eta_proj, eta) + + elif ord == 2: + # avoid_zero_div must go inside sqrt to avoid a divide by zero + # in the gradient through this operation + norm = tf.sqrt( + tf.maximum( + avoid_zero_div, reduce_sum(tf.square(eta), reduc_ind, keepdims=True) + ) + ) + # We must *clip* to within the norm ball, not *normalize* onto the + # surface of the ball + factor = tf.minimum(1.0, div(eps, norm)) + eta = eta * factor + return eta + + +def zero_out_clipped_grads(grad, x, clip_min, clip_max): + """ + Helper function to erase entries in the gradient where the update would be + clipped. + :param grad: The gradient + :param x: The current input + :param clip_min: Minimum input component value + :param clip_max: Maximum input component value + """ + signed_grad = tf.sign(grad) + + # Find input components that lie at the boundary of the input range, and + # where the gradient points in the wrong direction. + clip_low = tf.logical_and( + tf.less_equal(x, tf.cast(clip_min, x.dtype)), tf.less(signed_grad, 0) + ) + clip_high = tf.logical_and( + tf.greater_equal(x, tf.cast(clip_max, x.dtype)), tf.greater(signed_grad, 0) + ) + clip = tf.logical_or(clip_low, clip_high) + grad = tf.where(clip, mul(grad, 0), grad) + + return grad + + +def random_exponential(shape, rate=1.0, dtype=tf.float32, seed=None): + """ + Helper function to sample from the exponential distribution, which is not + included in core TensorFlow. + + :shape: shape of the sampled tensor. + :rate: (optional) rate parameter of the exponential distribution, defaults to 1.0. + :dtype: (optional) data type of the sempled tensor, defaults to tf.float32. + :seed: (optional) custom seed to be used for sampling. + """ + return tf.random_gamma(shape, alpha=1, beta=1.0 / rate, dtype=dtype, seed=seed) + + +def random_laplace(shape, loc=0.0, scale=1.0, dtype=tf.float32, seed=None): + """ + Helper function to sample from the Laplace distribution, which is not + included in core TensorFlow. + + :shape: shape of the sampled tensor. + :loc: (optional) mean of the laplace distribution, defaults to 0.0. + :scale: (optional) scale parameter of the laplace diustribution, defaults to 1.0. + :dtype: (optional) data type of the sempled tensor, defaults to tf.float32. + :seed: (optional) custom seed to be used for sampling. + """ + z1 = random_exponential(shape, 1.0 / scale, dtype=dtype, seed=seed) + z2 = random_exponential(shape, 1.0 / scale, dtype=dtype, seed=seed) + return z1 - z2 + loc + + +def random_lp_vector(shape, ord, eps, dtype=tf.float32, seed=None): + """ + Helper function to generate uniformly random vectors from a norm ball of + radius epsilon. + :param shape: Output shape of the random sample. The shape is expected to be + of the form `(n, d1, d2, ..., dn)` where `n` is the number of + i.i.d. samples that will be drawn from a norm ball of dimension + `d1*d1*...*dn`. + :param ord: Order of the norm (mimics Numpy). + Possible values: np.inf, 1 or 2. + :param eps: Epsilon, radius of the norm ball. + """ + if ord not in [np.inf, 1, 2]: + raise ValueError("ord must be np.inf, 1, or 2.") + + if ord == np.inf: + r = tf.random_uniform(shape, -eps, eps, dtype=dtype, seed=seed) + else: + + # For ord=1 and ord=2, we use the generic technique from + # (Calafiore et al. 1998) to sample uniformly from a norm ball. + # Paper link (Calafiore et al. 1998): + # https://ieeexplore.ieee.org/stamp/stamp.jsp?arnumber=758215&tag=1 + # We first sample from the surface of the norm ball, and then scale by + # a factor `w^(1/d)` where `w~U[0,1]` is a standard uniform random variable + # and `d` is the dimension of the ball. In high dimensions, this is roughly + # equivalent to sampling from the surface of the ball. + + dim = tf.reduce_prod(shape[1:]) + + if ord == 1: + x = random_laplace( + (shape[0], dim), loc=1.0, scale=1.0, dtype=dtype, seed=seed + ) + norm = tf.reduce_sum(tf.abs(x), axis=-1, keepdims=True) + elif ord == 2: + x = tf.random_normal((shape[0], dim), dtype=dtype, seed=seed) + norm = tf.sqrt(tf.reduce_sum(tf.square(x), axis=-1, keepdims=True)) + else: + raise ValueError("ord must be np.inf, 1, or 2.") + + w = tf.pow( + tf.random.uniform((shape[0], 1), dtype=dtype, seed=seed), + 1.0 / tf.cast(dim, dtype), + ) + r = eps * tf.reshape(w * x / norm, shape) + + return r + + +def model_train( + sess, + x, + y, + predictions, + X_train, + Y_train, + save=False, + predictions_adv=None, + init_all=True, + evaluate=None, + feed=None, + args=None, + rng=None, + var_list=None, +): + """ + Train a TF graph + :param sess: TF session to use when training the graph + :param x: input placeholder + :param y: output placeholder (for labels) + :param predictions: model output predictions + :param X_train: numpy array with training inputs + :param Y_train: numpy array with training outputs + :param save: boolean controlling the save operation + :param predictions_adv: if set with the adversarial example tensor, + will run adversarial training + :param init_all: (boolean) If set to true, all TF variables in the session + are (re)initialized, otherwise only previously + uninitialized variables are initialized before training. + :param evaluate: function that is run after each training iteration + (typically to display the test/validation accuracy). + :param feed: An optional dictionary that is appended to the feeding + dictionary before the session runs. Can be used to feed + the learning phase of a Keras model for instance. + :param args: dict or argparse `Namespace` object. + Should contain `nb_epochs`, `learning_rate`, + `batch_size` + If save is True, should also contain 'train_dir' + and 'filename' + :param rng: Instance of numpy.random.RandomState + :param var_list: Optional list of parameters to train. + :return: True if model trained + """ + warnings.warn( + "This function is deprecated and will be removed on or after" + " 2019-04-05. Switch to cleverhans.train.train." + ) + args = _ArgsWrapper(args or {}) + + # Check that necessary arguments were given (see doc above) + assert args.nb_epochs, "Number of epochs was not given in args dict" + assert args.learning_rate, "Learning rate was not given in args dict" + assert args.batch_size, "Batch size was not given in args dict" + + if save: + assert args.train_dir, "Directory for save was not given in args dict" + assert args.filename, "Filename for save was not given in args dict" + + if rng is None: + rng = np.random.RandomState() + + # Define loss + loss = model_loss(y, predictions) + if predictions_adv is not None: + loss = (loss + model_loss(y, predictions_adv)) / 2 + + train_step = tf.train.AdamOptimizer(learning_rate=args.learning_rate) + train_step = train_step.minimize(loss, var_list=var_list) + + with sess.as_default(): + if hasattr(tf, "global_variables_initializer"): + if init_all: + tf.global_variables_initializer().run() + else: + initialize_uninitialized_global_variables(sess) + else: + warnings.warn( + "Update your copy of tensorflow; future versions of " + "CleverHans may drop support for this version." + ) + sess.run(tf.initialize_all_variables()) + + for epoch in xrange(args.nb_epochs): + # Compute number of batches + nb_batches = int(math.ceil(float(len(X_train)) / args.batch_size)) + assert nb_batches * args.batch_size >= len(X_train) + + # Indices to shuffle training set + index_shuf = list(range(len(X_train))) + rng.shuffle(index_shuf) + + prev = time.time() + for batch in range(nb_batches): + + # Compute batch start and end indices + start, end = batch_indices(batch, len(X_train), args.batch_size) + + # Perform one training step + feed_dict = { + x: X_train[index_shuf[start:end]], + y: Y_train[index_shuf[start:end]], + } + if feed is not None: + feed_dict.update(feed) + train_step.run(feed_dict=feed_dict) + assert end >= len(X_train) # Check that all examples were used + cur = time.time() + _logger.info( + "Epoch " + str(epoch) + " took " + str(cur - prev) + " seconds" + ) + if evaluate is not None: + evaluate() + + if save: + save_path = os.path.join(args.train_dir, args.filename) + saver = tf.train.Saver() + saver.save(sess, save_path) + _logger.info("Completed model training and saved at: " + str(save_path)) + else: + _logger.info("Completed model training.") + + return True + + +def infer_devices(devices=None): + """ + Returns the list of devices that multi-replica code should use. + :param devices: list of string device names, e.g. ["/GPU:0"] + If the user specifies this, `infer_devices` checks that it is + valid, and then uses this user-specified list. + If the user does not specify this, infer_devices uses: + - All available GPUs, if there are any + - CPU otherwise + """ + if devices is None: + devices = get_available_gpus() + if len(devices) == 0: + warnings.warn("No GPUS, running on CPU") + # Set device to empy string, tf will figure out whether to use + # XLA or not, etc., automatically + devices = [""] + else: + assert len(devices) > 0 + for device in devices: + assert isinstance(device, six.string_types), type(device) + return devices + + +def get_available_gpus(): + """ + Returns a list of string names of all available GPUs + """ + local_device_protos = device_lib.list_local_devices() + return [x.name for x in local_device_protos if x.device_type == "GPU"] + + +def silence(): + """ + Silences tensorflaw's default printed messages + """ + os.environ["TF_CPP_MIN_LOG_LEVEL"] = "3" + + +def clip_by_value(t, clip_value_min, clip_value_max, name=None): + """ + A wrapper for clip_by_value that casts the clipping range if needed. + """ + + def cast_clip(clip): + """ + Cast clipping range argument if needed. + """ + if t.dtype in (tf.float32, tf.float64): + if hasattr(clip, "dtype"): + # Convert to tf dtype in case this is a numpy dtype + clip_dtype = tf.as_dtype(clip.dtype) + if clip_dtype != t.dtype: + return tf.cast(clip, t.dtype) + return clip + + clip_value_min = cast_clip(clip_value_min) + clip_value_max = cast_clip(clip_value_max) + + return tf.clip_by_value(t, clip_value_min, clip_value_max, name) + + +def mul(a, b): + """ + A wrapper around tf multiplication that does more automatic casting of + the input. + """ + + def multiply(a, b): + """Multiplication""" + return a * b + + return op_with_scalar_cast(a, b, multiply) + + +def div(a, b): + """ + A wrapper around tf division that does more automatic casting of + the input. + """ + + def divide(a, b): + """Division""" + return a / b + + return op_with_scalar_cast(a, b, divide) + + +def op_with_scalar_cast(a, b, f): + """ + Builds the graph to compute f(a, b). + If only one of the two arguments is a scalar and the operation would + cause a type error without casting, casts the scalar to match the + tensor. + :param a: a tf-compatible array or scalar + :param b: a tf-compatible array or scalar + """ + + try: + return f(a, b) + except (TypeError, ValueError): + pass + + def is_scalar(x): + """Return True if `x` is a scalar""" + if hasattr(x, "get_shape"): + shape = x.get_shape() + return shape.ndims == 0 + if hasattr(x, "ndim"): + return x.ndim == 0 + assert isinstance(x, (int, float)) + return True + + a_scalar = is_scalar(a) + b_scalar = is_scalar(b) + + if a_scalar and b_scalar: + raise TypeError("Trying to apply " + str(f) + " with mixed types") + + if a_scalar and not b_scalar: + a = tf.cast(a, b.dtype) + + if b_scalar and not a_scalar: + b = tf.cast(b, a.dtype) + + return f(a, b) + + +def assert_less_equal(*args, **kwargs): + """ + Wrapper for tf.assert_less_equal + Overrides tf.device so that the assert always goes on CPU. + The unwrapped version raises an exception if used with tf.device("/GPU:x"). + """ + with tf.device("/CPU:0"): + return tf.assert_less_equal(*args, **kwargs) + + +def assert_greater_equal(*args, **kwargs): + """ + Wrapper for tf.assert_greater_equal. + Overrides tf.device so that the assert always goes on CPU. + The unwrapped version raises an exception if used with tf.device("/GPU:x"). + """ + with tf.device("/CPU:0"): + return tf.assert_greater_equal(*args, **kwargs) + + +def assert_equal(*args, **kwargs): + """ + Wrapper for tf.assert_equal. + Overrides tf.device so that the assert always goes on CPU. + The unwrapped version raises an exception if used with tf.device("/GPU:x"). + """ + with tf.device("/CPU:0"): + return tf.assert_equal(*args, **kwargs) + + +def jacobian_graph(predictions, x, nb_classes): + """ + Create the Jacobian graph to be ran later in a TF session + :param predictions: the model's symbolic output (linear output, + pre-softmax) + :param x: the input placeholder + :param nb_classes: the number of classes the model has + :return: + """ + + # This function will return a list of TF gradients + list_derivatives = [] + + # Define the TF graph elements to compute our derivatives for each class + for class_ind in xrange(nb_classes): + (derivatives,) = tf.gradients(predictions[:, class_ind], x) + list_derivatives.append(derivatives) + + return list_derivatives + + +def jacobian_augmentation( + sess, x, X_sub_prev, Y_sub, grads, lmbda, aug_batch_size=512, feed=None +): + """ + Augment an adversary's substitute training set using the Jacobian + of a substitute model to generate new synthetic inputs. + See https://arxiv.org/abs/1602.02697 for more details. + See cleverhans_tutorials/mnist_blackbox.py for example use case + :param sess: TF session in which the substitute model is defined + :param x: input TF placeholder for the substitute model + :param X_sub_prev: substitute training data available to the adversary + at the previous iteration + :param Y_sub: substitute training labels available to the adversary + at the previous iteration + :param grads: Jacobian symbolic graph for the substitute + (should be generated using utils_tf.jacobian_graph) + :return: augmented substitute data (will need to be labeled by oracle) + """ + assert len(x.get_shape()) == len(np.shape(X_sub_prev)) + assert len(grads) >= np.max(Y_sub) + 1 + assert len(X_sub_prev) == len(Y_sub) + + aug_batch_size = min(aug_batch_size, X_sub_prev.shape[0]) + + # Prepare input_shape (outside loop) for feeding dictionary below + input_shape = list(x.get_shape()) + input_shape[0] = 1 + + # Create new numpy array for adversary training data + # with twice as many components on the first dimension. + X_sub = np.vstack([X_sub_prev, X_sub_prev]) + num_samples = X_sub_prev.shape[0] + + # Creating and processing as batch + for p_idxs in range(0, num_samples, aug_batch_size): + X_batch = X_sub_prev[p_idxs : p_idxs + aug_batch_size, ...] + feed_dict = {x: X_batch} + if feed is not None: + feed_dict.update(feed) + + # Compute sign matrix + grad_val = sess.run([tf.sign(grads)], feed_dict=feed_dict)[0] + + # Create new synthetic point in adversary substitute training set + for (indx, ind) in zip( + range(p_idxs, p_idxs + X_batch.shape[0]), range(X_batch.shape[0]) + ): + X_sub[num_samples + indx] = ( + X_batch[ind] + lmbda * grad_val[Y_sub[indx], ind, ...] + ) + + # Return augmented training data (needs to be labeled afterwards) + return X_sub diff --git a/cleverhans_v3.1.0/cleverhans/utils_tfe.py b/cleverhans_v3.1.0/cleverhans/utils_tfe.py new file mode 100644 index 000000000..1ed303e96 --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans/utils_tfe.py @@ -0,0 +1,220 @@ +""" +Utility functions for writing tf eager code +""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import math +import os +import time + +import numpy as np +import tensorflow as tf +from six.moves import xrange + +from cleverhans.loss import LossCrossEntropy +from cleverhans.model import Model +from .utils import batch_indices, _ArgsWrapper, create_logger + +_logger = create_logger("cleverhans.utils.tfe") + + +def train( + model, + X_train=None, + Y_train=None, + save=False, + predictions_adv=None, + evaluate=None, + args=None, + rng=None, + var_list=None, + attack=None, + attack_args=None, +): + """ + Train a TF Eager model + :param model: cleverhans.model.Model + :param X_train: numpy array with training inputs + :param Y_train: numpy array with training outputs + :param save: boolean controlling the save operation + :param predictions_adv: if set with the adversarial example tensor, + will run adversarial training + :param evaluate: function that is run after each training iteration + (typically to display the test/validation accuracy). + :param args: dict or argparse `Namespace` object. + Should contain `nb_epochs`, `learning_rate`, + `batch_size` + If save is True, should also contain 'train_dir' + and 'filename' + :param rng: Instance of numpy.random.RandomState + :param var_list: List of variables to train. + :param attack: Instance of the class cleverhans.attacks.attacks_eager + :param attack_args: Parameters required for the attack. + :return: True if model trained + """ + assert isinstance(model, Model) + args = _ArgsWrapper(args or {}) + if (attack is None) != (attack_args is None): + raise ValueError("attack and attack_args must be " "passed together.") + if X_train is None or Y_train is None: + raise ValueError("X_train argument and Y_train argument " "must be supplied.") + # Check that necessary arguments were given (see doc above) + assert args.nb_epochs, "Number of epochs was not given in args dict" + assert args.learning_rate, "Learning rate was not given in args dict" + assert args.batch_size, "Batch size was not given in args dict" + + if save: + assert args.train_dir, "Directory for save was not given in args dict" + assert args.filename, "Filename for save was not given in args dict" + + if rng is None: + rng = np.random.RandomState() + + # Optimizer + tfe = tf.contrib.eager + optimizer = tf.train.AdamOptimizer(learning_rate=args.learning_rate) + batch_x = tfe.Variable(X_train[0 : args.batch_size], dtype=tf.float32) + batch_y = tfe.Variable(Y_train[0 : args.batch_size], dtype=tf.float32) + + # One epoch of training. + for epoch in xrange(args.nb_epochs): + # Compute number of batches + nb_batches = int(math.ceil(float(len(X_train)) / args.batch_size)) + assert nb_batches * args.batch_size >= len(X_train) + + # Indices to shuffle training set + index_shuf = list(range(len(X_train))) + rng.shuffle(index_shuf) + + prev = time.time() + for batch in range(nb_batches): + + # Compute batch start and end indices + start, end = batch_indices(batch, len(X_train), args.batch_size) + + # Perform one training step + tf.assign(batch_x, X_train[index_shuf[start:end]]) + tf.assign(batch_y, Y_train[index_shuf[start:end]]) + # Compute grads + with tf.GradientTape() as tape: + # Define loss + loss_clean_obj = LossCrossEntropy(model, smoothing=0.0) + loss_clean = loss_clean_obj.fprop(x=batch_x, y=batch_y) + loss = loss_clean + # Adversarial training + if attack is not None: + batch_adv_x = attack.generate(batch_x, **attack_args) + loss_adv_obj = LossCrossEntropy(model, smoothing=0.0) + loss_adv = loss_adv_obj.fprop(x=batch_adv_x, y=batch_y) + loss = (loss_clean + loss_adv) / 2.0 + # Apply grads + model_variables = model.get_params() + grads = tape.gradient(loss, model_variables) + optimizer.apply_gradients(zip(grads, model_variables)) + + assert end >= len(X_train) # Check that all examples were used + cur = time.time() + _logger.info("Epoch " + str(epoch) + " took " + str(cur - prev) + " seconds") + if evaluate is not None: + evaluate() + + if save: + save_path = os.path.join(args.train_dir, args.filename) + saver = tf.train.Saver() + saver.save(save_path, model_variables) + _logger.info("Completed model training and saved at: " + str(save_path)) + else: + _logger.info("Completed model training.") + + return True + + +def model_eval( + model, X_test=None, Y_test=None, args=None, attack=None, attack_args=None +): + """ + Compute the accuracy of a TF Eager model on some data + :param model: instance of cleverhans.model.Model_Eager + with pretrained weights for evaluation. + :param X_test: numpy array with training inputs + :param Y_test: numpy array with training outputs + :param args: dict or argparse `Namespace` object. + Should contain `batch_size` + :param attack: instance of the class cleverhans.attacks.attacks_eager + :param attack_args: parameters required for the attack. + :return: a float with the accuracy value + """ + args = _ArgsWrapper(args or {}) + + if (attack is None) != (attack_args is None): + raise ValueError("attack and attack_args must be " "passed together.") + assert args.batch_size, "Batch size was not given in args dict" + if X_test is None or Y_test is None: + raise ValueError("X_test argument and Y_test argument " "must be supplied.") + + # Init result var + accuracy = 0.0 + + # Compute number of batches + nb_batches = int(math.ceil(float(len(X_test)) / args.batch_size)) + assert nb_batches * args.batch_size >= len(X_test) + + X_cur = np.zeros((args.batch_size,) + X_test.shape[1:], dtype=X_test.dtype) + Y_cur = np.zeros((args.batch_size,) + Y_test.shape[1:], dtype=Y_test.dtype) + + tfe = tf.contrib.eager + batch_x = tfe.Variable(X_test[0 : args.batch_size], dtype=tf.float32) + batch_y = tfe.Variable(Y_test[0 : args.batch_size], dtype=tf.float32) + for batch in range(nb_batches): + if batch % 100 == 0 and batch > 0: + _logger.debug("Batch " + str(batch)) + + # Must not use the `batch_indices` function here, because it + # repeats some examples. + # It's acceptable to repeat during training, but not eval. + start = batch * args.batch_size + end = min(len(X_test), start + args.batch_size) + + # The last batch may be smaller than all others. This should not + # affect the accuarcy disproportionately. + cur_batch_size = end - start + X_cur[:cur_batch_size] = X_test[start:end] + Y_cur[:cur_batch_size] = Y_test[start:end] + tf.assign(batch_x, X_cur) + tf.assign(batch_y, Y_cur) + if attack is not None: + batch_adv_x = attack.generate(batch_x, **attack_args) + predictions = model.get_probs(batch_adv_x) + else: + predictions = model.get_probs(batch_x) + cur_corr_preds = tf.equal( + tf.argmax(batch_y, axis=-1), tf.argmax(predictions, axis=-1) + ) + + accuracy += cur_corr_preds.numpy()[:cur_batch_size].sum() + + assert end >= len(X_test) + + # Divide by number of examples to get final value + accuracy /= len(X_test) + + return accuracy + + +def model_argmax(model, samples): + """ + Helper function that computes the current class prediction + :param samples: numpy array with input samples (dims must match x) + :return: the argmax output of predictions, i.e. the current predicted class + """ + tfe = tf.contrib.eager + tf_samples = tfe.Variable(samples) + probabilities = model.get_probs(tf_samples) + + if samples.shape[0] == 1: + return tf.argmax(probabilities) + else: + return tf.argmax(probabilities, axis=1) diff --git a/cleverhans_v3.1.0/cleverhans_tutorials/__init__.py b/cleverhans_v3.1.0/cleverhans_tutorials/__init__.py new file mode 100644 index 000000000..367691aee --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans_tutorials/__init__.py @@ -0,0 +1,26 @@ +""" +The CleverHans tutorials. +While mostly designed to be run as standalone scripts, the tutorials together also form an importable module. +Module importation is mostly intended to support writing unit tests of the tutorials themselves, etc. +The tutorial code is not part of our API contract and can change rapidly without warning. +""" +import os +import warnings + +import cleverhans + + +def check_installation(cur_file): + """Warn user if running cleverhans from a different directory than tutorial.""" + cur_dir = os.path.split(os.path.dirname(os.path.abspath(cur_file)))[0] + ch_dir = os.path.split(cleverhans.__path__[0])[0] + if cur_dir != ch_dir: + warnings.warn( + "It appears that you have at least two versions of " + "cleverhans installed, one at %s and one at" + " %s. You are running the tutorial script from the " + "former but python imported the library module from the " + "latter. This may cause errors, for example if the tutorial" + " version is newer than the library version and attempts to" + " call new features." % (cur_dir, ch_dir) + ) diff --git a/cleverhans_v3.1.0/cleverhans_tutorials/cifar10_tutorial_tf.py b/cleverhans_v3.1.0/cleverhans_tutorials/cifar10_tutorial_tf.py new file mode 100644 index 000000000..200574847 --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans_tutorials/cifar10_tutorial_tf.py @@ -0,0 +1,258 @@ +""" +This tutorial shows how to generate adversarial examples using FGSM +and train a model using adversarial training with TensorFlow. +The original paper can be found at: +https://arxiv.org/abs/1412.6572 +""" +# pylint: disable=missing-docstring +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import logging +import numpy as np +import tensorflow as tf + +from cleverhans.attacks import FastGradientMethod +from cleverhans.augmentation import random_horizontal_flip, random_shift +from cleverhans.compat import flags +from cleverhans.dataset import CIFAR10 +from cleverhans.loss import CrossEntropy +from cleverhans.model_zoo.all_convolutional import ModelAllConvolutional +from cleverhans.train import train +from cleverhans.utils import AccuracyReport, set_log_level +from cleverhans.utils_tf import model_eval + +FLAGS = flags.FLAGS + +NB_EPOCHS = 6 +BATCH_SIZE = 128 +LEARNING_RATE = 0.001 +CLEAN_TRAIN = True +BACKPROP_THROUGH_ATTACK = False +NB_FILTERS = 64 + + +def cifar10_tutorial( + train_start=0, + train_end=60000, + test_start=0, + test_end=10000, + nb_epochs=NB_EPOCHS, + batch_size=BATCH_SIZE, + learning_rate=LEARNING_RATE, + clean_train=CLEAN_TRAIN, + testing=False, + backprop_through_attack=BACKPROP_THROUGH_ATTACK, + nb_filters=NB_FILTERS, + num_threads=None, + label_smoothing=0.1, +): + """ + CIFAR10 cleverhans tutorial + :param train_start: index of first training set example + :param train_end: index of last training set example + :param test_start: index of first test set example + :param test_end: index of last test set example + :param nb_epochs: number of epochs to train model + :param batch_size: size of training batches + :param learning_rate: learning rate for training + :param clean_train: perform normal training on clean examples only + before performing adversarial training. + :param testing: if true, complete an AccuracyReport for unit tests + to verify that performance is adequate + :param backprop_through_attack: If True, backprop through adversarial + example construction process during + adversarial training. + :param label_smoothing: float, amount of label smoothing for cross entropy + :return: an AccuracyReport object + """ + + # Object used to keep track of (and return) key accuracies + report = AccuracyReport() + + # Set TF random seed to improve reproducibility + tf.set_random_seed(1234) + + # Set logging level to see debug information + set_log_level(logging.DEBUG) + + # Create TF session + if num_threads: + config_args = dict(intra_op_parallelism_threads=1) + else: + config_args = {} + sess = tf.Session(config=tf.ConfigProto(**config_args)) + + # Get CIFAR10 data + data = CIFAR10( + train_start=train_start, + train_end=train_end, + test_start=test_start, + test_end=test_end, + ) + dataset_size = data.x_train.shape[0] + dataset_train = data.to_tensorflow()[0] + dataset_train = dataset_train.map( + lambda x, y: (random_shift(random_horizontal_flip(x)), y), 4 + ) + dataset_train = dataset_train.batch(batch_size) + dataset_train = dataset_train.prefetch(16) + x_train, y_train = data.get_set("train") + x_test, y_test = data.get_set("test") + + # Use Image Parameters + img_rows, img_cols, nchannels = x_test.shape[1:4] + nb_classes = y_test.shape[1] + + # Define input TF placeholder + x = tf.placeholder(tf.float32, shape=(None, img_rows, img_cols, nchannels)) + y = tf.placeholder(tf.float32, shape=(None, nb_classes)) + + # Train an MNIST model + train_params = { + "nb_epochs": nb_epochs, + "batch_size": batch_size, + "learning_rate": learning_rate, + } + eval_params = {"batch_size": batch_size} + fgsm_params = {"eps": 0.3, "clip_min": 0.0, "clip_max": 1.0} + rng = np.random.RandomState([2017, 8, 30]) + + def do_eval(preds, x_set, y_set, report_key, is_adv=None): + acc = model_eval(sess, x, y, preds, x_set, y_set, args=eval_params) + setattr(report, report_key, acc) + if is_adv is None: + report_text = None + elif is_adv: + report_text = "adversarial" + else: + report_text = "legitimate" + if report_text: + print("Test accuracy on %s examples: %0.4f" % (report_text, acc)) + + if clean_train: + model = ModelAllConvolutional( + "model1", nb_classes, nb_filters, input_shape=[32, 32, 3] + ) + preds = model.get_logits(x) + loss = CrossEntropy(model, smoothing=label_smoothing) + + def evaluate(): + do_eval(preds, x_test, y_test, "clean_train_clean_eval", False) + + train( + sess, + loss, + None, + None, + dataset_train=dataset_train, + dataset_size=dataset_size, + evaluate=evaluate, + args=train_params, + rng=rng, + var_list=model.get_params(), + ) + + # Calculate training error + if testing: + do_eval(preds, x_train, y_train, "train_clean_train_clean_eval") + + # Initialize the Fast Gradient Sign Method (FGSM) attack object and + # graph + fgsm = FastGradientMethod(model, sess=sess) + adv_x = fgsm.generate(x, **fgsm_params) + preds_adv = model.get_logits(adv_x) + + # Evaluate the accuracy of the MNIST model on adversarial examples + do_eval(preds_adv, x_test, y_test, "clean_train_adv_eval", True) + + # Calculate training error + if testing: + do_eval(preds_adv, x_train, y_train, "train_clean_train_adv_eval") + + print("Repeating the process, using adversarial training") + + # Create a new model and train it to be robust to FastGradientMethod + model2 = ModelAllConvolutional( + "model2", nb_classes, nb_filters, input_shape=[32, 32, 3] + ) + fgsm2 = FastGradientMethod(model2, sess=sess) + + def attack(x): + return fgsm2.generate(x, **fgsm_params) + + loss2 = CrossEntropy(model2, smoothing=label_smoothing, attack=attack) + preds2 = model2.get_logits(x) + adv_x2 = attack(x) + + if not backprop_through_attack: + # For the fgsm attack used in this tutorial, the attack has zero + # gradient so enabling this flag does not change the gradient. + # For some other attacks, enabling this flag increases the cost of + # training, but gives the defender the ability to anticipate how + # the atacker will change their strategy in response to updates to + # the defender's parameters. + adv_x2 = tf.stop_gradient(adv_x2) + preds2_adv = model2.get_logits(adv_x2) + + def evaluate2(): + # Accuracy of adversarially trained model on legitimate test inputs + do_eval(preds2, x_test, y_test, "adv_train_clean_eval", False) + # Accuracy of the adversarially trained model on adversarial examples + do_eval(preds2_adv, x_test, y_test, "adv_train_adv_eval", True) + + # Perform and evaluate adversarial training + train( + sess, + loss2, + None, + None, + dataset_train=dataset_train, + dataset_size=dataset_size, + evaluate=evaluate2, + args=train_params, + rng=rng, + var_list=model2.get_params(), + ) + + # Calculate training errors + if testing: + do_eval(preds2, x_train, y_train, "train_adv_train_clean_eval") + do_eval(preds2_adv, x_train, y_train, "train_adv_train_adv_eval") + + return report + + +def main(argv=None): + from cleverhans_tutorials import check_installation + + check_installation(__file__) + + cifar10_tutorial( + nb_epochs=FLAGS.nb_epochs, + batch_size=FLAGS.batch_size, + learning_rate=FLAGS.learning_rate, + clean_train=FLAGS.clean_train, + backprop_through_attack=FLAGS.backprop_through_attack, + nb_filters=FLAGS.nb_filters, + ) + + +if __name__ == "__main__": + flags.DEFINE_integer("nb_filters", NB_FILTERS, "Model size multiplier") + flags.DEFINE_integer("nb_epochs", NB_EPOCHS, "Number of epochs to train model") + flags.DEFINE_integer("batch_size", BATCH_SIZE, "Size of training batches") + flags.DEFINE_float("learning_rate", LEARNING_RATE, "Learning rate for training") + flags.DEFINE_bool("clean_train", CLEAN_TRAIN, "Train on clean examples") + flags.DEFINE_bool( + "backprop_through_attack", + BACKPROP_THROUGH_ATTACK, + ( + "If True, backprop through adversarial example " + "construction process during adversarial training" + ), + ) + + tf.app.run() diff --git a/cleverhans_v3.1.0/cleverhans_tutorials/evaluate_pickled_model.py b/cleverhans_v3.1.0/cleverhans_tutorials/evaluate_pickled_model.py new file mode 100644 index 000000000..b9de18e38 --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans_tutorials/evaluate_pickled_model.py @@ -0,0 +1,114 @@ +""" +This script evaluates trained models that have been saved to the filesystem. +See mnist_tutorial_picklable.py for instructions. +""" +# pylint: disable=missing-docstring +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import logging + +import tensorflow as tf + +from cleverhans.compat import flags +from cleverhans.dataset import MNIST +from cleverhans.attacks import FastGradientMethod +from cleverhans.utils import set_log_level +from cleverhans.utils_tf import model_eval, silence +from cleverhans.serial import load + +silence() + +FLAGS = flags.FLAGS + + +def evaluate_model( + filepath, + train_start=0, + train_end=60000, + test_start=0, + test_end=10000, + batch_size=128, + testing=False, + num_threads=None, +): + """ + Run evaluation on a saved model + :param filepath: path to model to evaluate + :param train_start: index of first training set example + :param train_end: index of last training set example + :param test_start: index of first test set example + :param test_end: index of last test set example + :param batch_size: size of evaluation batches + """ + + # Set TF random seed to improve reproducibility + tf.set_random_seed(1234) + + # Set logging level to see debug information + set_log_level(logging.INFO) + + # Create TF session + if num_threads: + config_args = dict(intra_op_parallelism_threads=1) + else: + config_args = {} + sess = tf.Session(config=tf.ConfigProto(**config_args)) + + # Get MNIST test data + mnist = MNIST( + train_start=train_start, + train_end=train_end, + test_start=test_start, + test_end=test_end, + ) + x_train, y_train = mnist.get_set("train") + x_test, y_test = mnist.get_set("test") + + # Use Image Parameters + img_rows, img_cols, nchannels = x_train.shape[1:4] + nb_classes = y_train.shape[1] + + # Define input TF placeholder + x = tf.placeholder(tf.float32, shape=(None, img_rows, img_cols, nchannels)) + y = tf.placeholder(tf.float32, shape=(None, nb_classes)) + + eval_params = {"batch_size": batch_size} + fgsm_params = {"eps": 0.3, "clip_min": 0.0, "clip_max": 1.0} + + def do_eval(preds, x_set, y_set, report_key, is_adv=None): + acc = model_eval(sess, x, y, preds, x_set, y_set, args=eval_params) + if is_adv is None: + report_text = None + elif is_adv: + report_text = "adversarial" + else: + report_text = "legitimate" + if report_text: + print("Test accuracy on %s examples: %0.4f" % (report_text, acc)) + + with sess.as_default(): + model = load(filepath) + assert len(model.get_params()) > 0 + + # Initialize the Fast Gradient Sign Method (FGSM) attack object and + # graph + fgsm = FastGradientMethod(model, sess=sess) + adv_x = fgsm.generate(x, **fgsm_params) + preds_adv = model.get_logits(adv_x) + preds = model.get_logits(x) + + # Evaluate the accuracy of the MNIST model on adversarial examples + do_eval(preds, x_test, y_test, "train_clean_train_clean_eval", False) + do_eval(preds_adv, x_test, y_test, "clean_train_adv_eval", True) + + +def main(argv=None): + _, filepath = argv + evaluate_model(filepath=filepath) + + +if __name__ == "__main__": + tf.app.run() diff --git a/cleverhans_v3.1.0/cleverhans_tutorials/mnist_blackbox.py b/cleverhans_v3.1.0/cleverhans_tutorials/mnist_blackbox.py new file mode 100644 index 000000000..002d1749b --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans_tutorials/mnist_blackbox.py @@ -0,0 +1,406 @@ +""" +This tutorial shows how to generate adversarial examples +using FGSM in black-box setting. +The original paper can be found at: +https://arxiv.org/abs/1602.02697 +""" +# pylint: disable=missing-docstring +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import functools +import logging +import numpy as np +from six.moves import xrange +import tensorflow as tf + +from cleverhans.attacks import FastGradientMethod +from cleverhans.utils_tf import jacobian_graph, jacobian_augmentation +from cleverhans.compat import flags +from cleverhans.dataset import MNIST +from cleverhans.initializers import HeReLuNormalInitializer +from cleverhans.loss import CrossEntropy +from cleverhans.model import Model +from cleverhans.train import train +from cleverhans.utils import set_log_level +from cleverhans.utils import TemporaryLogLevel +from cleverhans.utils import to_categorical +from cleverhans.utils_tf import model_eval, batch_eval + +from cleverhans.model_zoo.basic_cnn import ModelBasicCNN + +FLAGS = flags.FLAGS + +NB_CLASSES = 10 +BATCH_SIZE = 128 +LEARNING_RATE = 0.001 +NB_EPOCHS = 10 +HOLDOUT = 150 +DATA_AUG = 6 +NB_EPOCHS_S = 10 +LMBDA = 0.1 +AUG_BATCH_SIZE = 512 + + +def setup_tutorial(): + """ + Helper function to check correct configuration of tf for tutorial + :return: True if setup checks completed + """ + + # Set TF random seed to improve reproducibility + tf.set_random_seed(1234) + + return True + + +def prep_bbox( + sess, + x, + y, + x_train, + y_train, + x_test, + y_test, + nb_epochs, + batch_size, + learning_rate, + rng, + nb_classes=10, + img_rows=28, + img_cols=28, + nchannels=1, +): + """ + Define and train a model that simulates the "remote" + black-box oracle described in the original paper. + :param sess: the TF session + :param x: the input placeholder for MNIST + :param y: the ouput placeholder for MNIST + :param x_train: the training data for the oracle + :param y_train: the training labels for the oracle + :param x_test: the testing data for the oracle + :param y_test: the testing labels for the oracle + :param nb_epochs: number of epochs to train model + :param batch_size: size of training batches + :param learning_rate: learning rate for training + :param rng: numpy.random.RandomState + :return: + """ + + # Define TF model graph (for the black-box model) + nb_filters = 64 + model = ModelBasicCNN("model1", nb_classes, nb_filters) + loss = CrossEntropy(model, smoothing=0.1) + predictions = model.get_logits(x) + print("Defined TensorFlow model graph.") + + # Train an MNIST model + train_params = { + "nb_epochs": nb_epochs, + "batch_size": batch_size, + "learning_rate": learning_rate, + } + train(sess, loss, x_train, y_train, args=train_params, rng=rng) + + # Print out the accuracy on legitimate data + eval_params = {"batch_size": batch_size} + accuracy = model_eval(sess, x, y, predictions, x_test, y_test, args=eval_params) + print("Test accuracy of black-box on legitimate test " "examples: " + str(accuracy)) + + return model, predictions, accuracy + + +class ModelSubstitute(Model): + def __init__(self, scope, nb_classes, nb_filters=200, **kwargs): + del kwargs + Model.__init__(self, scope, nb_classes, locals()) + self.nb_filters = nb_filters + + def fprop(self, x, **kwargs): + del kwargs + my_dense = functools.partial( + tf.layers.dense, kernel_initializer=HeReLuNormalInitializer + ) + with tf.variable_scope(self.scope, reuse=tf.AUTO_REUSE): + y = tf.layers.flatten(x) + y = my_dense(y, self.nb_filters, activation=tf.nn.relu) + y = my_dense(y, self.nb_filters, activation=tf.nn.relu) + logits = my_dense(y, self.nb_classes) + return {self.O_LOGITS: logits, self.O_PROBS: tf.nn.softmax(logits=logits)} + + +def train_sub( + sess, + x, + y, + bbox_preds, + x_sub, + y_sub, + nb_classes, + nb_epochs_s, + batch_size, + learning_rate, + data_aug, + lmbda, + aug_batch_size, + rng, + img_rows=28, + img_cols=28, + nchannels=1, +): + """ + This function creates the substitute by alternatively + augmenting the training data and training the substitute. + :param sess: TF session + :param x: input TF placeholder + :param y: output TF placeholder + :param bbox_preds: output of black-box model predictions + :param x_sub: initial substitute training data + :param y_sub: initial substitute training labels + :param nb_classes: number of output classes + :param nb_epochs_s: number of epochs to train substitute model + :param batch_size: size of training batches + :param learning_rate: learning rate for training + :param data_aug: number of times substitute training data is augmented + :param lmbda: lambda from arxiv.org/abs/1602.02697 + :param rng: numpy.random.RandomState instance + :return: + """ + # Define TF model graph (for the black-box model) + model_sub = ModelSubstitute("model_s", nb_classes) + preds_sub = model_sub.get_logits(x) + loss_sub = CrossEntropy(model_sub, smoothing=0) + + print("Defined TensorFlow model graph for the substitute.") + + # Define the Jacobian symbolically using TensorFlow + grads = jacobian_graph(preds_sub, x, nb_classes) + + # Train the substitute and augment dataset alternatively + for rho in xrange(data_aug): + print("Substitute training epoch #" + str(rho)) + train_params = { + "nb_epochs": nb_epochs_s, + "batch_size": batch_size, + "learning_rate": learning_rate, + } + with TemporaryLogLevel(logging.WARNING, "cleverhans.utils.tf"): + train( + sess, + loss_sub, + x_sub, + to_categorical(y_sub, nb_classes), + init_all=False, + args=train_params, + rng=rng, + var_list=model_sub.get_params(), + ) + + # If we are not at last substitute training iteration, augment dataset + if rho < data_aug - 1: + print("Augmenting substitute training data.") + # Perform the Jacobian augmentation + lmbda_coef = 2 * int(int(rho / 3) % 2 == 0) - 1 + x_sub = jacobian_augmentation( + sess, x, x_sub, y_sub, grads, lmbda_coef * lmbda, aug_batch_size + ) + + print("Labeling substitute training data.") + # Label the newly generated synthetic points using the black-box + y_sub = np.hstack([y_sub, y_sub]) + x_sub_prev = x_sub[int(len(x_sub) / 2) :] + eval_params = {"batch_size": batch_size} + bbox_val = batch_eval( + sess, [x], [bbox_preds], [x_sub_prev], args=eval_params + )[0] + # Note here that we take the argmax because the adversary + # only has access to the label (not the probabilities) output + # by the black-box model + y_sub[int(len(x_sub) / 2) :] = np.argmax(bbox_val, axis=1) + + return model_sub, preds_sub + + +def mnist_blackbox( + train_start=0, + train_end=60000, + test_start=0, + test_end=10000, + nb_classes=NB_CLASSES, + batch_size=BATCH_SIZE, + learning_rate=LEARNING_RATE, + nb_epochs=NB_EPOCHS, + holdout=HOLDOUT, + data_aug=DATA_AUG, + nb_epochs_s=NB_EPOCHS_S, + lmbda=LMBDA, + aug_batch_size=AUG_BATCH_SIZE, +): + """ + MNIST tutorial for the black-box attack from arxiv.org/abs/1602.02697 + :param train_start: index of first training set example + :param train_end: index of last training set example + :param test_start: index of first test set example + :param test_end: index of last test set example + :return: a dictionary with: + * black-box model accuracy on test set + * substitute model accuracy on test set + * black-box model accuracy on adversarial examples transferred + from the substitute model + """ + + # Set logging level to see debug information + set_log_level(logging.DEBUG) + + # Dictionary used to keep track and return key accuracies + accuracies = {} + + # Perform tutorial setup + assert setup_tutorial() + + # Create TF session + sess = tf.Session() + + # Get MNIST data + mnist = MNIST( + train_start=train_start, + train_end=train_end, + test_start=test_start, + test_end=test_end, + ) + x_train, y_train = mnist.get_set("train") + x_test, y_test = mnist.get_set("test") + + # Initialize substitute training set reserved for adversary + x_sub = x_test[:holdout] + y_sub = np.argmax(y_test[:holdout], axis=1) + + # Redefine test set as remaining samples unavailable to adversaries + x_test = x_test[holdout:] + y_test = y_test[holdout:] + + # Obtain Image parameters + img_rows, img_cols, nchannels = x_train.shape[1:4] + nb_classes = y_train.shape[1] + + # Define input TF placeholder + x = tf.placeholder(tf.float32, shape=(None, img_rows, img_cols, nchannels)) + y = tf.placeholder(tf.float32, shape=(None, nb_classes)) + + # Seed random number generator so tutorial is reproducible + rng = np.random.RandomState([2017, 8, 30]) + + # Simulate the black-box model locally + # You could replace this by a remote labeling API for instance + print("Preparing the black-box model.") + prep_bbox_out = prep_bbox( + sess, + x, + y, + x_train, + y_train, + x_test, + y_test, + nb_epochs, + batch_size, + learning_rate, + rng, + nb_classes, + img_rows, + img_cols, + nchannels, + ) + model, bbox_preds, accuracies["bbox"] = prep_bbox_out + + # Train substitute using method from https://arxiv.org/abs/1602.02697 + print("Training the substitute model.") + train_sub_out = train_sub( + sess, + x, + y, + bbox_preds, + x_sub, + y_sub, + nb_classes, + nb_epochs_s, + batch_size, + learning_rate, + data_aug, + lmbda, + aug_batch_size, + rng, + img_rows, + img_cols, + nchannels, + ) + model_sub, preds_sub = train_sub_out + + # Evaluate the substitute model on clean test examples + eval_params = {"batch_size": batch_size} + acc = model_eval(sess, x, y, preds_sub, x_test, y_test, args=eval_params) + accuracies["sub"] = acc + + # Initialize the Fast Gradient Sign Method (FGSM) attack object. + fgsm_par = {"eps": 0.3, "ord": np.inf, "clip_min": 0.0, "clip_max": 1.0} + fgsm = FastGradientMethod(model_sub, sess=sess) + + # Craft adversarial examples using the substitute + eval_params = {"batch_size": batch_size} + x_adv_sub = fgsm.generate(x, **fgsm_par) + + # Evaluate the accuracy of the "black-box" model on adversarial examples + accuracy = model_eval( + sess, x, y, model.get_logits(x_adv_sub), x_test, y_test, args=eval_params + ) + print( + "Test accuracy of oracle on adversarial examples generated " + "using the substitute: " + str(accuracy) + ) + accuracies["bbox_on_sub_adv_ex"] = accuracy + + return accuracies + + +def main(argv=None): + from cleverhans_tutorials import check_installation + + check_installation(__file__) + + mnist_blackbox( + nb_classes=FLAGS.nb_classes, + batch_size=FLAGS.batch_size, + learning_rate=FLAGS.learning_rate, + nb_epochs=FLAGS.nb_epochs, + holdout=FLAGS.holdout, + data_aug=FLAGS.data_aug, + nb_epochs_s=FLAGS.nb_epochs_s, + lmbda=FLAGS.lmbda, + aug_batch_size=FLAGS.data_aug_batch_size, + ) + + +if __name__ == "__main__": + + # General flags + flags.DEFINE_integer("nb_classes", NB_CLASSES, "Number of classes in problem") + flags.DEFINE_integer("batch_size", BATCH_SIZE, "Size of training batches") + flags.DEFINE_float("learning_rate", LEARNING_RATE, "Learning rate for training") + + # Flags related to oracle + flags.DEFINE_integer("nb_epochs", NB_EPOCHS, "Number of epochs to train model") + + # Flags related to substitute + flags.DEFINE_integer("holdout", HOLDOUT, "Test set holdout for adversary") + flags.DEFINE_integer( + "data_aug", DATA_AUG, "Number of substitute data augmentations" + ) + flags.DEFINE_integer("nb_epochs_s", NB_EPOCHS_S, "Training epochs for substitute") + flags.DEFINE_float("lmbda", LMBDA, "Lambda from arxiv.org/abs/1602.02697") + flags.DEFINE_integer( + "data_aug_batch_size", AUG_BATCH_SIZE, "Batch size for augmentation" + ) + + tf.app.run() diff --git a/cleverhans_v3.1.0/cleverhans_tutorials/mnist_tutorial_cw.py b/cleverhans_v3.1.0/cleverhans_tutorials/mnist_tutorial_cw.py new file mode 100644 index 000000000..8cdb0af7f --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans_tutorials/mnist_tutorial_cw.py @@ -0,0 +1,287 @@ +""" +This tutorial shows how to generate adversarial examples +using C&W attack in white-box setting. +The original paper can be found at: +https://nicholas.carlini.com/papers/2017_sp_nnrobustattacks.pdf +""" +# pylint: disable=missing-docstring +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import logging +import os +import numpy as np +import tensorflow as tf + +from cleverhans.attacks import CarliniWagnerL2 +from cleverhans.compat import flags +from cleverhans.dataset import MNIST +from cleverhans.loss import CrossEntropy +from cleverhans.utils import grid_visual, AccuracyReport +from cleverhans.utils import set_log_level +from cleverhans.utils_tf import model_eval, tf_model_load +from cleverhans.train import train +from cleverhans.model_zoo.basic_cnn import ModelBasicCNN + +FLAGS = flags.FLAGS + +VIZ_ENABLED = True +BATCH_SIZE = 128 +NB_EPOCHS = 6 +SOURCE_SAMPLES = 10 +LEARNING_RATE = 0.001 +CW_LEARNING_RATE = 0.2 +ATTACK_ITERATIONS = 100 +MODEL_PATH = os.path.join("models", "mnist") +TARGETED = True + + +def mnist_tutorial_cw( + train_start=0, + train_end=60000, + test_start=0, + test_end=10000, + viz_enabled=VIZ_ENABLED, + nb_epochs=NB_EPOCHS, + batch_size=BATCH_SIZE, + source_samples=SOURCE_SAMPLES, + learning_rate=LEARNING_RATE, + attack_iterations=ATTACK_ITERATIONS, + model_path=MODEL_PATH, + targeted=TARGETED, +): + """ + MNIST tutorial for Carlini and Wagner's attack + :param train_start: index of first training set example + :param train_end: index of last training set example + :param test_start: index of first test set example + :param test_end: index of last test set example + :param viz_enabled: (boolean) activate plots of adversarial examples + :param nb_epochs: number of epochs to train model + :param batch_size: size of training batches + :param nb_classes: number of output classes + :param source_samples: number of test inputs to attack + :param learning_rate: learning rate for training + :param model_path: path to the model file + :param targeted: should we run a targeted attack? or untargeted? + :return: an AccuracyReport object + """ + # Object used to keep track of (and return) key accuracies + report = AccuracyReport() + + # Set TF random seed to improve reproducibility + tf.set_random_seed(1234) + + # Create TF session + sess = tf.Session() + print("Created TensorFlow session.") + + set_log_level(logging.DEBUG) + + # Get MNIST test data + mnist = MNIST( + train_start=train_start, + train_end=train_end, + test_start=test_start, + test_end=test_end, + ) + x_train, y_train = mnist.get_set("train") + x_test, y_test = mnist.get_set("test") + + # Obtain Image Parameters + img_rows, img_cols, nchannels = x_train.shape[1:4] + nb_classes = y_train.shape[1] + + # Define input TF placeholder + x = tf.placeholder(tf.float32, shape=(None, img_rows, img_cols, nchannels)) + y = tf.placeholder(tf.float32, shape=(None, nb_classes)) + nb_filters = 64 + + # Define TF model graph + model = ModelBasicCNN("model1", nb_classes, nb_filters) + preds = model.get_logits(x) + loss = CrossEntropy(model, smoothing=0.1) + print("Defined TensorFlow model graph.") + + ########################################################################### + # Training the model using TensorFlow + ########################################################################### + + # Train an MNIST model + train_params = { + "nb_epochs": nb_epochs, + "batch_size": batch_size, + "learning_rate": learning_rate, + "filename": os.path.split(model_path)[-1], + } + + rng = np.random.RandomState([2017, 8, 30]) + # check if we've trained before, and if we have, use that pre-trained model + if os.path.exists(model_path + ".meta"): + tf_model_load(sess, model_path) + else: + train(sess, loss, x_train, y_train, args=train_params, rng=rng) + saver = tf.train.Saver() + saver.save(sess, model_path) + + # Evaluate the accuracy of the MNIST model on legitimate test examples + eval_params = {"batch_size": batch_size} + accuracy = model_eval(sess, x, y, preds, x_test, y_test, args=eval_params) + assert x_test.shape[0] == test_end - test_start, x_test.shape + print("Test accuracy on legitimate test examples: {0}".format(accuracy)) + report.clean_train_clean_eval = accuracy + + ########################################################################### + # Craft adversarial examples using Carlini and Wagner's approach + ########################################################################### + nb_adv_per_sample = str(nb_classes - 1) if targeted else "1" + print( + "Crafting " + + str(source_samples) + + " * " + + nb_adv_per_sample + + " adversarial examples" + ) + print("This could take some time ...") + + # Instantiate a CW attack object + cw = CarliniWagnerL2(model, sess=sess) + + if viz_enabled: + assert source_samples == nb_classes + idxs = [ + np.where(np.argmax(y_test, axis=1) == i)[0][0] for i in range(nb_classes) + ] + if targeted: + if viz_enabled: + # Initialize our array for grid visualization + grid_shape = (nb_classes, nb_classes, img_rows, img_cols, nchannels) + grid_viz_data = np.zeros(grid_shape, dtype="f") + + adv_inputs = np.array( + [[instance] * nb_classes for instance in x_test[idxs]], dtype=np.float32 + ) + else: + adv_inputs = np.array( + [[instance] * nb_classes for instance in x_test[:source_samples]], + dtype=np.float32, + ) + + one_hot = np.zeros((nb_classes, nb_classes)) + one_hot[np.arange(nb_classes), np.arange(nb_classes)] = 1 + + adv_inputs = adv_inputs.reshape( + (source_samples * nb_classes, img_rows, img_cols, nchannels) + ) + adv_ys = np.array([one_hot] * source_samples, dtype=np.float32).reshape( + (source_samples * nb_classes, nb_classes) + ) + yname = "y_target" + else: + if viz_enabled: + # Initialize our array for grid visualization + grid_shape = (nb_classes, 2, img_rows, img_cols, nchannels) + grid_viz_data = np.zeros(grid_shape, dtype="f") + + adv_inputs = x_test[idxs] + else: + adv_inputs = x_test[:source_samples] + + adv_ys = None + yname = "y" + + if targeted: + cw_params_batch_size = source_samples * nb_classes + else: + cw_params_batch_size = source_samples + cw_params = { + "binary_search_steps": 1, + yname: adv_ys, + "max_iterations": attack_iterations, + "learning_rate": CW_LEARNING_RATE, + "batch_size": cw_params_batch_size, + "initial_const": 10, + } + + adv = cw.generate_np(adv_inputs, **cw_params) + + eval_params = {"batch_size": np.minimum(nb_classes, source_samples)} + if targeted: + adv_accuracy = model_eval(sess, x, y, preds, adv, adv_ys, args=eval_params) + else: + if viz_enabled: + err = model_eval(sess, x, y, preds, adv, y_test[idxs], args=eval_params) + adv_accuracy = 1 - err + else: + err = model_eval( + sess, x, y, preds, adv, y_test[:source_samples], args=eval_params + ) + adv_accuracy = 1 - err + + if viz_enabled: + for j in range(nb_classes): + if targeted: + for i in range(nb_classes): + grid_viz_data[i, j] = adv[i * nb_classes + j] + else: + grid_viz_data[j, 0] = adv_inputs[j] + grid_viz_data[j, 1] = adv[j] + + print(grid_viz_data.shape) + + print("--------------------------------------") + + # Compute the number of adversarial examples that were successfully found + print("Avg. rate of successful adv. examples {0:.4f}".format(adv_accuracy)) + report.clean_train_adv_eval = 1.0 - adv_accuracy + + # Compute the average distortion introduced by the algorithm + percent_perturbed = np.mean(np.sum((adv - adv_inputs) ** 2, axis=(1, 2, 3)) ** 0.5) + print("Avg. L_2 norm of perturbations {0:.4f}".format(percent_perturbed)) + + # Close TF session + sess.close() + + # Finally, block & display a grid of all the adversarial examples + if viz_enabled: + _ = grid_visual(grid_viz_data) + + return report + + +def main(argv=None): + from cleverhans_tutorials import check_installation + + check_installation(__file__) + + mnist_tutorial_cw( + viz_enabled=FLAGS.viz_enabled, + nb_epochs=FLAGS.nb_epochs, + batch_size=FLAGS.batch_size, + source_samples=FLAGS.source_samples, + learning_rate=FLAGS.learning_rate, + attack_iterations=FLAGS.attack_iterations, + model_path=FLAGS.model_path, + targeted=FLAGS.targeted, + ) + + +if __name__ == "__main__": + flags.DEFINE_boolean("viz_enabled", VIZ_ENABLED, "Visualize adversarial ex.") + flags.DEFINE_integer("nb_epochs", NB_EPOCHS, "Number of epochs to train model") + flags.DEFINE_integer("batch_size", BATCH_SIZE, "Size of training batches") + flags.DEFINE_integer( + "source_samples", SOURCE_SAMPLES, "Number of test inputs to attack" + ) + flags.DEFINE_float("learning_rate", LEARNING_RATE, "Learning rate for training") + flags.DEFINE_string("model_path", MODEL_PATH, "Path to save or load the model file") + flags.DEFINE_integer( + "attack_iterations", + ATTACK_ITERATIONS, + "Number of iterations to run attack; 1000 is good", + ) + flags.DEFINE_boolean("targeted", TARGETED, "Run the tutorial in targeted mode?") + + tf.app.run() diff --git a/cleverhans_v3.1.0/cleverhans_tutorials/mnist_tutorial_jsma.py b/cleverhans_v3.1.0/cleverhans_tutorials/mnist_tutorial_jsma.py new file mode 100644 index 000000000..fef773dde --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans_tutorials/mnist_tutorial_jsma.py @@ -0,0 +1,262 @@ +""" +This tutorial shows how to generate adversarial examples +using JSMA in white-box setting. +The original paper can be found at: +https://arxiv.org/abs/1511.07528 +""" +# pylint: disable=missing-docstring +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import logging +import numpy as np +from six.moves import xrange +import tensorflow as tf + +from cleverhans.attacks import SaliencyMapMethod +from cleverhans.compat import flags +from cleverhans.dataset import MNIST +from cleverhans.loss import CrossEntropy +from cleverhans.utils import other_classes, set_log_level +from cleverhans.utils import pair_visual, grid_visual, AccuracyReport +from cleverhans.utils_tf import model_eval, model_argmax +from cleverhans.train import train +from cleverhans.model_zoo.basic_cnn import ModelBasicCNN + +FLAGS = flags.FLAGS + +VIZ_ENABLED = True +NB_EPOCHS = 6 +BATCH_SIZE = 128 +LEARNING_RATE = 0.001 +SOURCE_SAMPLES = 10 + + +def mnist_tutorial_jsma( + train_start=0, + train_end=60000, + test_start=0, + test_end=10000, + viz_enabled=VIZ_ENABLED, + nb_epochs=NB_EPOCHS, + batch_size=BATCH_SIZE, + source_samples=SOURCE_SAMPLES, + learning_rate=LEARNING_RATE, +): + """ + MNIST tutorial for the Jacobian-based saliency map approach (JSMA) + :param train_start: index of first training set example + :param train_end: index of last training set example + :param test_start: index of first test set example + :param test_end: index of last test set example + :param viz_enabled: (boolean) activate plots of adversarial examples + :param nb_epochs: number of epochs to train model + :param batch_size: size of training batches + :param nb_classes: number of output classes + :param source_samples: number of test inputs to attack + :param learning_rate: learning rate for training + :return: an AccuracyReport object + """ + # Object used to keep track of (and return) key accuracies + report = AccuracyReport() + + # Set TF random seed to improve reproducibility + tf.set_random_seed(1234) + + # Create TF session and set as Keras backend session + sess = tf.Session() + print("Created TensorFlow session.") + + set_log_level(logging.DEBUG) + + # Get MNIST test data + mnist = MNIST( + train_start=train_start, + train_end=train_end, + test_start=test_start, + test_end=test_end, + ) + x_train, y_train = mnist.get_set("train") + x_test, y_test = mnist.get_set("test") + + # Obtain Image Parameters + img_rows, img_cols, nchannels = x_train.shape[1:4] + nb_classes = y_train.shape[1] + + # Define input TF placeholder + x = tf.placeholder(tf.float32, shape=(None, img_rows, img_cols, nchannels)) + y = tf.placeholder(tf.float32, shape=(None, nb_classes)) + + nb_filters = 64 + # Define TF model graph + model = ModelBasicCNN("model1", nb_classes, nb_filters) + preds = model.get_logits(x) + loss = CrossEntropy(model, smoothing=0.1) + print("Defined TensorFlow model graph.") + + ########################################################################### + # Training the model using TensorFlow + ########################################################################### + + # Train an MNIST model + train_params = { + "nb_epochs": nb_epochs, + "batch_size": batch_size, + "learning_rate": learning_rate, + } + sess.run(tf.global_variables_initializer()) + rng = np.random.RandomState([2017, 8, 30]) + train(sess, loss, x_train, y_train, args=train_params, rng=rng) + + # Evaluate the accuracy of the MNIST model on legitimate test examples + eval_params = {"batch_size": batch_size} + accuracy = model_eval(sess, x, y, preds, x_test, y_test, args=eval_params) + assert x_test.shape[0] == test_end - test_start, x_test.shape + print("Test accuracy on legitimate test examples: {0}".format(accuracy)) + report.clean_train_clean_eval = accuracy + + ########################################################################### + # Craft adversarial examples using the Jacobian-based saliency map approach + ########################################################################### + print( + "Crafting " + + str(source_samples) + + " * " + + str(nb_classes - 1) + + " adversarial examples" + ) + + # Keep track of success (adversarial example classified in target) + results = np.zeros((nb_classes, source_samples), dtype="i") + + # Rate of perturbed features for each test set example and target class + perturbations = np.zeros((nb_classes, source_samples), dtype="f") + + # Initialize our array for grid visualization + grid_shape = (nb_classes, nb_classes, img_rows, img_cols, nchannels) + grid_viz_data = np.zeros(grid_shape, dtype="f") + + # Instantiate a SaliencyMapMethod attack object + jsma = SaliencyMapMethod(model, sess=sess) + jsma_params = { + "theta": 1.0, + "gamma": 0.1, + "clip_min": 0.0, + "clip_max": 1.0, + "y_target": None, + } + + figure = None + # Loop over the samples we want to perturb into adversarial examples + for sample_ind in xrange(0, source_samples): + print("--------------------------------------") + print("Attacking input %i/%i" % (sample_ind + 1, source_samples)) + sample = x_test[sample_ind : (sample_ind + 1)] + + # We want to find an adversarial example for each possible target class + # (i.e. all classes that differ from the label given in the dataset) + current_class = int(np.argmax(y_test[sample_ind])) + target_classes = other_classes(nb_classes, current_class) + + # For the grid visualization, keep original images along the diagonal + grid_viz_data[current_class, current_class, :, :, :] = np.reshape( + sample, (img_rows, img_cols, nchannels) + ) + + # Loop over all target classes + for target in target_classes: + print("Generating adv. example for target class %i" % target) + + # This call runs the Jacobian-based saliency map approach + one_hot_target = np.zeros((1, nb_classes), dtype=np.float32) + one_hot_target[0, target] = 1 + jsma_params["y_target"] = one_hot_target + adv_x = jsma.generate_np(sample, **jsma_params) + + # Check if success was achieved + res = int(model_argmax(sess, x, preds, adv_x) == target) + + # Compute number of modified features + adv_x_reshape = adv_x.reshape(-1) + test_in_reshape = x_test[sample_ind].reshape(-1) + nb_changed = np.where(adv_x_reshape != test_in_reshape)[0].shape[0] + percent_perturb = float(nb_changed) / adv_x.reshape(-1).shape[0] + + # Display the original and adversarial images side-by-side + if viz_enabled: + figure = pair_visual( + np.reshape(sample, (img_rows, img_cols, nchannels)), + np.reshape(adv_x, (img_rows, img_cols, nchannels)), + figure, + ) + + # Add our adversarial example to our grid data + grid_viz_data[target, current_class, :, :, :] = np.reshape( + adv_x, (img_rows, img_cols, nchannels) + ) + + # Update the arrays for later analysis + results[target, sample_ind] = res + perturbations[target, sample_ind] = percent_perturb + + print("--------------------------------------") + + # Compute the number of adversarial examples that were successfully found + nb_targets_tried = (nb_classes - 1) * source_samples + succ_rate = float(np.sum(results)) / nb_targets_tried + print("Avg. rate of successful adv. examples {0:.4f}".format(succ_rate)) + report.clean_train_adv_eval = 1.0 - succ_rate + + # Compute the average distortion introduced by the algorithm + percent_perturbed = np.mean(perturbations[np.where(perturbations != 0)]) + print("Avg. rate of perturbed features {0:.4f}".format(percent_perturbed)) + + # Compute the average distortion introduced for successful samples only + percent_perturb_succ = np.mean( + perturbations[np.where(perturbations != 0)] + * (results[np.where(perturbations != 0)] == 1) + ) + print( + "Avg. rate of perturbed features for successful " + "adversarial examples {0:.4f}".format(percent_perturb_succ) + ) + + # Close TF session + sess.close() + + # Finally, block & display a grid of all the adversarial examples + if viz_enabled: + import matplotlib.pyplot as plt + + plt.close(figure) + _ = grid_visual(grid_viz_data) + + return report + + +def main(argv=None): + from cleverhans_tutorials import check_installation + + check_installation(__file__) + + mnist_tutorial_jsma( + viz_enabled=FLAGS.viz_enabled, + nb_epochs=FLAGS.nb_epochs, + batch_size=FLAGS.batch_size, + source_samples=FLAGS.source_samples, + learning_rate=FLAGS.learning_rate, + ) + + +if __name__ == "__main__": + flags.DEFINE_boolean("viz_enabled", VIZ_ENABLED, "Visualize adversarial ex.") + flags.DEFINE_integer("nb_epochs", NB_EPOCHS, "Number of epochs to train model") + flags.DEFINE_integer("batch_size", BATCH_SIZE, "Size of training batches") + flags.DEFINE_integer( + "source_samples", SOURCE_SAMPLES, "Nb of test inputs to attack" + ) + flags.DEFINE_float("learning_rate", LEARNING_RATE, "Learning rate for training") + + tf.app.run() diff --git a/cleverhans_v3.1.0/cleverhans_tutorials/mnist_tutorial_keras.py b/cleverhans_v3.1.0/cleverhans_tutorials/mnist_tutorial_keras.py new file mode 100644 index 000000000..e3f28c457 --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans_tutorials/mnist_tutorial_keras.py @@ -0,0 +1,241 @@ +""" +This tutorial shows how to generate adversarial examples using FGSM +and train a model using adversarial training with Keras. +The original paper can be found at: +https://arxiv.org/abs/1412.6572 +""" +# pylint: disable=missing-docstring +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import tensorflow as tf +from tensorflow import keras + +from cleverhans.attacks import FastGradientMethod +from cleverhans.compat import flags +from cleverhans.dataset import MNIST +from cleverhans.utils import AccuracyReport +from cleverhans.utils_keras import cnn_model +from cleverhans.utils_keras import KerasModelWrapper + +FLAGS = flags.FLAGS + +NB_EPOCHS = 6 +BATCH_SIZE = 128 +LEARNING_RATE = 0.001 + + +def mnist_tutorial( + train_start=0, + train_end=60000, + test_start=0, + test_end=10000, + nb_epochs=NB_EPOCHS, + batch_size=BATCH_SIZE, + learning_rate=LEARNING_RATE, + testing=False, + label_smoothing=0.1, +): + """ + MNIST CleverHans tutorial + :param train_start: index of first training set example + :param train_end: index of last training set example + :param test_start: index of first test set example + :param test_end: index of last test set example + :param nb_epochs: number of epochs to train model + :param batch_size: size of training batches + :param learning_rate: learning rate for training + :param testing: if true, training error is calculated + :param label_smoothing: float, amount of label smoothing for cross entropy + :return: an AccuracyReport object + """ + + # Object used to keep track of (and return) key accuracies + report = AccuracyReport() + + # Set TF random seed to improve reproducibility + tf.set_random_seed(1234) + # Force TensorFlow to use single thread to improve reproducibility + config = tf.ConfigProto( + intra_op_parallelism_threads=1, inter_op_parallelism_threads=1 + ) + + if keras.backend.image_data_format() != "channels_last": + raise NotImplementedError( + "this tutorial requires keras to be configured to channels_last format" + ) + + # Create TF session and set as Keras backend session + sess = tf.Session(config=config) + keras.backend.set_session(sess) + + # Get MNIST test data + mnist = MNIST( + train_start=train_start, + train_end=train_end, + test_start=test_start, + test_end=test_end, + ) + x_train, y_train = mnist.get_set("train") + x_test, y_test = mnist.get_set("test") + + # Obtain Image Parameters + img_rows, img_cols, nchannels = x_train.shape[1:4] + nb_classes = y_train.shape[1] + + # Label smoothing + y_train -= label_smoothing * (y_train - 1.0 / nb_classes) + + # Define Keras model + model = cnn_model( + img_rows=img_rows, + img_cols=img_cols, + channels=nchannels, + nb_filters=64, + nb_classes=nb_classes, + ) + print("Defined Keras model.") + + # To be able to call the model in the custom loss, we need to call it once + # before, see https://github.com/tensorflow/tensorflow/issues/23769 + model(model.input) + + # Initialize the Fast Gradient Sign Method (FGSM) attack object + wrap = KerasModelWrapper(model) + fgsm = FastGradientMethod(wrap, sess=sess) + fgsm_params = {"eps": 0.3, "clip_min": 0.0, "clip_max": 1.0} + + adv_acc_metric = get_adversarial_acc_metric(model, fgsm, fgsm_params) + model.compile( + optimizer=keras.optimizers.Adam(learning_rate), + loss="categorical_crossentropy", + metrics=["accuracy", adv_acc_metric], + ) + + # Train an MNIST model + model.fit( + x_train, + y_train, + batch_size=batch_size, + epochs=nb_epochs, + validation_data=(x_test, y_test), + verbose=2, + ) + + # Evaluate the accuracy on legitimate and adversarial test examples + _, acc, adv_acc = model.evaluate(x_test, y_test, batch_size=batch_size, verbose=0) + report.clean_train_clean_eval = acc + report.clean_train_adv_eval = adv_acc + print("Test accuracy on legitimate examples: %0.4f" % acc) + print("Test accuracy on adversarial examples: %0.4f\n" % adv_acc) + + # Calculate training error + if testing: + _, train_acc, train_adv_acc = model.evaluate( + x_train, y_train, batch_size=batch_size, verbose=0 + ) + report.train_clean_train_clean_eval = train_acc + report.train_clean_train_adv_eval = train_adv_acc + + print("Repeating the process, using adversarial training") + # Redefine Keras model + model_2 = cnn_model( + img_rows=img_rows, + img_cols=img_cols, + channels=nchannels, + nb_filters=64, + nb_classes=nb_classes, + ) + model_2(model_2.input) + wrap_2 = KerasModelWrapper(model_2) + fgsm_2 = FastGradientMethod(wrap_2, sess=sess) + + # Use a loss function based on legitimate and adversarial examples + adv_loss_2 = get_adversarial_loss(model_2, fgsm_2, fgsm_params) + adv_acc_metric_2 = get_adversarial_acc_metric(model_2, fgsm_2, fgsm_params) + model_2.compile( + optimizer=keras.optimizers.Adam(learning_rate), + loss=adv_loss_2, + metrics=["accuracy", adv_acc_metric_2], + ) + + # Train an MNIST model + model_2.fit( + x_train, + y_train, + batch_size=batch_size, + epochs=nb_epochs, + validation_data=(x_test, y_test), + verbose=2, + ) + + # Evaluate the accuracy on legitimate and adversarial test examples + _, acc, adv_acc = model_2.evaluate(x_test, y_test, batch_size=batch_size, verbose=0) + report.adv_train_clean_eval = acc + report.adv_train_adv_eval = adv_acc + print("Test accuracy on legitimate examples: %0.4f" % acc) + print("Test accuracy on adversarial examples: %0.4f\n" % adv_acc) + + # Calculate training error + if testing: + _, train_acc, train_adv_acc = model_2.evaluate( + x_train, y_train, batch_size=batch_size, verbose=0 + ) + report.train_adv_train_clean_eval = train_acc + report.train_adv_train_adv_eval = train_adv_acc + + return report + + +def get_adversarial_acc_metric(model, fgsm, fgsm_params): + def adv_acc(y, _): + # Generate adversarial examples + x_adv = fgsm.generate(model.input, **fgsm_params) + # Consider the attack to be constant + x_adv = tf.stop_gradient(x_adv) + + # Accuracy on the adversarial examples + preds_adv = model(x_adv) + return keras.metrics.categorical_accuracy(y, preds_adv) + + return adv_acc + + +def get_adversarial_loss(model, fgsm, fgsm_params): + def adv_loss(y, preds): + # Cross-entropy on the legitimate examples + cross_ent = keras.losses.categorical_crossentropy(y, preds) + + # Generate adversarial examples + x_adv = fgsm.generate(model.input, **fgsm_params) + # Consider the attack to be constant + x_adv = tf.stop_gradient(x_adv) + + # Cross-entropy on the adversarial examples + preds_adv = model(x_adv) + cross_ent_adv = keras.losses.categorical_crossentropy(y, preds_adv) + + return 0.5 * cross_ent + 0.5 * cross_ent_adv + + return adv_loss + + +def main(argv=None): + from cleverhans_tutorials import check_installation + + check_installation(__file__) + + mnist_tutorial( + nb_epochs=FLAGS.nb_epochs, + batch_size=FLAGS.batch_size, + learning_rate=FLAGS.learning_rate, + ) + + +if __name__ == "__main__": + flags.DEFINE_integer("nb_epochs", NB_EPOCHS, "Number of epochs to train model") + flags.DEFINE_integer("batch_size", BATCH_SIZE, "Size of training batches") + flags.DEFINE_float("learning_rate", LEARNING_RATE, "Learning rate for training") + tf.app.run() diff --git a/cleverhans_v3.1.0/cleverhans_tutorials/mnist_tutorial_keras_tf.py b/cleverhans_v3.1.0/cleverhans_tutorials/mnist_tutorial_keras_tf.py new file mode 100644 index 000000000..10cdd0836 --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans_tutorials/mnist_tutorial_keras_tf.py @@ -0,0 +1,253 @@ +""" +This tutorial shows how to generate adversarial examples using FGSM +and train a model using adversarial training with Keras. +It is very similar to mnist_tutorial_tf.py, which does the same +thing but without a dependence on keras. +The original paper can be found at: +https://arxiv.org/abs/1412.6572 +""" +# pylint: disable=missing-docstring +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import os + +import tensorflow as tf +from tensorflow import keras +import numpy as np + +from cleverhans.attacks import FastGradientMethod +from cleverhans.compat import flags +from cleverhans.dataset import MNIST +from cleverhans.loss import CrossEntropy +from cleverhans.train import train +from cleverhans.utils import AccuracyReport +from cleverhans.utils_keras import cnn_model +from cleverhans.utils_keras import KerasModelWrapper +from cleverhans.utils_tf import model_eval + +FLAGS = flags.FLAGS + +NB_EPOCHS = 6 +BATCH_SIZE = 128 +LEARNING_RATE = 0.001 +TRAIN_DIR = "train_dir" +FILENAME = "mnist.ckpt" +LOAD_MODEL = False + + +def mnist_tutorial( + train_start=0, + train_end=60000, + test_start=0, + test_end=10000, + nb_epochs=NB_EPOCHS, + batch_size=BATCH_SIZE, + learning_rate=LEARNING_RATE, + train_dir=TRAIN_DIR, + filename=FILENAME, + load_model=LOAD_MODEL, + testing=False, + label_smoothing=0.1, +): + """ + MNIST CleverHans tutorial + :param train_start: index of first training set example + :param train_end: index of last training set example + :param test_start: index of first test set example + :param test_end: index of last test set example + :param nb_epochs: number of epochs to train model + :param batch_size: size of training batches + :param learning_rate: learning rate for training + :param train_dir: Directory storing the saved model + :param filename: Filename to save model under + :param load_model: True for load, False for not load + :param testing: if true, test error is calculated + :param label_smoothing: float, amount of label smoothing for cross entropy + :return: an AccuracyReport object + """ + tf.keras.backend.set_learning_phase(0) + + # Object used to keep track of (and return) key accuracies + report = AccuracyReport() + + # Set TF random seed to improve reproducibility + tf.set_random_seed(1234) + + if keras.backend.image_data_format() != "channels_last": + raise NotImplementedError( + "this tutorial requires keras to be configured to channels_last format" + ) + + # Create TF session and set as Keras backend session + sess = tf.Session() + keras.backend.set_session(sess) + + # Get MNIST test data + mnist = MNIST( + train_start=train_start, + train_end=train_end, + test_start=test_start, + test_end=test_end, + ) + x_train, y_train = mnist.get_set("train") + x_test, y_test = mnist.get_set("test") + + # Obtain Image Parameters + img_rows, img_cols, nchannels = x_train.shape[1:4] + nb_classes = y_train.shape[1] + + # Define input TF placeholder + x = tf.placeholder(tf.float32, shape=(None, img_rows, img_cols, nchannels)) + y = tf.placeholder(tf.float32, shape=(None, nb_classes)) + + # Define TF model graph + model = cnn_model( + img_rows=img_rows, + img_cols=img_cols, + channels=nchannels, + nb_filters=64, + nb_classes=nb_classes, + ) + preds = model(x) + print("Defined TensorFlow model graph.") + + def evaluate(): + # Evaluate the accuracy of the MNIST model on legitimate test examples + eval_params = {"batch_size": batch_size} + acc = model_eval(sess, x, y, preds, x_test, y_test, args=eval_params) + report.clean_train_clean_eval = acc + # assert X_test.shape[0] == test_end - test_start, X_test.shape + print("Test accuracy on legitimate examples: %0.4f" % acc) + + # Train an MNIST model + train_params = { + "nb_epochs": nb_epochs, + "batch_size": batch_size, + "learning_rate": learning_rate, + "train_dir": train_dir, + "filename": filename, + } + + rng = np.random.RandomState([2017, 8, 30]) + if not os.path.exists(train_dir): + os.mkdir(train_dir) + + ckpt = tf.train.get_checkpoint_state(train_dir) + print(train_dir, ckpt) + ckpt_path = False if ckpt is None else ckpt.model_checkpoint_path + wrap = KerasModelWrapper(model) + + if load_model and ckpt_path: + saver = tf.train.Saver() + print(ckpt_path) + saver.restore(sess, ckpt_path) + print("Model loaded from: {}".format(ckpt_path)) + evaluate() + else: + print("Model was not loaded, training from scratch.") + loss = CrossEntropy(wrap, smoothing=label_smoothing) + train( + sess, loss, x_train, y_train, evaluate=evaluate, args=train_params, rng=rng + ) + + # Calculate training error + if testing: + eval_params = {"batch_size": batch_size} + acc = model_eval(sess, x, y, preds, x_train, y_train, args=eval_params) + report.train_clean_train_clean_eval = acc + + # Initialize the Fast Gradient Sign Method (FGSM) attack object and graph + fgsm = FastGradientMethod(wrap, sess=sess) + fgsm_params = {"eps": 0.3, "clip_min": 0.0, "clip_max": 1.0} + adv_x = fgsm.generate(x, **fgsm_params) + # Consider the attack to be constant + adv_x = tf.stop_gradient(adv_x) + preds_adv = model(adv_x) + + # Evaluate the accuracy of the MNIST model on adversarial examples + eval_par = {"batch_size": batch_size} + acc = model_eval(sess, x, y, preds_adv, x_test, y_test, args=eval_par) + print("Test accuracy on adversarial examples: %0.4f\n" % acc) + report.clean_train_adv_eval = acc + + # Calculating train error + if testing: + eval_par = {"batch_size": batch_size} + acc = model_eval(sess, x, y, preds_adv, x_train, y_train, args=eval_par) + report.train_clean_train_adv_eval = acc + + print("Repeating the process, using adversarial training") + # Redefine TF model graph + model_2 = cnn_model( + img_rows=img_rows, + img_cols=img_cols, + channels=nchannels, + nb_filters=64, + nb_classes=nb_classes, + ) + wrap_2 = KerasModelWrapper(model_2) + preds_2 = model_2(x) + fgsm2 = FastGradientMethod(wrap_2, sess=sess) + + def attack(x): + return fgsm2.generate(x, **fgsm_params) + + preds_2_adv = model_2(attack(x)) + loss_2 = CrossEntropy(wrap_2, smoothing=label_smoothing, attack=attack) + + def evaluate_2(): + # Accuracy of adversarially trained model on legitimate test inputs + eval_params = {"batch_size": batch_size} + accuracy = model_eval(sess, x, y, preds_2, x_test, y_test, args=eval_params) + print("Test accuracy on legitimate examples: %0.4f" % accuracy) + report.adv_train_clean_eval = accuracy + + # Accuracy of the adversarially trained model on adversarial examples + accuracy = model_eval(sess, x, y, preds_2_adv, x_test, y_test, args=eval_params) + print("Test accuracy on adversarial examples: %0.4f" % accuracy) + report.adv_train_adv_eval = accuracy + + # Perform and evaluate adversarial training + train( + sess, loss_2, x_train, y_train, evaluate=evaluate_2, args=train_params, rng=rng + ) + + # Calculate training errors + if testing: + eval_params = {"batch_size": batch_size} + accuracy = model_eval(sess, x, y, preds_2, x_train, y_train, args=eval_params) + report.train_adv_train_clean_eval = accuracy + accuracy = model_eval( + sess, x, y, preds_2_adv, x_train, y_train, args=eval_params + ) + report.train_adv_train_adv_eval = accuracy + + return report + + +def main(argv=None): + from cleverhans_tutorials import check_installation + + check_installation(__file__) + + mnist_tutorial( + nb_epochs=FLAGS.nb_epochs, + batch_size=FLAGS.batch_size, + learning_rate=FLAGS.learning_rate, + train_dir=FLAGS.train_dir, + filename=FLAGS.filename, + load_model=FLAGS.load_model, + ) + + +if __name__ == "__main__": + flags.DEFINE_integer("nb_epochs", NB_EPOCHS, "Number of epochs to train model") + flags.DEFINE_integer("batch_size", BATCH_SIZE, "Size of training batches") + flags.DEFINE_float("learning_rate", LEARNING_RATE, "Learning rate for training") + flags.DEFINE_string("train_dir", TRAIN_DIR, "Directory where to save model.") + flags.DEFINE_string("filename", FILENAME, "Checkpoint filename.") + flags.DEFINE_boolean("load_model", LOAD_MODEL, "Load saved model or train.") + tf.app.run() diff --git a/cleverhans_v3.1.0/cleverhans_tutorials/mnist_tutorial_picklable.py b/cleverhans_v3.1.0/cleverhans_tutorials/mnist_tutorial_picklable.py new file mode 100644 index 000000000..48fc1eab6 --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans_tutorials/mnist_tutorial_picklable.py @@ -0,0 +1,287 @@ +""" +This tutorial shows how to use cleverhans.picklable_model +to create models that can be saved for evaluation later. +""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import logging +import numpy as np +import tensorflow as tf + +from cleverhans.attacks import FastGradientMethod +from cleverhans.compat import flags +from cleverhans.dataset import MNIST +from cleverhans.loss import CrossEntropy +from cleverhans.serial import save +from cleverhans.utils_tf import model_eval, silence +from cleverhans.train import train +from cleverhans.utils import AccuracyReport, set_log_level +from cleverhans_tutorials.tutorial_models import make_basic_picklable_cnn + +silence() + +FLAGS = flags.FLAGS + +NB_EPOCHS = 6 +BATCH_SIZE = 128 +LEARNING_RATE = 0.001 +NB_FILTERS = 64 +CLEAN_TRAIN = True +BACKPROP_THROUGH_ATTACK = False + + +def mnist_tutorial( + train_start=0, + train_end=60000, + test_start=0, + test_end=10000, + nb_epochs=NB_EPOCHS, + batch_size=BATCH_SIZE, + learning_rate=LEARNING_RATE, + clean_train=CLEAN_TRAIN, + testing=False, + backprop_through_attack=BACKPROP_THROUGH_ATTACK, + nb_filters=NB_FILTERS, + num_threads=None, + label_smoothing=0.1, +): + """ + MNIST cleverhans tutorial + :param train_start: index of first training set example + :param train_end: index of last training set example + :param test_start: index of first test set example + :param test_end: index of last test set example + :param nb_epochs: number of epochs to train model + :param batch_size: size of training batches + :param learning_rate: learning rate for training + :param clean_train: perform normal training on clean examples only + before performing adversarial training. + :param testing: if true, complete an AccuracyReport for unit tests + to verify that performance is adequate + :param backprop_through_attack: If True, backprop through adversarial + example construction process during + adversarial training. + :param label_smoothing: float, amount of label smoothing for cross entropy + :return: an AccuracyReport object + """ + + # Object used to keep track of (and return) key accuracies + report = AccuracyReport() + + # Set TF random seed to improve reproducibility + tf.set_random_seed(1234) + + # Set logging level to see debug information + set_log_level(logging.DEBUG) + + # Create TF session + if num_threads: + config_args = dict(intra_op_parallelism_threads=1) + else: + config_args = {} + sess = tf.Session(config=tf.ConfigProto(**config_args)) + + # Get MNIST test data + mnist = MNIST( + train_start=train_start, + train_end=train_end, + test_start=test_start, + test_end=test_end, + ) + x_train, y_train = mnist.get_set("train") + x_test, y_test = mnist.get_set("test") + + # Use Image Parameters + img_rows, img_cols, nchannels = x_train.shape[1:4] + nb_classes = y_train.shape[1] + + # Define input TF placeholder + x = tf.placeholder(tf.float32, shape=(None, img_rows, img_cols, nchannels)) + y = tf.placeholder(tf.float32, shape=(None, nb_classes)) + + # Train an MNIST model + train_params = { + "nb_epochs": nb_epochs, + "batch_size": batch_size, + "learning_rate": learning_rate, + } + eval_params = {"batch_size": batch_size} + fgsm_params = {"eps": 0.3, "clip_min": 0.0, "clip_max": 1.0} + rng = np.random.RandomState([2017, 8, 30]) + + def do_eval(preds, x_set, y_set, report_key, is_adv=None): + """ + Run the evaluation and print the results. + """ + acc = model_eval(sess, x, y, preds, x_set, y_set, args=eval_params) + setattr(report, report_key, acc) + if is_adv is None: + report_text = None + elif is_adv: + report_text = "adversarial" + else: + report_text = "legitimate" + if report_text: + print("Test accuracy on %s examples: %0.4f" % (report_text, acc)) + + if clean_train: + model = make_basic_picklable_cnn() + # Tag the model so that when it is saved to disk, future scripts will + # be able to tell what data it was trained on + model.dataset_factory = mnist.get_factory() + preds = model.get_logits(x) + assert len(model.get_params()) > 0 + loss = CrossEntropy(model, smoothing=label_smoothing) + + def evaluate(): + """ + Run evaluation for the naively trained model on clean examples. + """ + do_eval(preds, x_test, y_test, "clean_train_clean_eval", False) + + train( + sess, + loss, + x_train, + y_train, + evaluate=evaluate, + args=train_params, + rng=rng, + var_list=model.get_params(), + ) + + with sess.as_default(): + save("clean_model.joblib", model) + + print( + "Now that the model has been saved, you can evaluate it in a" + " separate process using `evaluate_pickled_model.py`. " + "You should get exactly the same result for both clean and " + "adversarial accuracy as you get within this program." + ) + + # Calculate training error + if testing: + do_eval(preds, x_train, y_train, "train_clean_train_clean_eval") + + # Initialize the Fast Gradient Sign Method (FGSM) attack object and + # graph + fgsm = FastGradientMethod(model, sess=sess) + adv_x = fgsm.generate(x, **fgsm_params) + preds_adv = model.get_logits(adv_x) + + # Evaluate the accuracy of the MNIST model on adversarial examples + do_eval(preds_adv, x_test, y_test, "clean_train_adv_eval", True) + + # Calculate training error + if testing: + do_eval(preds_adv, x_train, y_train, "train_clean_train_adv_eval") + + print("Repeating the process, using adversarial training") + + # Create a new model and train it to be robust to FastGradientMethod + model2 = make_basic_picklable_cnn() + # Tag the model so that when it is saved to disk, future scripts will + # be able to tell what data it was trained on + model2.dataset_factory = mnist.get_factory() + fgsm2 = FastGradientMethod(model2, sess=sess) + + def attack(x): + """Return an adversarial example near clean example `x`""" + return fgsm2.generate(x, **fgsm_params) + + loss2 = CrossEntropy(model2, smoothing=label_smoothing, attack=attack) + preds2 = model2.get_logits(x) + adv_x2 = attack(x) + + if not backprop_through_attack: + # For the fgsm attack used in this tutorial, the attack has zero + # gradient so enabling this flag does not change the gradient. + # For some other attacks, enabling this flag increases the cost of + # training, but gives the defender the ability to anticipate how + # the atacker will change their strategy in response to updates to + # the defender's parameters. + adv_x2 = tf.stop_gradient(adv_x2) + preds2_adv = model2.get_logits(adv_x2) + + def evaluate_adv(): + """ + Evaluate the adversarially trained model. + """ + # Accuracy of adversarially trained model on legitimate test inputs + do_eval(preds2, x_test, y_test, "adv_train_clean_eval", False) + # Accuracy of the adversarially trained model on adversarial examples + do_eval(preds2_adv, x_test, y_test, "adv_train_adv_eval", True) + + # Perform and evaluate adversarial training + train( + sess, + loss2, + x_train, + y_train, + evaluate=evaluate_adv, + args=train_params, + rng=rng, + var_list=model2.get_params(), + ) + + with sess.as_default(): + save("adv_model.joblib", model2) + print( + "Now that the model has been saved, you can evaluate it in a " + "separate process using " + "`python evaluate_pickled_model.py adv_model.joblib`. " + "You should get exactly the same result for both clean and " + "adversarial accuracy as you get within this program." + " You can also move beyond the tutorials directory and run the " + " real `compute_accuracy.py` script (make sure cleverhans/scripts " + "is in your PATH) to see that this FGSM-trained " + "model is actually not very robust---it's just a model that trains " + " quickly so the tutorial does not take a long time" + ) + + # Calculate training errors + if testing: + do_eval(preds2, x_train, y_train, "train_adv_train_clean_eval") + do_eval(preds2_adv, x_train, y_train, "train_adv_train_adv_eval") + + return report + + +def main(argv=None): + """ + Run the tutorial using command line flags + """ + from cleverhans_tutorials import check_installation + + check_installation(__file__) + + mnist_tutorial( + nb_epochs=FLAGS.nb_epochs, + batch_size=FLAGS.batch_size, + learning_rate=FLAGS.learning_rate, + clean_train=FLAGS.clean_train, + backprop_through_attack=FLAGS.backprop_through_attack, + nb_filters=FLAGS.nb_filters, + ) + + +if __name__ == "__main__": + flags.DEFINE_integer("nb_filters", NB_FILTERS, "Model size multiplier") + flags.DEFINE_integer("nb_epochs", NB_EPOCHS, "Number of epochs to train model") + flags.DEFINE_integer("batch_size", BATCH_SIZE, "Size of training batches") + flags.DEFINE_float("learning_rate", LEARNING_RATE, "Learning rate for training") + flags.DEFINE_bool("clean_train", CLEAN_TRAIN, "Train on clean examples") + flags.DEFINE_bool( + "backprop_through_attack", + BACKPROP_THROUGH_ATTACK, + ( + "If True, backprop through adversarial example " + "construction process during adversarial training" + ), + ) + + tf.app.run() diff --git a/cleverhans_v3.1.0/cleverhans_tutorials/mnist_tutorial_pytorch.py b/cleverhans_v3.1.0/cleverhans_tutorials/mnist_tutorial_pytorch.py new file mode 100644 index 000000000..bedbd5678 --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans_tutorials/mnist_tutorial_pytorch.py @@ -0,0 +1,206 @@ +""" +This tutorial shows how to generate adversarial examples using FGSM +and train a model using adversarial training with TensorFlow. +It is very similar to mnist_tutorial_keras_tf.py, which does the same +thing but with a dependence on keras. +The original paper can be found at: +https://arxiv.org/abs/1412.6572 +""" +# pylint: disable=missing-docstring +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import warnings +import numpy as np +import tensorflow as tf +import torch +from torch import nn +import torch.nn.functional as F +from torch import optim +from torch.autograd import Variable +from torchvision import datasets, transforms + +from cleverhans.attacks import FastGradientMethod +from cleverhans.compat import flags +from cleverhans.model import CallableModelWrapper +from cleverhans.utils import AccuracyReport +from cleverhans.utils_pytorch import convert_pytorch_model_to_tf + +FLAGS = flags.FLAGS + +NB_EPOCHS = 6 +BATCH_SIZE = 128 +LEARNING_RATE = 0.001 + + +warnings.warn( + "convert_pytorch_model_to_tf is deprecated, switch to" + + " dedicated PyTorch support provided by CleverHans v4." +) + + +class PytorchMnistModel(nn.Module): + """Basic MNIST model from github + https://github.com/rickiepark/pytorch-examples/blob/master/mnist.ipynb + """ + + def __init__(self): + super(PytorchMnistModel, self).__init__() + # input is 28x28 + # padding=2 for same padding + self.conv1 = nn.Conv2d(1, 32, 5, padding=2) + # feature map size is 14*14 by pooling + # padding=2 for same padding + self.conv2 = nn.Conv2d(32, 64, 5, padding=2) + # feature map size is 7*7 by pooling + self.fc1 = nn.Linear(64 * 7 * 7, 1024) + self.fc2 = nn.Linear(1024, 10) + + def forward(self, x): + x = F.max_pool2d(F.relu(self.conv1(x)), 2) + x = F.max_pool2d(F.relu(self.conv2(x)), 2) + x = x.view(-1, 64 * 7 * 7) # reshape Variable + x = F.relu(self.fc1(x)) + x = self.fc2(x) + return F.log_softmax(x, dim=-1) + + +def mnist_tutorial( + nb_epochs=NB_EPOCHS, + batch_size=BATCH_SIZE, + train_end=-1, + test_end=-1, + learning_rate=LEARNING_RATE, +): + """ + MNIST cleverhans tutorial + :param nb_epochs: number of epochs to train model + :param batch_size: size of training batches + :param learning_rate: learning rate for training + :return: an AccuracyReport object + """ + # Train a pytorch MNIST model + torch_model = PytorchMnistModel() + if torch.cuda.is_available(): + torch_model = torch_model.cuda() + report = AccuracyReport() + + train_loader = torch.utils.data.DataLoader( + datasets.MNIST( + "data", train=True, download=True, transform=transforms.ToTensor() + ), + batch_size=batch_size, + shuffle=True, + ) + test_loader = torch.utils.data.DataLoader( + datasets.MNIST("data", train=False, transform=transforms.ToTensor()), + batch_size=batch_size, + ) + + # Truncate the datasets so that our test run more quickly + train_loader.dataset.train_data = train_loader.dataset.train_data[:train_end] + test_loader.dataset.test_data = test_loader.dataset.test_data[:test_end] + + # Train our model + optimizer = optim.Adam(torch_model.parameters(), lr=learning_rate) + train_loss = [] + + total = 0 + correct = 0 + step = 0 + for _epoch in range(nb_epochs): + for xs, ys in train_loader: + xs, ys = Variable(xs), Variable(ys) + if torch.cuda.is_available(): + xs, ys = xs.cuda(), ys.cuda() + optimizer.zero_grad() + preds = torch_model(xs) + loss = F.nll_loss(preds, ys) + loss.backward() # calc gradients + train_loss.append(loss.data.item()) + optimizer.step() # update gradients + + preds_np = preds.cpu().detach().numpy() + correct += (np.argmax(preds_np, axis=1) == ys.cpu().detach().numpy()).sum() + total += train_loader.batch_size + step += 1 + if total % 1000 == 0: + acc = float(correct) / total + print("[%s] Training accuracy: %.2f%%" % (step, acc * 100)) + total = 0 + correct = 0 + + # Evaluate on clean data + total = 0 + correct = 0 + for xs, ys in test_loader: + xs, ys = Variable(xs), Variable(ys) + if torch.cuda.is_available(): + xs, ys = xs.cuda(), ys.cuda() + + preds = torch_model(xs) + preds_np = preds.cpu().detach().numpy() + + correct += (np.argmax(preds_np, axis=1) == ys.cpu().detach().numpy()).sum() + total += len(xs) + + acc = float(correct) / total + report.clean_train_clean_eval = acc + print("[%s] Clean accuracy: %.2f%%" % (step, acc * 100)) + + # We use tf for evaluation on adversarial data + sess = tf.Session() + x_op = tf.placeholder( + tf.float32, + shape=( + None, + 1, + 28, + 28, + ), + ) + + # Convert pytorch model to a tf_model and wrap it in cleverhans + tf_model_fn = convert_pytorch_model_to_tf(torch_model) + cleverhans_model = CallableModelWrapper(tf_model_fn, output_layer="logits") + + # Create an FGSM attack + fgsm_op = FastGradientMethod(cleverhans_model, sess=sess) + fgsm_params = {"eps": 0.3, "clip_min": 0.0, "clip_max": 1.0} + adv_x_op = fgsm_op.generate(x_op, **fgsm_params) + adv_preds_op = tf_model_fn(adv_x_op) + + # Run an evaluation of our model against fgsm + total = 0 + correct = 0 + for xs, ys in test_loader: + adv_preds = sess.run(adv_preds_op, feed_dict={x_op: xs}) + correct += (np.argmax(adv_preds, axis=1) == ys.cpu().detach().numpy()).sum() + total += test_loader.batch_size + + acc = float(correct) / total + print("Adv accuracy: {:.3f}".format(acc * 100)) + report.clean_train_adv_eval = acc + return report + + +def main(_=None): + from cleverhans_tutorials import check_installation + + check_installation(__file__) + + mnist_tutorial( + nb_epochs=FLAGS.nb_epochs, + batch_size=FLAGS.batch_size, + learning_rate=FLAGS.learning_rate, + ) + + +if __name__ == "__main__": + flags.DEFINE_integer("nb_epochs", NB_EPOCHS, "Number of epochs to train model") + flags.DEFINE_integer("batch_size", BATCH_SIZE, "Size of training batches") + flags.DEFINE_float("learning_rate", LEARNING_RATE, "Learning rate for training") + + tf.app.run() diff --git a/cleverhans_v3.1.0/cleverhans_tutorials/mnist_tutorial_tf.py b/cleverhans_v3.1.0/cleverhans_tutorials/mnist_tutorial_tf.py new file mode 100644 index 000000000..07195ad53 --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans_tutorials/mnist_tutorial_tf.py @@ -0,0 +1,247 @@ +""" +This tutorial shows how to generate adversarial examples using FGSM +and train a model using adversarial training with TensorFlow. +It is very similar to mnist_tutorial_keras_tf.py, which does the same +thing but with a dependence on keras. +The original paper can be found at: +https://arxiv.org/abs/1412.6572 +""" +# pylint: disable=missing-docstring +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import logging +import numpy as np +import tensorflow as tf + +from cleverhans.compat import flags +from cleverhans.loss import CrossEntropy +from cleverhans.dataset import MNIST +from cleverhans.utils_tf import model_eval +from cleverhans.train import train +from cleverhans.attacks import FastGradientMethod +from cleverhans.utils import AccuracyReport, set_log_level +from cleverhans.model_zoo.basic_cnn import ModelBasicCNN + +FLAGS = flags.FLAGS + +NB_EPOCHS = 6 +BATCH_SIZE = 128 +LEARNING_RATE = 0.001 +CLEAN_TRAIN = True +BACKPROP_THROUGH_ATTACK = False +NB_FILTERS = 64 + + +def mnist_tutorial( + train_start=0, + train_end=60000, + test_start=0, + test_end=10000, + nb_epochs=NB_EPOCHS, + batch_size=BATCH_SIZE, + learning_rate=LEARNING_RATE, + clean_train=CLEAN_TRAIN, + testing=False, + backprop_through_attack=BACKPROP_THROUGH_ATTACK, + nb_filters=NB_FILTERS, + num_threads=None, + label_smoothing=0.1, +): + """ + MNIST cleverhans tutorial + :param train_start: index of first training set example + :param train_end: index of last training set example + :param test_start: index of first test set example + :param test_end: index of last test set example + :param nb_epochs: number of epochs to train model + :param batch_size: size of training batches + :param learning_rate: learning rate for training + :param clean_train: perform normal training on clean examples only + before performing adversarial training. + :param testing: if true, complete an AccuracyReport for unit tests + to verify that performance is adequate + :param backprop_through_attack: If True, backprop through adversarial + example construction process during + adversarial training. + :param label_smoothing: float, amount of label smoothing for cross entropy + :return: an AccuracyReport object + """ + + # Object used to keep track of (and return) key accuracies + report = AccuracyReport() + + # Set TF random seed to improve reproducibility + tf.set_random_seed(1234) + + # Set logging level to see debug information + set_log_level(logging.DEBUG) + + # Create TF session + if num_threads: + config_args = dict(intra_op_parallelism_threads=1) + else: + config_args = {} + sess = tf.Session(config=tf.ConfigProto(**config_args)) + + # Get MNIST data + mnist = MNIST( + train_start=train_start, + train_end=train_end, + test_start=test_start, + test_end=test_end, + ) + x_train, y_train = mnist.get_set("train") + x_test, y_test = mnist.get_set("test") + + # Use Image Parameters + img_rows, img_cols, nchannels = x_train.shape[1:4] + nb_classes = y_train.shape[1] + + # Define input TF placeholder + x = tf.placeholder(tf.float32, shape=(None, img_rows, img_cols, nchannels)) + y = tf.placeholder(tf.float32, shape=(None, nb_classes)) + + # Train an MNIST model + train_params = { + "nb_epochs": nb_epochs, + "batch_size": batch_size, + "learning_rate": learning_rate, + } + eval_params = {"batch_size": batch_size} + fgsm_params = {"eps": 0.3, "clip_min": 0.0, "clip_max": 1.0} + rng = np.random.RandomState([2017, 8, 30]) + + def do_eval(preds, x_set, y_set, report_key, is_adv=None): + acc = model_eval(sess, x, y, preds, x_set, y_set, args=eval_params) + setattr(report, report_key, acc) + if is_adv is None: + report_text = None + elif is_adv: + report_text = "adversarial" + else: + report_text = "legitimate" + if report_text: + print("Test accuracy on %s examples: %0.4f" % (report_text, acc)) + + if clean_train: + model = ModelBasicCNN("model1", nb_classes, nb_filters) + preds = model.get_logits(x) + loss = CrossEntropy(model, smoothing=label_smoothing) + + def evaluate(): + do_eval(preds, x_test, y_test, "clean_train_clean_eval", False) + + train( + sess, + loss, + x_train, + y_train, + evaluate=evaluate, + args=train_params, + rng=rng, + var_list=model.get_params(), + ) + + # Calculate training error + if testing: + do_eval(preds, x_train, y_train, "train_clean_train_clean_eval") + + # Initialize the Fast Gradient Sign Method (FGSM) attack object and + # graph + fgsm = FastGradientMethod(model, sess=sess) + adv_x = fgsm.generate(x, **fgsm_params) + preds_adv = model.get_logits(adv_x) + + # Evaluate the accuracy of the MNIST model on adversarial examples + do_eval(preds_adv, x_test, y_test, "clean_train_adv_eval", True) + + # Calculate training error + if testing: + do_eval(preds_adv, x_train, y_train, "train_clean_train_adv_eval") + + print("Repeating the process, using adversarial training") + + # Create a new model and train it to be robust to FastGradientMethod + model2 = ModelBasicCNN("model2", nb_classes, nb_filters) + fgsm2 = FastGradientMethod(model2, sess=sess) + + def attack(x): + return fgsm2.generate(x, **fgsm_params) + + loss2 = CrossEntropy(model2, smoothing=label_smoothing, attack=attack) + preds2 = model2.get_logits(x) + adv_x2 = attack(x) + + if not backprop_through_attack: + # For the fgsm attack used in this tutorial, the attack has zero + # gradient so enabling this flag does not change the gradient. + # For some other attacks, enabling this flag increases the cost of + # training, but gives the defender the ability to anticipate how + # the atacker will change their strategy in response to updates to + # the defender's parameters. + adv_x2 = tf.stop_gradient(adv_x2) + preds2_adv = model2.get_logits(adv_x2) + + def evaluate2(): + # Accuracy of adversarially trained model on legitimate test inputs + do_eval(preds2, x_test, y_test, "adv_train_clean_eval", False) + # Accuracy of the adversarially trained model on adversarial examples + do_eval(preds2_adv, x_test, y_test, "adv_train_adv_eval", True) + + # Perform and evaluate adversarial training + train( + sess, + loss2, + x_train, + y_train, + evaluate=evaluate2, + args=train_params, + rng=rng, + var_list=model2.get_params(), + ) + + # Calculate training errors + if testing: + do_eval(preds2, x_train, y_train, "train_adv_train_clean_eval") + do_eval(preds2_adv, x_train, y_train, "train_adv_train_adv_eval") + + return report + + +def main(argv=None): + """ + Run the tutorial using command line flags. + """ + from cleverhans_tutorials import check_installation + + check_installation(__file__) + + mnist_tutorial( + nb_epochs=FLAGS.nb_epochs, + batch_size=FLAGS.batch_size, + learning_rate=FLAGS.learning_rate, + clean_train=FLAGS.clean_train, + backprop_through_attack=FLAGS.backprop_through_attack, + nb_filters=FLAGS.nb_filters, + ) + + +if __name__ == "__main__": + flags.DEFINE_integer("nb_filters", NB_FILTERS, "Model size multiplier") + flags.DEFINE_integer("nb_epochs", NB_EPOCHS, "Number of epochs to train model") + flags.DEFINE_integer("batch_size", BATCH_SIZE, "Size of training batches") + flags.DEFINE_float("learning_rate", LEARNING_RATE, "Learning rate for training") + flags.DEFINE_bool("clean_train", CLEAN_TRAIN, "Train on clean examples") + flags.DEFINE_bool( + "backprop_through_attack", + BACKPROP_THROUGH_ATTACK, + ( + "If True, backprop through adversarial example " + "construction process during adversarial training" + ), + ) + + tf.app.run() diff --git a/cleverhans_v3.1.0/cleverhans_tutorials/mnist_tutorial_tfe.py b/cleverhans_v3.1.0/cleverhans_tutorials/mnist_tutorial_tfe.py new file mode 100644 index 000000000..4b4625dbb --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans_tutorials/mnist_tutorial_tfe.py @@ -0,0 +1,301 @@ +""" +This tutorial shows how to generate adversarial examples using FGSM +and train a model using adversarial training with TensorFlow Eager. + +It is similar to mnist_tutorial_tf.py. +mnist_tutorial_tf - dynaminc eager execution. +mnist_tutorial_tf - graph based execution. +The original paper can be found at: +https://arxiv.org/abs/1412.6572 +""" +# pylint: disable=missing-docstring +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import logging +import numpy as np +import tensorflow as tf + +from cleverhans.compat import flags +from cleverhans.utils import AccuracyReport +from cleverhans.utils_tfe import train, model_eval +from cleverhans.attacks_tfe import BasicIterativeMethod +from cleverhans.attacks_tfe import FastGradientMethod +from cleverhans.dataset import MNIST +from cleverhans.utils import set_log_level +from cleverhans_tutorials.tutorial_models_tfe import ModelBasicCNNTFE + +if tf.executing_eagerly() is True: + print("TF Eager Activated.") +else: + raise Exception("Error Enabling Eager Execution.") +tfe = tf.contrib.eager + +FLAGS = flags.FLAGS + +NB_EPOCHS = 6 +BATCH_SIZE = 128 +LEARNING_RATE = 0.001 +NB_FILTERS = 64 + +# Keeps track of implemented attacks. +# Maps attack string taken from bash to attack class +# -- 'fgsm' : FastGradientMethod +# -- 'bim': BasicIterativeMethod + +AVAILABLE_ATTACKS = {"fgsm": FastGradientMethod, "bim": BasicIterativeMethod} + + +def attack_selection(attack_string): + """ + Selects the Attack Class using string input. + :param attack_string: adversarial attack name in string format + :return: attack class defined in cleverhans.attacks_eager + """ + + # List of Implemented attacks + attacks_list = AVAILABLE_ATTACKS.keys() + + # Checking for requested attack in list of available attacks. + if attack_string is None: + raise AttributeError( + "Attack type is not specified, " + "list of available attacks\t".join(attacks_list) + ) + if attack_string not in attacks_list: + raise AttributeError( + "Attack not available " "list of available attacks\t".join(attacks_list) + ) + # Mapping attack from string to class. + attack_class = AVAILABLE_ATTACKS[attack_string] + return attack_class + + +def mnist_tutorial( + train_start=0, + train_end=60000, + test_start=0, + test_end=10000, + nb_epochs=NB_EPOCHS, + batch_size=BATCH_SIZE, + learning_rate=LEARNING_RATE, + clean_train=True, + testing=False, + backprop_through_attack=False, + nb_filters=NB_FILTERS, + num_threads=None, + attack_string=None, +): + """ + MNIST cleverhans tutorial + :param train_start: index of first training set example. + :param train_end: index of last training set example. + :param test_start: index of first test set example. + :param test_end: index of last test set example. + :param nb_epochs: number of epochs to train model. + :param batch_size: size of training batches. + :param learning_rate: learning rate for training. + :param clean_train: perform normal training on clean examples only + before performing adversarial training. + :param testing: if true, complete an AccuracyReport for unit tests + to verify that performance is adequate. + :param backprop_through_attack: If True, backprop through adversarial + example construction process during + adversarial training. + :param nb_filters: number of filters in the CNN used for training. + :param num_threads: number of threads used for running the process. + :param attack_string: attack name for crafting adversarial attacks and + adversarial training, in string format. + :return: an AccuracyReport object + """ + + # Object used to keep track of (and return) key accuracies + report = AccuracyReport() + + # Set TF random seed to improve reproducibility + tf.set_random_seed(1234) + + # Set logging level to see debug information + set_log_level(logging.DEBUG) + + # Get MNIST test data + mnist = MNIST( + train_start=train_start, + train_end=train_end, + test_start=test_start, + test_end=test_end, + ) + X_train, Y_train = mnist.get_set("train") + X_test, Y_test = mnist.get_set("test") + + # Use label smoothing + assert Y_train.shape[1] == 10 + label_smooth = 0.1 + Y_train = Y_train.clip(label_smooth / 9.0, 1.0 - label_smooth) + + # Train an MNIST model + train_params = { + "nb_epochs": nb_epochs, + "batch_size": batch_size, + "learning_rate": learning_rate, + } + + # Initialize the attack object + attack_class = attack_selection(attack_string) + attack_params = {"eps": 0.3, "clip_min": 0.0, "clip_max": 1.0} + + rng = np.random.RandomState([2018, 6, 18]) + if clean_train: + model = ModelBasicCNNTFE(nb_filters=nb_filters) + + def evaluate_clean(): + """Evaluate the accuracy of the MNIST model on legitimate test + examples + """ + eval_params = {"batch_size": batch_size} + acc = model_eval(model, X_test, Y_test, args=eval_params) + report.clean_train_clean_eval = acc + assert X_test.shape[0] == test_end - test_start, X_test.shape + print("Test accuracy on legitimate examples: %0.4f" % acc) + + train( + model, + X_train, + Y_train, + evaluate=evaluate_clean, + args=train_params, + rng=rng, + var_list=model.get_params(), + ) + + if testing: + # Calculate training error + eval_params = {"batch_size": batch_size} + acc = model_eval(model, X_train, Y_train, args=eval_params) + report.train_clean_train_clean_eval = acc + + # Evaluate the accuracy of the MNIST model on adversarial examples + eval_par = {"batch_size": batch_size} + attack = attack_class(model) + acc = model_eval( + model, + X_test, + Y_test, + args=eval_par, + attack=attack, + attack_args=attack_params, + ) + print("Test accuracy on adversarial examples: %0.4f\n" % acc) + report.clean_train_adv_eval = acc + + # Calculate training error + if testing: + eval_par = {"batch_size": batch_size} + acc = model_eval( + model, + X_train, + Y_train, + args=eval_par, + attack=attack, + attack_args=attack_params, + ) + print("Train accuracy on adversarial examples: %0.4f\n" % acc) + report.train_clean_train_adv_eval = acc + + attack = None + print("Repeating the process, using adversarial training") + + model_adv_train = ModelBasicCNNTFE(nb_filters=nb_filters) + attack = attack_class(model_adv_train) + + def evaluate_adv(): + # Accuracy of adversarially trained model on legitimate test inputs + eval_params = {"batch_size": batch_size} + accuracy = model_eval(model_adv_train, X_test, Y_test, args=eval_params) + print("Test accuracy on legitimate examples: %0.4f" % accuracy) + report.adv_train_clean_eval = accuracy + # Accuracy of the adversarially trained model on adversarial examples + accuracy = model_eval( + model_adv_train, + X_test, + Y_test, + args=eval_params, + attack=attack, + attack_args=attack_params, + ) + print("Test accuracy on adversarial examples: %0.4f" % accuracy) + report.adv_train_adv_eval = accuracy + + # Perform and evaluate adversarial training + train( + model_adv_train, + X_train, + Y_train, + evaluate=evaluate_adv, + args=train_params, + rng=rng, + var_list=model_adv_train.get_params(), + attack=attack, + attack_args=attack_params, + ) + + # Calculate training errors + if testing: + eval_params = {"batch_size": batch_size} + accuracy = model_eval( + model_adv_train, + X_train, + Y_train, + args=eval_params, + attack=None, + attack_args=None, + ) + report.train_adv_train_clean_eval = accuracy + accuracy = model_eval( + model_adv_train, + X_train, + Y_train, + args=eval_params, + attack=attack, + attack_args=attack_params, + ) + report.train_adv_train_adv_eval = accuracy + return report + + +def main(argv=None): + from cleverhans_tutorials import check_installation + + check_installation(__file__) + + mnist_tutorial( + nb_epochs=FLAGS.nb_epochs, + batch_size=FLAGS.batch_size, + learning_rate=FLAGS.learning_rate, + clean_train=FLAGS.clean_train, + backprop_through_attack=FLAGS.backprop_through_attack, + nb_filters=FLAGS.nb_filters, + attack_string=FLAGS.attack, + ) + + +if __name__ == "__main__": + flags.DEFINE_integer("nb_filters", NB_FILTERS, "Model size multiplier") + flags.DEFINE_integer("nb_epochs", NB_EPOCHS, "Number of epochs to train model") + flags.DEFINE_integer("batch_size", BATCH_SIZE, "Size of training batches") + flags.DEFINE_float("learning_rate", LEARNING_RATE, "Learning rate for training") + flags.DEFINE_bool("clean_train", True, "Train on clean examples") + flags.DEFINE_bool( + "backprop_through_attack", + False, + ( + "If True, backprop through adversarial example " + "construction process during adversarial training" + ), + ) + flags.DEFINE_string( + "attack", "fgsm", "Adversarial attack crafted and used for training" + ) + tf.app.run() diff --git a/cleverhans_v3.1.0/cleverhans_tutorials/tutorial_models.py b/cleverhans_v3.1.0/cleverhans_tutorials/tutorial_models.py new file mode 100644 index 000000000..f5933b7d7 --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans_tutorials/tutorial_models.py @@ -0,0 +1,30 @@ +""" +A pure TensorFlow implementation of a neural network. This can be +used as a drop-in replacement for a Keras model. +""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +from cleverhans.picklable_model import MLP, Conv2D, ReLU, Flatten, Linear +from cleverhans.picklable_model import Softmax + + +def make_basic_picklable_cnn( + nb_filters=64, nb_classes=10, input_shape=(None, 28, 28, 1) +): + """The model for the picklable models tutorial.""" + layers = [ + Conv2D(nb_filters, (8, 8), (2, 2), "SAME"), + ReLU(), + Conv2D(nb_filters * 2, (6, 6), (2, 2), "VALID"), + ReLU(), + Conv2D(nb_filters * 2, (5, 5), (1, 1), "VALID"), + ReLU(), + Flatten(), + Linear(nb_classes), + Softmax(), + ] + model = MLP(layers, input_shape) + return model diff --git a/cleverhans_v3.1.0/cleverhans_tutorials/tutorial_models_tfe.py b/cleverhans_v3.1.0/cleverhans_tutorials/tutorial_models_tfe.py new file mode 100644 index 000000000..da8756180 --- /dev/null +++ b/cleverhans_v3.1.0/cleverhans_tutorials/tutorial_models_tfe.py @@ -0,0 +1,121 @@ +""" +A TensorFlow Eager implementation of a neural network. +""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import tensorflow as tf +from cleverhans.model import Model + + +class ModelBasicCNNTFE(Model): + """ + Basic CNN model for tensorflow eager execution. + """ + + def __init__( + self, nb_classes=10, nb_filters=64, dummy_input=tf.zeros((32, 28, 28, 1)) + ): + Model.__init__(self, nb_classes=nb_classes) + + # Parametes + # number of filters, number of classes. + self.nb_filters = nb_filters + self.nb_classes = nb_classes + + # Lists for layers attributes. + # layer names , layers, layer activations + self.layer_names = ["input", "conv_1", "conv_2", "conv_3", "flatten", "logits"] + self.layers = {} + self.layer_acts = {} + + # layer definitions + self.layers["conv_1"] = tf.layers.Conv2D( + filters=self.nb_filters, + kernel_size=8, + strides=2, + padding="same", + activation=tf.nn.relu, + ) + self.layers["conv_2"] = tf.layers.Conv2D( + filters=self.nb_filters * 2, + kernel_size=6, + strides=2, + padding="valid", + activation=tf.nn.relu, + ) + self.layers["conv_3"] = tf.layers.Conv2D( + filters=self.nb_filters * 2, + kernel_size=5, + strides=1, + padding="valid", + activation=tf.nn.relu, + ) + self.layers["flatten"] = tf.layers.Flatten() + self.layers["logits"] = tf.layers.Dense(self.nb_classes, activation=None) + + # Dummy fprop to activate the network. + self.fprop(dummy_input) + + def fprop(self, x): + """ + Forward propagation throught the network + :return: dictionary with layer names mapping to activation values. + """ + + # Feed forward through the network layers + for layer_name in self.layer_names: + if layer_name == "input": + prev_layer_act = x + continue + else: + self.layer_acts[layer_name] = self.layers[layer_name](prev_layer_act) + prev_layer_act = self.layer_acts[layer_name] + + # Adding softmax values to list of activations. + self.layer_acts["probs"] = tf.nn.softmax(logits=self.layer_acts["logits"]) + return self.layer_acts + + def get_layer_params(self, layer_name): + """ + Provides access to the parameters of the given layer. + Works arounds the non-availability of graph collections in + eager mode. + :layer_name: name of the layer for which parameters are + required, must be one of the string in the + list layer_names + :return: list of parameters corresponding to the given + layer. + """ + assert layer_name in self.layer_names + + out = [] + layer = self.layers[layer_name] + layer_variables = layer.variables + + # For each parameter in a layer. + for param in layer_variables: + if param not in out: + out.append(param) + return out + + def get_params(self): + """ + Provides access to the model's parameters. + Works arounds the non-availability of graph collections in + eager mode. + :return: A list of all Variables defining the model parameters. + """ + assert tf.executing_eagerly() + out = [] + + # Collecting params from each layer. + for layer_name in self.layers: + out += self.get_layer_params(layer_name) + return out + + def get_layer_names(self): + """:return: the list of exposed layers for this model.""" + return self.layer_names diff --git a/cleverhans_v3.1.0/docs/.nojekyll b/cleverhans_v3.1.0/docs/.nojekyll new file mode 100644 index 000000000..e69de29bb diff --git a/cleverhans_v3.1.0/docs/README.html b/cleverhans_v3.1.0/docs/README.html new file mode 100644 index 000000000..84029c5b9 --- /dev/null +++ b/cleverhans_v3.1.0/docs/README.html @@ -0,0 +1,136 @@ + + + + + + + + <no title> — CleverHans documentation + + + + + + + + + + + + + + + + + +
+
+
+ + +
+ +

# Generate documentation

+

To generate the documentation do: +make github

+

The documentation files will be copied to the cleverhans/docs directory.

+

### Preparation

+

Please do: +pip install sphinx

+

Add a .nojekyll file in the cleverhans/docs directory. When GitHub sees +a .nojekyll file, it serves the root index.html file. The .nojekyll file +indicates that we are not using Jekyll as our static site generator in this +repository.

+

### Enable GitHub Pages for the GitHub repository

+
    +
  1. Go to the repository on the GitHub website and make sure you are logged in.

  2. +
  3. Add a /docs directory to the master branch. Otherwise you do not get the +master branch /docs folder for the Source option in the drop-down list.

  4. +
  5. Click the Settings tab. You first go to the Options section.

  6. +
  7. Scroll down to the GitHub Pages section and choose the drop-down list under +Source. Note: Your choices will differ based on whether you’re in a User repo +or an Org repository.

  8. +
  9. To keep source and output HTML separate, choose master branch /docs folder +for Source.

  10. +
+

### Build Sphinx locally and publish on GitHub Pages

+

We keep the source docsource and output docs separate, but still are able to +publish on GitHub Pages and preview builds locally.

+

We have the following option in the Makefile:

+
+
```
+
github:

@make html +@cp -a _build/html/. ../docs

+
+
+
+
+

```

+

Thus, we can run make github from the docsource directory to generate a +local preview and move the docs where GitHub wants to serve them from.

+

### Hacks

+

If you cannot build the docs for attacks, uncomment +import tensorflow_addons as tfa in cleverhans/attacks/spsa.py.

+

Otherwise:

+

`angular2html +WARNING: autodoc: failed to import module 'attacks' from module 'cleverhans'; the following exception was raised: +cannot import name 'keras_tensor' +`

+

It is convenient to create a virtual environment to install all the specific +libraries (e.g. virutalen cleverhans).

+ + +
+ +
+
+ +
+
+ + + + \ No newline at end of file diff --git a/cleverhans_v3.1.0/docs/_modules/abc.html b/cleverhans_v3.1.0/docs/_modules/abc.html new file mode 100644 index 000000000..466ba0c98 --- /dev/null +++ b/cleverhans_v3.1.0/docs/_modules/abc.html @@ -0,0 +1,341 @@ + + + + + + + + abc — CleverHans documentation + + + + + + + + + + + + + + + + + +
+
+
+ + +
+ +

Source code for abc

+# Copyright 2007 Google, Inc. All Rights Reserved.
+# Licensed to PSF under a Contributor Agreement.
+
+"""Abstract Base Classes (ABCs) according to PEP 3119."""
+
+from _weakrefset import WeakSet
+
+
+def abstractmethod(funcobj):
+    """A decorator indicating abstract methods.
+
+    Requires that the metaclass is ABCMeta or derived from it.  A
+    class that has a metaclass derived from ABCMeta cannot be
+    instantiated unless all of its abstract methods are overridden.
+    The abstract methods can be called using any of the normal
+    'super' call mechanisms.
+
+    Usage:
+
+        class C(metaclass=ABCMeta):
+            @abstractmethod
+            def my_abstract_method(self, ...):
+                ...
+    """
+    funcobj.__isabstractmethod__ = True
+    return funcobj
+
+
+class abstractclassmethod(classmethod):
+    """
+    A decorator indicating abstract classmethods.
+
+    Similar to abstractmethod.
+
+    Usage:
+
+        class C(metaclass=ABCMeta):
+            @abstractclassmethod
+            def my_abstract_classmethod(cls, ...):
+                ...
+
+    'abstractclassmethod' is deprecated. Use 'classmethod' with
+    'abstractmethod' instead.
+    """
+
+    __isabstractmethod__ = True
+
+    def __init__(self, callable):
+        callable.__isabstractmethod__ = True
+        super().__init__(callable)
+
+
+class abstractstaticmethod(staticmethod):
+    """
+    A decorator indicating abstract staticmethods.
+
+    Similar to abstractmethod.
+
+    Usage:
+
+        class C(metaclass=ABCMeta):
+            @abstractstaticmethod
+            def my_abstract_staticmethod(...):
+                ...
+
+    'abstractstaticmethod' is deprecated. Use 'staticmethod' with
+    'abstractmethod' instead.
+    """
+
+    __isabstractmethod__ = True
+
+    def __init__(self, callable):
+        callable.__isabstractmethod__ = True
+        super().__init__(callable)
+
+
+class abstractproperty(property):
+    """
+    A decorator indicating abstract properties.
+
+    Requires that the metaclass is ABCMeta or derived from it.  A
+    class that has a metaclass derived from ABCMeta cannot be
+    instantiated unless all of its abstract properties are overridden.
+    The abstract properties can be called using any of the normal
+    'super' call mechanisms.
+
+    Usage:
+
+        class C(metaclass=ABCMeta):
+            @abstractproperty
+            def my_abstract_property(self):
+                ...
+
+    This defines a read-only property; you can also define a read-write
+    abstract property using the 'long' form of property declaration:
+
+        class C(metaclass=ABCMeta):
+            def getx(self): ...
+            def setx(self, value): ...
+            x = abstractproperty(getx, setx)
+
+    'abstractproperty' is deprecated. Use 'property' with 'abstractmethod'
+    instead.
+    """
+
+    __isabstractmethod__ = True
+
+
+
[docs]class ABCMeta(type): + + """Metaclass for defining Abstract Base Classes (ABCs). + + Use this metaclass to create an ABC. An ABC can be subclassed + directly, and then acts as a mix-in class. You can also register + unrelated concrete classes (even built-in classes) and unrelated + ABCs as 'virtual subclasses' -- these and their descendants will + be considered subclasses of the registering ABC by the built-in + issubclass() function, but the registering ABC won't show up in + their MRO (Method Resolution Order) nor will method + implementations defined by the registering ABC be callable (not + even via super()). + + """ + + # A global counter that is incremented each time a class is + # registered as a virtual subclass of anything. It forces the + # negative cache to be cleared before its next use. + # Note: this counter is private. Use `abc.get_cache_token()` for + # external code. + _abc_invalidation_counter = 0 + + def __new__(mcls, name, bases, namespace, **kwargs): + cls = super().__new__(mcls, name, bases, namespace, **kwargs) + # Compute set of abstract method names + abstracts = {name + for name, value in namespace.items() + if getattr(value, "__isabstractmethod__", False)} + for base in bases: + for name in getattr(base, "__abstractmethods__", set()): + value = getattr(cls, name, None) + if getattr(value, "__isabstractmethod__", False): + abstracts.add(name) + cls.__abstractmethods__ = frozenset(abstracts) + # Set up inheritance registry + cls._abc_registry = WeakSet() + cls._abc_cache = WeakSet() + cls._abc_negative_cache = WeakSet() + cls._abc_negative_cache_version = ABCMeta._abc_invalidation_counter + return cls + +
[docs] def register(cls, subclass): + """Register a virtual subclass of an ABC. + + Returns the subclass, to allow usage as a class decorator. + """ + if not isinstance(subclass, type): + raise TypeError("Can only register classes") + if issubclass(subclass, cls): + return subclass # Already a subclass + # Subtle: test for cycles *after* testing for "already a subclass"; + # this means we allow X.register(X) and interpret it as a no-op. + if issubclass(cls, subclass): + # This would create a cycle, which is bad for the algorithm below + raise RuntimeError("Refusing to create an inheritance cycle") + cls._abc_registry.add(subclass) + ABCMeta._abc_invalidation_counter += 1 # Invalidate negative cache + return subclass
+ + def _dump_registry(cls, file=None): + """Debug helper to print the ABC registry.""" + print("Class: %s.%s" % (cls.__module__, cls.__qualname__), file=file) + print("Inv.counter: %s" % ABCMeta._abc_invalidation_counter, file=file) + for name in sorted(cls.__dict__): + if name.startswith("_abc_"): + value = getattr(cls, name) + if isinstance(value, WeakSet): + value = set(value) + print("%s: %r" % (name, value), file=file) + + def __instancecheck__(cls, instance): + """Override for isinstance(instance, cls).""" + # Inline the cache checking + subclass = instance.__class__ + if subclass in cls._abc_cache: + return True + subtype = type(instance) + if subtype is subclass: + if (cls._abc_negative_cache_version == + ABCMeta._abc_invalidation_counter and + subclass in cls._abc_negative_cache): + return False + # Fall back to the subclass check. + return cls.__subclasscheck__(subclass) + return any(cls.__subclasscheck__(c) for c in {subclass, subtype}) + + def __subclasscheck__(cls, subclass): + """Override for issubclass(subclass, cls).""" + # Check cache + if subclass in cls._abc_cache: + return True + # Check negative cache; may have to invalidate + if cls._abc_negative_cache_version < ABCMeta._abc_invalidation_counter: + # Invalidate the negative cache + cls._abc_negative_cache = WeakSet() + cls._abc_negative_cache_version = ABCMeta._abc_invalidation_counter + elif subclass in cls._abc_negative_cache: + return False + # Check the subclass hook + ok = cls.__subclasshook__(subclass) + if ok is not NotImplemented: + assert isinstance(ok, bool) + if ok: + cls._abc_cache.add(subclass) + else: + cls._abc_negative_cache.add(subclass) + return ok + # Check if it's a direct subclass + if cls in getattr(subclass, '__mro__', ()): + cls._abc_cache.add(subclass) + return True + # Check if it's a subclass of a registered class (recursive) + for rcls in cls._abc_registry: + if issubclass(subclass, rcls): + cls._abc_cache.add(subclass) + return True + # Check if it's a subclass of a subclass (recursive) + for scls in cls.__subclasses__(): + if issubclass(subclass, scls): + cls._abc_cache.add(subclass) + return True + # No dice; update negative cache + cls._abc_negative_cache.add(subclass) + return False
+ + +class ABC(metaclass=ABCMeta): + """Helper class that provides a standard way to create an ABC using + inheritance. + """ + pass + + +def get_cache_token(): + """Returns the current ABC cache token. + + The token is an opaque object (supporting equality testing) identifying the + current version of the ABC cache for virtual subclasses. The token changes + with every call to ``register()`` on any ABC. + """ + return ABCMeta._abc_invalidation_counter +
+ +
+ +
+
+ +
+
+ + + + \ No newline at end of file diff --git a/cleverhans_v3.1.0/docs/_modules/cleverhans/attacks/attack.html b/cleverhans_v3.1.0/docs/_modules/cleverhans/attacks/attack.html new file mode 100644 index 000000000..fdbc7c351 --- /dev/null +++ b/cleverhans_v3.1.0/docs/_modules/cleverhans/attacks/attack.html @@ -0,0 +1,438 @@ + + + + + + + + cleverhans.attacks.attack — CleverHans documentation + + + + + + + + + + + + + + + + + +
+
+
+ + +
+ +

Source code for cleverhans.attacks.attack

+"""
+The Attack interface.
+"""
+
+from abc import ABCMeta
+import collections
+import warnings
+
+import numpy as np
+import tensorflow as tf
+
+from cleverhans.compat import reduce_max
+from cleverhans.model import Model
+from cleverhans import utils
+
+_logger = utils.create_logger("cleverhans.attacks.attack")
+
+
+
[docs]class Attack(object): + """ + Abstract base class for all attack classes. + """ + __metaclass__ = ABCMeta + + def __init__(self, model, sess=None, dtypestr='float32', **kwargs): + """ + :param model: An instance of the cleverhans.model.Model class. + :param sess: The (possibly optional) tf.Session to run graphs in. + :param dtypestr: Floating point precision to use (change to float64 + to avoid numerical instabilities). + :param back: (deprecated and will be removed on or after 2019-03-26). + The backend to use. Currently 'tf' is the only option. + """ + if 'back' in kwargs: + if kwargs['back'] == 'tf': + warnings.warn("Argument back to attack constructors is not needed" + " anymore and will be removed on or after 2019-03-26." + " All attacks are implemented using TensorFlow.") + else: + raise ValueError("Backend argument must be 'tf' and is now deprecated" + "It will be removed on or after 2019-03-26.") + + self.tf_dtype = tf.as_dtype(dtypestr) + self.np_dtype = np.dtype(dtypestr) + + if sess is not None and not isinstance(sess, tf.Session): + raise TypeError("sess is not an instance of tf.Session") + + from cleverhans import attacks_tf + attacks_tf.np_dtype = self.np_dtype + attacks_tf.tf_dtype = self.tf_dtype + + if not isinstance(model, Model): + raise TypeError("The model argument should be an instance of" + " the cleverhans.model.Model class.") + + # Prepare attributes + self.model = model + self.sess = sess + self.dtypestr = dtypestr + + # We are going to keep track of old graphs and cache them. + self.graphs = {} + + # When calling generate_np, arguments in the following set should be + # fed into the graph, as they are not structural items that require + # generating a new graph. + # This dict should map names of arguments to the types they should + # have. + # (Usually, the target class will be a feedable keyword argument.) + self.feedable_kwargs = tuple() + + # When calling generate_np, arguments in the following set should NOT + # be fed into the graph, as they ARE structural items that require + # generating a new graph. + # This list should contain the names of the structural arguments. + self.structural_kwargs = [] + +
[docs] def generate(self, x, **kwargs): + """ + Generate the attack's symbolic graph for adversarial examples. This + method should be overriden in any child class that implements an + attack that is expressable symbolically. Otherwise, it will wrap the + numerical implementation as a symbolic operator. + + :param x: The model's symbolic inputs. + :param **kwargs: optional parameters used by child classes. + Each child class defines additional parameters as needed. + Child classes that use the following concepts should use the following + names: + clip_min: minimum feature value + clip_max: maximum feature value + eps: size of norm constraint on adversarial perturbation + ord: order of norm constraint + nb_iter: number of iterations + eps_iter: size of norm constraint on iteration + y_target: if specified, the attack is targeted. + y: Do not specify if y_target is specified. + If specified, the attack is untargeted, aims to make the output + class not be y. + If neither y_target nor y is specified, y is inferred by having + the model classify the input. + For other concepts, it's generally a good idea to read other classes + and check for name consistency. + :return: A symbolic representation of the adversarial examples. + """ + + error = "Sub-classes must implement generate." + raise NotImplementedError(error) + # Include an unused return so pylint understands the method signature + return x
+ +
[docs] def construct_graph(self, fixed, feedable, x_val, hash_key): + """ + Construct the graph required to run the attack through generate_np. + + :param fixed: Structural elements that require defining a new graph. + :param feedable: Arguments that can be fed to the same graph when + they take different values. + :param x_val: symbolic adversarial example + :param hash_key: the key used to store this graph in our cache + """ + # try our very best to create a TF placeholder for each of the + # feedable keyword arguments, and check the types are one of + # the allowed types + class_name = str(self.__class__).split(".")[-1][:-2] + _logger.info("Constructing new graph for attack " + class_name) + + # remove the None arguments, they are just left blank + for k in list(feedable.keys()): + if feedable[k] is None: + del feedable[k] + + # process all of the rest and create placeholders for them + new_kwargs = dict(x for x in fixed.items()) + for name, value in feedable.items(): + given_type = value.dtype + if isinstance(value, np.ndarray): + if value.ndim == 0: + # This is pretty clearly not a batch of data + new_kwargs[name] = tf.placeholder(given_type, shape=[], name=name) + else: + # Assume that this is a batch of data, make the first axis variable + # in size + new_shape = [None] + list(value.shape[1:]) + new_kwargs[name] = tf.placeholder(given_type, new_shape, name=name) + elif isinstance(value, utils.known_number_types): + new_kwargs[name] = tf.placeholder(given_type, shape=[], name=name) + else: + raise ValueError("Could not identify type of argument " + + name + ": " + str(value)) + + # x is a special placeholder we always want to have + x_shape = [None] + list(x_val.shape)[1:] + x = tf.placeholder(self.tf_dtype, shape=x_shape) + + # now we generate the graph that we want + x_adv = self.generate(x, **new_kwargs) + + self.graphs[hash_key] = (x, new_kwargs, x_adv) + + if len(self.graphs) >= 10: + warnings.warn("Calling generate_np() with multiple different " + "structural parameters is inefficient and should" + " be avoided. Calling generate() is preferred.")
+ +
[docs] def generate_np(self, x_val, **kwargs): + """ + Generate adversarial examples and return them as a NumPy array. + Sub-classes *should not* implement this method unless they must + perform special handling of arguments. + + :param x_val: A NumPy array with the original inputs. + :param **kwargs: optional parameters used by child classes. + :return: A NumPy array holding the adversarial examples. + """ + + if self.sess is None: + raise ValueError("Cannot use `generate_np` when no `sess` was" + " provided") + + packed = self.construct_variables(kwargs) + fixed, feedable, _, hash_key = packed + + if hash_key not in self.graphs: + self.construct_graph(fixed, feedable, x_val, hash_key) + else: + # remove the None arguments, they are just left blank + for k in list(feedable.keys()): + if feedable[k] is None: + del feedable[k] + + x, new_kwargs, x_adv = self.graphs[hash_key] + + feed_dict = {x: x_val} + + for name in feedable: + feed_dict[new_kwargs[name]] = feedable[name] + + return self.sess.run(x_adv, feed_dict)
+ +
[docs] def construct_variables(self, kwargs): + """ + Construct the inputs to the attack graph to be used by generate_np. + + :param kwargs: Keyword arguments to generate_np. + :return: + Structural arguments + Feedable arguments + Output of `arg_type` describing feedable arguments + A unique key + """ + if isinstance(self.feedable_kwargs, dict): + warnings.warn("Using a dict for `feedable_kwargs is deprecated." + "Switch to using a tuple." + "It is not longer necessary to specify the types " + "of the arguments---we build a different graph " + "for each received type." + "Using a dict may become an error on or after " + "2019-04-18.") + feedable_names = tuple(sorted(self.feedable_kwargs.keys())) + else: + feedable_names = self.feedable_kwargs + if not isinstance(feedable_names, tuple): + raise TypeError("Attack.feedable_kwargs should be a tuple, but " + "for subclass " + str(type(self)) + " it is " + + str(self.feedable_kwargs) + " of type " + + str(type(self.feedable_kwargs))) + + # the set of arguments that are structural properties of the attack + # if these arguments are different, we must construct a new graph + fixed = dict( + (k, v) for k, v in kwargs.items() if k in self.structural_kwargs) + + # the set of arguments that are passed as placeholders to the graph + # on each call, and can change without constructing a new graph + feedable = {k: v for k, v in kwargs.items() if k in feedable_names} + for k in feedable: + if isinstance(feedable[k], (float, int)): + feedable[k] = np.array(feedable[k]) + + for key in kwargs: + if key not in fixed and key not in feedable: + raise ValueError(str(type(self)) + ": Undeclared argument: " + key) + + feed_arg_type = arg_type(feedable_names, feedable) + + if not all(isinstance(value, collections.Hashable) + for value in fixed.values()): + # we have received a fixed value that isn't hashable + # this means we can't cache this graph for later use, + # and it will have to be discarded later + hash_key = None + else: + # create a unique key for this set of fixed paramaters + hash_key = tuple(sorted(fixed.items())) + tuple([feed_arg_type]) + + return fixed, feedable, feed_arg_type, hash_key
+ +
[docs] def get_or_guess_labels(self, x, kwargs): + """ + Get the label to use in generating an adversarial example for x. + The kwargs are fed directly from the kwargs of the attack. + If 'y' is in kwargs, then assume it's an untargeted attack and + use that as the label. + If 'y_target' is in kwargs and is not none, then assume it's a + targeted attack and use that as the label. + Otherwise, use the model's prediction as the label and perform an + untargeted attack. + """ + if 'y' in kwargs and 'y_target' in kwargs: + raise ValueError("Can not set both 'y' and 'y_target'.") + elif 'y' in kwargs: + labels = kwargs['y'] + elif 'y_target' in kwargs and kwargs['y_target'] is not None: + labels = kwargs['y_target'] + else: + preds = self.model.get_probs(x) + preds_max = reduce_max(preds, 1, keepdims=True) + original_predictions = tf.to_float(tf.equal(preds, preds_max)) + labels = tf.stop_gradient(original_predictions) + del preds + if isinstance(labels, np.ndarray): + nb_classes = labels.shape[1] + else: + nb_classes = labels.get_shape().as_list()[1] + return labels, nb_classes
+ +
[docs] def parse_params(self, params=None): + """ + Take in a dictionary of parameters and applies attack-specific checks + before saving them as attributes. + + :param params: a dictionary of attack-specific parameters + :return: True when parsing was successful + """ + + if params is not None: + warnings.warn("`params` is unused and will be removed " + " on or after 2019-04-26.") + return True
+ + +def arg_type(arg_names, kwargs): + """ + Returns a hashable summary of the types of arg_names within kwargs. + :param arg_names: tuple containing names of relevant arguments + :param kwargs: dict mapping string argument names to values. + These must be values for which we can create a tf placeholder. + Currently supported: numpy darray or something that can ducktype it + returns: + API contract is to return a hashable object describing all + structural consequences of argument values that can otherwise + be fed into a graph of fixed structure. + Currently this is implemented as a tuple of tuples that track: + - whether each argument was passed + - whether each argument was passed and not None + - the dtype of each argument + Callers shouldn't rely on the exact structure of this object, + just its hashability and one-to-one mapping between graph structures. + """ + assert isinstance(arg_names, tuple) + passed = tuple(name in kwargs for name in arg_names) + passed_and_not_none = [] + for name in arg_names: + if name in kwargs: + passed_and_not_none.append(kwargs[name] is not None) + else: + passed_and_not_none.append(False) + passed_and_not_none = tuple(passed_and_not_none) + dtypes = [] + for name in arg_names: + if name not in kwargs: + dtypes.append(None) + continue + value = kwargs[name] + if value is None: + dtypes.append(None) + continue + assert hasattr(value, 'dtype'), type(value) + dtype = value.dtype + if not isinstance(dtype, np.dtype): + dtype = dtype.as_np_dtype + assert isinstance(dtype, np.dtype) + dtypes.append(dtype) + dtypes = tuple(dtypes) + return (passed, passed_and_not_none, dtypes) +
+ +
+ +
+
+ +
+
+ + + + \ No newline at end of file diff --git a/cleverhans_v3.1.0/docs/_modules/cleverhans/attacks/basic_iterative_method.html b/cleverhans_v3.1.0/docs/_modules/cleverhans/attacks/basic_iterative_method.html new file mode 100644 index 000000000..2b54c2554 --- /dev/null +++ b/cleverhans_v3.1.0/docs/_modules/cleverhans/attacks/basic_iterative_method.html @@ -0,0 +1,107 @@ + + + + + + + + cleverhans.attacks.basic_iterative_method — CleverHans documentation + + + + + + + + + + + + + + + + + +
+
+
+ + +
+ +

Source code for cleverhans.attacks.basic_iterative_method

+"""
+The BasicIterativeMethod attack.
+"""
+
+from cleverhans.attacks.projected_gradient_descent import ProjectedGradientDescent
+
+
+
[docs]class BasicIterativeMethod(ProjectedGradientDescent): + """ + The BasicIterativeMethod attack. + """ + def __init__(self, model, sess=None, dtypestr='float32', **kwargs): + super(BasicIterativeMethod, self).__init__(model, sess=sess, + dtypestr=dtypestr, + default_rand_init=False, + **kwargs)
+
+ +
+ +
+
+ +
+
+ + + + \ No newline at end of file diff --git a/cleverhans_v3.1.0/docs/_modules/cleverhans/attacks/carlini_wagner_l2.html b/cleverhans_v3.1.0/docs/_modules/cleverhans/attacks/carlini_wagner_l2.html new file mode 100644 index 000000000..d5bae7236 --- /dev/null +++ b/cleverhans_v3.1.0/docs/_modules/cleverhans/attacks/carlini_wagner_l2.html @@ -0,0 +1,506 @@ + + + + + + + + cleverhans.attacks.carlini_wagner_l2 — CleverHans documentation + + + + + + + + + + + + + + + + + +
+
+
+ + +
+ +

Source code for cleverhans.attacks.carlini_wagner_l2

+"""The CarliniWagnerL2 attack
+"""
+# pylint: disable=missing-docstring
+import logging
+
+import numpy as np
+import tensorflow as tf
+
+from cleverhans.attacks.attack import Attack
+from cleverhans.compat import reduce_sum, reduce_max
+from cleverhans.model import CallableModelWrapper, Model, wrapper_warning_logits
+from cleverhans import utils
+
+np_dtype = np.dtype('float32')
+tf_dtype = tf.as_dtype('float32')
+
+_logger = utils.create_logger("cleverhans.attacks.carlini_wagner_l2")
+_logger.setLevel(logging.INFO)
+
+
+
[docs]class CarliniWagnerL2(Attack): + """ + This attack was originally proposed by Carlini and Wagner. It is an + iterative attack that finds adversarial examples on many defenses that + are robust to other attacks. + Paper link: https://arxiv.org/abs/1608.04644 + + At a high level, this attack is an iterative attack using Adam and + a specially-chosen loss function to find adversarial examples with + lower distortion than other attacks. This comes at the cost of speed, + as this attack is often much slower than others. + + :param model: cleverhans.model.Model + :param sess: tf.Session + :param dtypestr: dtype of the data + :param kwargs: passed through to super constructor + """ + + def __init__(self, model, sess, dtypestr='float32', **kwargs): + """ + Note: the model parameter should be an instance of the + cleverhans.model.Model abstraction provided by CleverHans. + """ + if not isinstance(model, Model): + wrapper_warning_logits() + model = CallableModelWrapper(model, 'logits') + + super(CarliniWagnerL2, self).__init__(model, sess, dtypestr, **kwargs) + + self.feedable_kwargs = ('y', 'y_target') + + self.structural_kwargs = [ + 'batch_size', 'confidence', 'targeted', 'learning_rate', + 'binary_search_steps', 'max_iterations', 'abort_early', + 'initial_const', 'clip_min', 'clip_max' + ] + +
[docs] def generate(self, x, **kwargs): + """ + Return a tensor that constructs adversarial examples for the given + input. Generate uses tf.py_func in order to operate over tensors. + + :param x: A tensor with the inputs. + :param kwargs: See `parse_params` + """ + assert self.sess is not None, \ + 'Cannot use `generate` when no `sess` was provided' + self.parse_params(**kwargs) + + labels, nb_classes = self.get_or_guess_labels(x, kwargs) + + attack = CWL2(self.sess, self.model, self.batch_size, self.confidence, + 'y_target' in kwargs, self.learning_rate, + self.binary_search_steps, self.max_iterations, + self.abort_early, self.initial_const, self.clip_min, + self.clip_max, nb_classes, + x.get_shape().as_list()[1:]) + + def cw_wrap(x_val, y_val): + return np.array(attack.attack(x_val, y_val), dtype=self.np_dtype) + + wrap = tf.py_func(cw_wrap, [x, labels], self.tf_dtype) + wrap.set_shape(x.get_shape()) + + return wrap
+ +
[docs] def parse_params(self, + y=None, + y_target=None, + batch_size=1, + confidence=0, + learning_rate=5e-3, + binary_search_steps=5, + max_iterations=1000, + abort_early=True, + initial_const=1e-2, + clip_min=0, + clip_max=1): + """ + :param y: (optional) A tensor with the true labels for an untargeted + attack. If None (and y_target is None) then use the + original labels the classifier assigns. + :param y_target: (optional) A tensor with the target labels for a + targeted attack. + :param confidence: Confidence of adversarial examples: higher produces + examples with larger l2 distortion, but more + strongly classified as adversarial. + :param batch_size: Number of attacks to run simultaneously. + :param learning_rate: The learning rate for the attack algorithm. + Smaller values produce better results but are + slower to converge. + :param binary_search_steps: The number of times we perform binary + search to find the optimal tradeoff- + constant between norm of the purturbation + and confidence of the classification. + :param max_iterations: The maximum number of iterations. Setting this + to a larger value will produce lower distortion + results. Using only a few iterations requires + a larger learning rate, and will produce larger + distortion results. + :param abort_early: If true, allows early aborts if gradient descent + is unable to make progress (i.e., gets stuck in + a local minimum). + :param initial_const: The initial tradeoff-constant to use to tune the + relative importance of size of the perturbation + and confidence of classification. + If binary_search_steps is large, the initial + constant is not important. A smaller value of + this constant gives lower distortion results. + :param clip_min: (optional float) Minimum input component value + :param clip_max: (optional float) Maximum input component value + """ + + # ignore the y and y_target argument + self.batch_size = batch_size + self.confidence = confidence + self.learning_rate = learning_rate + self.binary_search_steps = binary_search_steps + self.max_iterations = max_iterations + self.abort_early = abort_early + self.initial_const = initial_const + self.clip_min = clip_min + self.clip_max = clip_max
+ + +def ZERO(): + return np.asarray(0., dtype=np_dtype) + + +class CWL2(object): + def __init__(self, sess, model, batch_size, confidence, targeted, + learning_rate, binary_search_steps, max_iterations, + abort_early, initial_const, clip_min, clip_max, num_labels, + shape): + """ + Return a tensor that constructs adversarial examples for the given + input. Generate uses tf.py_func in order to operate over tensors. + + :param sess: a TF session. + :param model: a cleverhans.model.Model object. + :param batch_size: Number of attacks to run simultaneously. + :param confidence: Confidence of adversarial examples: higher produces + examples with larger l2 distortion, but more + strongly classified as adversarial. + :param targeted: boolean controlling the behavior of the adversarial + examples produced. If set to False, they will be + misclassified in any wrong class. If set to True, + they will be misclassified in a chosen target class. + :param learning_rate: The learning rate for the attack algorithm. + Smaller values produce better results but are + slower to converge. + :param binary_search_steps: The number of times we perform binary + search to find the optimal tradeoff- + constant between norm of the purturbation + and confidence of the classification. + :param max_iterations: The maximum number of iterations. Setting this + to a larger value will produce lower distortion + results. Using only a few iterations requires + a larger learning rate, and will produce larger + distortion results. + :param abort_early: If true, allows early aborts if gradient descent + is unable to make progress (i.e., gets stuck in + a local minimum). + :param initial_const: The initial tradeoff-constant to use to tune the + relative importance of size of the pururbation + and confidence of classification. + If binary_search_steps is large, the initial + constant is not important. A smaller value of + this constant gives lower distortion results. + :param clip_min: (optional float) Minimum input component value. + :param clip_max: (optional float) Maximum input component value. + :param num_labels: the number of classes in the model's output. + :param shape: the shape of the model's input tensor. + """ + + self.sess = sess + self.TARGETED = targeted + self.LEARNING_RATE = learning_rate + self.MAX_ITERATIONS = max_iterations + self.BINARY_SEARCH_STEPS = binary_search_steps + self.ABORT_EARLY = abort_early + self.CONFIDENCE = confidence + self.initial_const = initial_const + self.batch_size = batch_size + self.clip_min = clip_min + self.clip_max = clip_max + self.model = model + + self.repeat = binary_search_steps >= 10 + + self.shape = shape = tuple([batch_size] + list(shape)) + + # the variable we're going to optimize over + modifier = tf.Variable(np.zeros(shape, dtype=np_dtype)) + + # these are variables to be more efficient in sending data to tf + self.timg = tf.Variable(np.zeros(shape), dtype=tf_dtype, name='timg') + self.tlab = tf.Variable( + np.zeros((batch_size, num_labels)), dtype=tf_dtype, name='tlab') + self.const = tf.Variable( + np.zeros(batch_size), dtype=tf_dtype, name='const') + + # and here's what we use to assign them + self.assign_timg = tf.placeholder(tf_dtype, shape, name='assign_timg') + self.assign_tlab = tf.placeholder( + tf_dtype, (batch_size, num_labels), name='assign_tlab') + self.assign_const = tf.placeholder( + tf_dtype, [batch_size], name='assign_const') + + # the resulting instance, tanh'd to keep bounded from clip_min + # to clip_max + self.newimg = (tf.tanh(modifier + self.timg) + 1) / 2 + self.newimg = self.newimg * (clip_max - clip_min) + clip_min + + # prediction BEFORE-SOFTMAX of the model + self.output = model.get_logits(self.newimg) + + # distance to the input data + self.other = (tf.tanh(self.timg) + 1) / \ + 2 * (clip_max - clip_min) + clip_min + self.l2dist = reduce_sum( + tf.square(self.newimg - self.other), list(range(1, len(shape)))) + + # compute the probability of the label class versus the maximum other + real = reduce_sum((self.tlab) * self.output, 1) + other = reduce_max((1 - self.tlab) * self.output - self.tlab * 10000, + 1) + + if self.TARGETED: + # if targeted, optimize for making the other class most likely + loss1 = tf.maximum(ZERO(), other - real + self.CONFIDENCE) + else: + # if untargeted, optimize for making this class least likely. + loss1 = tf.maximum(ZERO(), real - other + self.CONFIDENCE) + + # sum up the losses + self.loss2 = reduce_sum(self.l2dist) + self.loss1 = reduce_sum(self.const * loss1) + self.loss = self.loss1 + self.loss2 + + # Setup the adam optimizer and keep track of variables we're creating + start_vars = set(x.name for x in tf.global_variables()) + optimizer = tf.train.AdamOptimizer(self.LEARNING_RATE) + self.train = optimizer.minimize(self.loss, var_list=[modifier]) + end_vars = tf.global_variables() + new_vars = [x for x in end_vars if x.name not in start_vars] + + # these are the variables to initialize when we run + self.setup = [] + self.setup.append(self.timg.assign(self.assign_timg)) + self.setup.append(self.tlab.assign(self.assign_tlab)) + self.setup.append(self.const.assign(self.assign_const)) + + self.init = tf.variables_initializer(var_list=[modifier] + new_vars) + + def attack(self, imgs, targets): + """ + Perform the L_2 attack on the given instance for the given targets. + + If self.targeted is true, then the targets represents the target labels + If self.targeted is false, then targets are the original class labels + """ + + r = [] + for i in range(0, len(imgs), self.batch_size): + _logger.debug( + ("Running CWL2 attack on instance %s of %s", i, len(imgs))) + r.extend( + self.attack_batch(imgs[i:i + self.batch_size], + targets[i:i + self.batch_size])) + return np.array(r) + + def attack_batch(self, imgs, labs): + """ + Run the attack on a batch of instance and labels. + """ + + def compare(x, y): + if not isinstance(x, (float, int, np.int64)): + x = np.copy(x) + if self.TARGETED: + x[y] -= self.CONFIDENCE + else: + x[y] += self.CONFIDENCE + x = np.argmax(x) + if self.TARGETED: + return x == y + else: + return x != y + + batch_size = self.batch_size + + oimgs = np.clip(imgs, self.clip_min, self.clip_max) + + # re-scale instances to be within range [0, 1] + imgs = (imgs - self.clip_min) / (self.clip_max - self.clip_min) + imgs = np.clip(imgs, 0, 1) + # now convert to [-1, 1] + imgs = (imgs * 2) - 1 + # convert to tanh-space + imgs = np.arctanh(imgs * .999999) + + # set the lower and upper bounds accordingly + lower_bound = np.zeros(batch_size) + CONST = np.ones(batch_size) * self.initial_const + upper_bound = np.ones(batch_size) * 1e10 + + # placeholders for the best l2, score, and instance attack found so far + o_bestl2 = [1e10] * batch_size + o_bestscore = [-1] * batch_size + o_bestattack = np.copy(oimgs) + + for outer_step in range(self.BINARY_SEARCH_STEPS): + # completely reset adam's internal state. + self.sess.run(self.init) + batch = imgs[:batch_size] + batchlab = labs[:batch_size] + + bestl2 = [1e10] * batch_size + bestscore = [-1] * batch_size + _logger.debug(" Binary search step %s of %s", + outer_step, self.BINARY_SEARCH_STEPS) + + # The last iteration (if we run many steps) repeat the search once. + if self.repeat and outer_step == self.BINARY_SEARCH_STEPS - 1: + CONST = upper_bound + + # set the variables so that we don't have to send them over again + self.sess.run( + self.setup, { + self.assign_timg: batch, + self.assign_tlab: batchlab, + self.assign_const: CONST + }) + + prev = 1e6 + for iteration in range(self.MAX_ITERATIONS): + # perform the attack + _, l, l2s, scores, nimg = self.sess.run([ + self.train, self.loss, self.l2dist, self.output, + self.newimg + ]) + + if iteration % ((self.MAX_ITERATIONS // 10) or 1) == 0: + _logger.debug((" Iteration {} of {}: loss={:.3g} " + + "l2={:.3g} f={:.3g}").format( + iteration, self.MAX_ITERATIONS, l, + np.mean(l2s), np.mean(scores))) + + # check if we should abort search if we're getting nowhere. + if self.ABORT_EARLY and \ + iteration % ((self.MAX_ITERATIONS // 10) or 1) == 0: + if l > prev * .9999: + msg = " Failed to make progress; stop early" + _logger.debug(msg) + break + prev = l + + # adjust the best result found so far + for e, (l2, sc, ii) in enumerate(zip(l2s, scores, nimg)): + lab = np.argmax(batchlab[e]) + if l2 < bestl2[e] and compare(sc, lab): + bestl2[e] = l2 + bestscore[e] = np.argmax(sc) + if l2 < o_bestl2[e] and compare(sc, lab): + o_bestl2[e] = l2 + o_bestscore[e] = np.argmax(sc) + o_bestattack[e] = ii + + # adjust the constant as needed + for e in range(batch_size): + if compare(bestscore[e], np.argmax(batchlab[e])) and \ + bestscore[e] != -1: + # success, divide const by two + upper_bound[e] = min(upper_bound[e], CONST[e]) + if upper_bound[e] < 1e9: + CONST[e] = (lower_bound[e] + upper_bound[e]) / 2 + else: + # failure, either multiply by 10 if no solution found yet + # or do binary search with the known upper bound + lower_bound[e] = max(lower_bound[e], CONST[e]) + if upper_bound[e] < 1e9: + CONST[e] = (lower_bound[e] + upper_bound[e]) / 2 + else: + CONST[e] *= 10 + _logger.debug(" Successfully generated adversarial examples " + + "on {} of {} instances.".format( + sum(upper_bound < 1e9), batch_size)) + o_bestl2 = np.array(o_bestl2) + mean = np.mean(np.sqrt(o_bestl2[o_bestl2 < 1e9])) + _logger.debug(" Mean successful distortion: {:.4g}".format(mean)) + + # return the best solution found + o_bestl2 = np.array(o_bestl2) + return o_bestattack +
+ +
+ +
+
+ +
+
+ + + + \ No newline at end of file diff --git a/cleverhans_v3.1.0/docs/_modules/cleverhans/attacks/deep_fool.html b/cleverhans_v3.1.0/docs/_modules/cleverhans/attacks/deep_fool.html new file mode 100644 index 000000000..a00ba3daa --- /dev/null +++ b/cleverhans_v3.1.0/docs/_modules/cleverhans/attacks/deep_fool.html @@ -0,0 +1,343 @@ + + + + + + + + cleverhans.attacks.deep_fool — CleverHans documentation + + + + + + + + + + + + + + + + + +
+
+
+ + +
+ +

Source code for cleverhans.attacks.deep_fool

+"""The DeepFool attack
+
+"""
+import copy
+import logging
+import warnings
+
+import numpy as np
+import tensorflow as tf
+
+from cleverhans.attacks.attack import Attack
+from cleverhans.model import Model, wrapper_warning_logits, CallableModelWrapper
+from cleverhans import utils
+from cleverhans import utils_tf
+
+np_dtype = np.dtype('float32')
+
+_logger = utils.create_logger("cleverhans.attacks.deep_fool")
+_logger.setLevel(logging.INFO)
+
+
[docs]class DeepFool(Attack): + """ + DeepFool is an untargeted & iterative attack which is based on an + iterative linearization of the classifier. The implementation here + is w.r.t. the L2 norm. + Paper link: "https://arxiv.org/pdf/1511.04599.pdf" + + :param model: cleverhans.model.Model + :param sess: tf.Session + :param dtypestr: dtype of the data + :param kwargs: passed through to super constructor + """ + + def __init__(self, model, sess, dtypestr='float32', **kwargs): + """ + Create a DeepFool instance. + """ + if not isinstance(model, Model): + wrapper_warning_logits() + model = CallableModelWrapper(model, 'logits') + + super(DeepFool, self).__init__(model, sess, dtypestr, **kwargs) + + self.structural_kwargs = [ + 'overshoot', 'max_iter', 'clip_max', 'clip_min', 'nb_candidate' + ] + +
[docs] def generate(self, x, **kwargs): + """ + Generate symbolic graph for adversarial examples and return. + + :param x: The model's symbolic inputs. + :param kwargs: See `parse_params` + """ + assert self.sess is not None, \ + 'Cannot use `generate` when no `sess` was provided' + from cleverhans.utils_tf import jacobian_graph + + # Parse and save attack-specific parameters + assert self.parse_params(**kwargs) + + # Define graph wrt to this input placeholder + logits = self.model.get_logits(x) + self.nb_classes = logits.get_shape().as_list()[-1] + assert self.nb_candidate <= self.nb_classes, \ + 'nb_candidate should not be greater than nb_classes' + preds = tf.reshape( + tf.nn.top_k(logits, k=self.nb_candidate)[0], + [-1, self.nb_candidate]) + # grads will be the shape [batch_size, nb_candidate, image_size] + grads = tf.stack(jacobian_graph(preds, x, self.nb_candidate), axis=1) + + # Define graph + def deepfool_wrap(x_val): + """deepfool function for py_func""" + return deepfool_batch(self.sess, x, preds, logits, grads, x_val, + self.nb_candidate, self.overshoot, + self.max_iter, self.clip_min, self.clip_max, + self.nb_classes) + + wrap = tf.py_func(deepfool_wrap, [x], self.tf_dtype) + wrap.set_shape(x.get_shape()) + return wrap
+ +
[docs] def parse_params(self, + nb_candidate=10, + overshoot=0.02, + max_iter=50, + clip_min=0., + clip_max=1., + **kwargs): + """ + :param nb_candidate: The number of classes to test against, i.e., + deepfool only consider nb_candidate classes when + attacking(thus accelerate speed). The nb_candidate + classes are chosen according to the prediction + confidence during implementation. + :param overshoot: A termination criterion to prevent vanishing updates + :param max_iter: Maximum number of iteration for deepfool + :param clip_min: Minimum component value for clipping + :param clip_max: Maximum component value for clipping + """ + self.nb_candidate = nb_candidate + self.overshoot = overshoot + self.max_iter = max_iter + self.clip_min = clip_min + self.clip_max = clip_max + if len(kwargs.keys()) > 0: + warnings.warn("kwargs is unused and will be removed on or after " + "2019-04-26.") + + return True
+ + +def deepfool_batch(sess, + x, + pred, + logits, + grads, + X, + nb_candidate, + overshoot, + max_iter, + clip_min, + clip_max, + nb_classes, + feed=None): + """ + Applies DeepFool to a batch of inputs + :param sess: TF session + :param x: The input placeholder + :param pred: The model's sorted symbolic output of logits, only the top + nb_candidate classes are contained + :param logits: The model's unnormalized output tensor (the input to + the softmax layer) + :param grads: Symbolic gradients of the top nb_candidate classes, procuded + from gradient_graph + :param X: Numpy array with sample inputs + :param nb_candidate: The number of classes to test against, i.e., + deepfool only consider nb_candidate classes when + attacking(thus accelerate speed). The nb_candidate + classes are chosen according to the prediction + confidence during implementation. + :param overshoot: A termination criterion to prevent vanishing updates + :param max_iter: Maximum number of iteration for DeepFool + :param clip_min: Minimum value for components of the example returned + :param clip_max: Maximum value for components of the example returned + :param nb_classes: Number of model output classes + :return: Adversarial examples + """ + X_adv = deepfool_attack( + sess, + x, + pred, + logits, + grads, + X, + nb_candidate, + overshoot, + max_iter, + clip_min, + clip_max, + feed=feed) + + return np.asarray(X_adv, dtype=np_dtype) + + +def deepfool_attack(sess, + x, + predictions, + logits, + grads, + sample, + nb_candidate, + overshoot, + max_iter, + clip_min, + clip_max, + feed=None): + """ + TensorFlow implementation of DeepFool. + Paper link: see https://arxiv.org/pdf/1511.04599.pdf + :param sess: TF session + :param x: The input placeholder + :param predictions: The model's sorted symbolic output of logits, only the + top nb_candidate classes are contained + :param logits: The model's unnormalized output tensor (the input to + the softmax layer) + :param grads: Symbolic gradients of the top nb_candidate classes, procuded + from gradient_graph + :param sample: Numpy array with sample input + :param nb_candidate: The number of classes to test against, i.e., + deepfool only consider nb_candidate classes when + attacking(thus accelerate speed). The nb_candidate + classes are chosen according to the prediction + confidence during implementation. + :param overshoot: A termination criterion to prevent vanishing updates + :param max_iter: Maximum number of iteration for DeepFool + :param clip_min: Minimum value for components of the example returned + :param clip_max: Maximum value for components of the example returned + :return: Adversarial examples + """ + adv_x = copy.copy(sample) + # Initialize the loop variables + iteration = 0 + current = utils_tf.model_argmax(sess, x, logits, adv_x, feed=feed) + if current.shape == (): + current = np.array([current]) + w = np.squeeze(np.zeros(sample.shape[1:])) # same shape as original image + r_tot = np.zeros(sample.shape) + original = current # use original label as the reference + + _logger.debug( + "Starting DeepFool attack up to %s iterations", max_iter) + # Repeat this main loop until we have achieved misclassification + while (np.any(current == original) and iteration < max_iter): + + if iteration % 5 == 0 and iteration > 0: + _logger.info("Attack result at iteration %s is %s", iteration, current) + gradients = sess.run(grads, feed_dict={x: adv_x}) + predictions_val = sess.run(predictions, feed_dict={x: adv_x}) + for idx in range(sample.shape[0]): + pert = np.inf + if current[idx] != original[idx]: + continue + for k in range(1, nb_candidate): + w_k = gradients[idx, k, ...] - gradients[idx, 0, ...] + f_k = predictions_val[idx, k] - predictions_val[idx, 0] + # adding value 0.00001 to prevent f_k = 0 + pert_k = (abs(f_k) + 0.00001) / np.linalg.norm(w_k.flatten()) + if pert_k < pert: + pert = pert_k + w = w_k + r_i = pert * w / np.linalg.norm(w) + r_tot[idx, ...] = r_tot[idx, ...] + r_i + + adv_x = np.clip(r_tot + sample, clip_min, clip_max) + current = utils_tf.model_argmax(sess, x, logits, adv_x, feed=feed) + if current.shape == (): + current = np.array([current]) + # Update loop variables + iteration = iteration + 1 + + # need more revision, including info like how many succeed + _logger.info("Attack result at iteration %s is %s", iteration, current) + _logger.info("%s out of %s become adversarial examples at iteration %s", + sum(current != original), + sample.shape[0], + iteration) + # need to clip this image into the given range + adv_x = np.clip((1 + overshoot) * r_tot + sample, clip_min, clip_max) + return adv_x +
+ +
+ +
+
+ +
+
+ + + + \ No newline at end of file diff --git a/cleverhans_v3.1.0/docs/_modules/cleverhans/attacks/elastic_net_method.html b/cleverhans_v3.1.0/docs/_modules/cleverhans/attacks/elastic_net_method.html new file mode 100644 index 000000000..d9b121b9d --- /dev/null +++ b/cleverhans_v3.1.0/docs/_modules/cleverhans/attacks/elastic_net_method.html @@ -0,0 +1,617 @@ + + + + + + + + cleverhans.attacks.elastic_net_method — CleverHans documentation + + + + + + + + + + + + + + + + + +
+
+
+ + +
+ +

Source code for cleverhans.attacks.elastic_net_method

+"""The ElasticNetMethod attack.
+"""
+# pylint: disable=missing-docstring
+import logging
+
+import numpy as np
+import tensorflow as tf
+
+from cleverhans.attacks.attack import Attack
+from cleverhans.compat import reduce_sum, reduce_max
+from cleverhans.model import Model, CallableModelWrapper, wrapper_warning_logits
+from cleverhans import utils
+
+np_dtype = np.dtype('float32')
+tf_dtype = tf.as_dtype('float32')
+
+_logger = utils.create_logger("cleverhans.attacks.elastic_net_method")
+_logger.setLevel(logging.INFO)
+
+
+def ZERO():
+  return np.asarray(0., dtype=np_dtype)
+
+
+
[docs]class ElasticNetMethod(Attack): + """ + This attack features L1-oriented adversarial examples and includes + the C&W L2 attack as a special case (when beta is set to 0). + Adversarial examples attain similar performance to those + generated by the C&W L2 attack in the white-box case, + and more importantly, have improved transferability properties + and complement adversarial training. + Paper link: https://arxiv.org/abs/1709.04114 + + :param model: cleverhans.model.Model + :param sess: tf.Session + :param dtypestr: dtype of the data + :param kwargs: passed through to super constructor + """ + + def __init__(self, model, sess, dtypestr='float32', **kwargs): + """ + Note: the model parameter should be an instance of the + cleverhans.model.Model abstraction provided by CleverHans. + """ + if not isinstance(model, Model): + wrapper_warning_logits() + model = CallableModelWrapper(model, 'logits') + + super(ElasticNetMethod, self).__init__(model, sess, dtypestr, **kwargs) + + self.feedable_kwargs = ('y', 'y_target') + + self.structural_kwargs = [ + 'beta', 'decision_rule', 'batch_size', 'confidence', + 'targeted', 'learning_rate', 'binary_search_steps', + 'max_iterations', 'abort_early', 'initial_const', 'clip_min', + 'clip_max' + ] + +
[docs] def generate(self, x, **kwargs): + """ + Return a tensor that constructs adversarial examples for the given + input. Generate uses tf.py_func in order to operate over tensors. + + :param x: (required) A tensor with the inputs. + :param kwargs: See `parse_params` + """ + assert self.sess is not None, \ + 'Cannot use `generate` when no `sess` was provided' + self.parse_params(**kwargs) + + labels, nb_classes = self.get_or_guess_labels(x, kwargs) + + attack = EAD(self.sess, self.model, self.beta, + self.decision_rule, self.batch_size, self.confidence, + 'y_target' in kwargs, self.learning_rate, + self.binary_search_steps, self.max_iterations, + self.abort_early, self.initial_const, self.clip_min, + self.clip_max, nb_classes, + x.get_shape().as_list()[1:]) + + def ead_wrap(x_val, y_val): + return np.array(attack.attack(x_val, y_val), dtype=self.np_dtype) + + wrap = tf.py_func(ead_wrap, [x, labels], self.tf_dtype) + wrap.set_shape(x.get_shape()) + + return wrap
+ +
[docs] def parse_params(self, + y=None, + y_target=None, + beta=1e-2, + decision_rule='EN', + batch_size=1, + confidence=0, + learning_rate=1e-2, + binary_search_steps=9, + max_iterations=1000, + abort_early=False, + initial_const=1e-3, + clip_min=0, + clip_max=1): + """ + :param y: (optional) A tensor with the true labels for an untargeted + attack. If None (and y_target is None) then use the + original labels the classifier assigns. + :param y_target: (optional) A tensor with the target labels for a + targeted attack. + :param beta: Trades off L2 distortion with L1 distortion: higher + produces examples with lower L1 distortion, at the + cost of higher L2 (and typically Linf) distortion + :param decision_rule: EN or L1. Select final adversarial example from + all successful examples based on the least + elastic-net or L1 distortion criterion. + :param confidence: Confidence of adversarial examples: higher produces + examples with larger l2 distortion, but more + strongly classified as adversarial. + :param batch_size: Number of attacks to run simultaneously. + :param learning_rate: The learning rate for the attack algorithm. + Smaller values produce better results but are + slower to converge. + :param binary_search_steps: The number of times we perform binary + search to find the optimal tradeoff- + constant between norm of the perturbation + and confidence of the classification. Set + 'initial_const' to a large value and fix + this param to 1 for speed. + :param max_iterations: The maximum number of iterations. Setting this + to a larger value will produce lower distortion + results. Using only a few iterations requires + a larger learning rate, and will produce larger + distortion results. + :param abort_early: If true, allows early abort when the total + loss starts to increase (greatly speeds up attack, + but hurts performance, particularly on ImageNet) + :param initial_const: The initial tradeoff-constant to use to tune the + relative importance of size of the perturbation + and confidence of classification. + If binary_search_steps is large, the initial + constant is not important. A smaller value of + this constant gives lower distortion results. + For computational efficiency, fix + binary_search_steps to 1 and set this param + to a large value. + :param clip_min: (optional float) Minimum input component value + :param clip_max: (optional float) Maximum input component value + """ + + # ignore the y and y_target argument + self.beta = beta + self.decision_rule = decision_rule + self.batch_size = batch_size + self.confidence = confidence + self.learning_rate = learning_rate + self.binary_search_steps = binary_search_steps + self.max_iterations = max_iterations + self.abort_early = abort_early + self.initial_const = initial_const + self.clip_min = clip_min + self.clip_max = clip_max
+ + +class EAD(object): + def __init__(self, sess, model, beta, decision_rule, batch_size, + confidence, targeted, learning_rate, binary_search_steps, + max_iterations, abort_early, initial_const, clip_min, + clip_max, num_labels, shape): + """ + EAD Attack + + Return a tensor that constructs adversarial examples for the given + input. Generate uses tf.py_func in order to operate over tensors. + + :param sess: a TF session. + :param model: a cleverhans.model.Model object. + :param beta: Trades off L2 distortion with L1 distortion: higher + produces examples with lower L1 distortion, at the + cost of higher L2 (and typically Linf) distortion + :param decision_rule: EN or L1. Select final adversarial example from + all successful examples based on the least + elastic-net or L1 distortion criterion. + :param batch_size: Number of attacks to run simultaneously. + :param confidence: Confidence of adversarial examples: higher produces + examples with larger l2 distortion, but more + strongly classified as adversarial. + :param targeted: boolean controlling the behavior of the adversarial + examples produced. If set to False, they will be + misclassified in any wrong class. If set to True, + they will be misclassified in a chosen target class. + :param learning_rate: The learning rate for the attack algorithm. + Smaller values produce better results but are + slower to converge. + :param binary_search_steps: The number of times we perform binary + search to find the optimal tradeoff- + constant between norm of the perturbation + and confidence of the classification. Set + 'initial_const' to a large value and fix + this param to 1 for speed. + :param max_iterations: The maximum number of iterations. Setting this + to a larger value will produce lower distortion + results. Using only a few iterations requires + a larger learning rate, and will produce larger + distortion results. + :param abort_early: If true, allows early abort when the total + loss starts to increase (greatly speeds up attack, + but hurts performance, particularly on ImageNet) + :param initial_const: The initial tradeoff-constant to use to tune the + relative importance of size of the perturbation + and confidence of classification. + If binary_search_steps is large, the initial + constant is not important. A smaller value of + this constant gives lower distortion results. + For computational efficiency, fix + binary_search_steps to 1 and set this param + to a large value. + :param clip_min: (optional float) Minimum input component value. + :param clip_max: (optional float) Maximum input component value. + :param num_labels: the number of classes in the model's output. + :param shape: the shape of the model's input tensor. + """ + + self.sess = sess + self.TARGETED = targeted + self.LEARNING_RATE = learning_rate + self.MAX_ITERATIONS = max_iterations + self.BINARY_SEARCH_STEPS = binary_search_steps + self.ABORT_EARLY = abort_early + self.CONFIDENCE = confidence + self.initial_const = initial_const + self.batch_size = batch_size + self.clip_min = clip_min + self.clip_max = clip_max + self.model = model + self.decision_rule = decision_rule + + self.beta = beta + self.beta_t = tf.cast(self.beta, tf_dtype) + + self.repeat = binary_search_steps >= 10 + + self.shape = shape = tuple([batch_size] + list(shape)) + + # these are variables to be more efficient in sending data to tf + self.timg = tf.Variable(np.zeros(shape), dtype=tf_dtype, name='timg') + self.newimg = tf.Variable( + np.zeros(shape), dtype=tf_dtype, name='newimg') + self.slack = tf.Variable( + np.zeros(shape), dtype=tf_dtype, name='slack') + self.tlab = tf.Variable( + np.zeros((batch_size, num_labels)), dtype=tf_dtype, name='tlab') + self.const = tf.Variable( + np.zeros(batch_size), dtype=tf_dtype, name='const') + + # and here's what we use to assign them + self.assign_timg = tf.placeholder(tf_dtype, shape, name='assign_timg') + self.assign_newimg = tf.placeholder( + tf_dtype, shape, name='assign_newimg') + self.assign_slack = tf.placeholder( + tf_dtype, shape, name='assign_slack') + self.assign_tlab = tf.placeholder( + tf_dtype, (batch_size, num_labels), name='assign_tlab') + self.assign_const = tf.placeholder( + tf_dtype, [batch_size], name='assign_const') + + self.global_step = tf.Variable(0, trainable=False) + self.global_step_t = tf.cast(self.global_step, tf_dtype) + + # Fast Iterative Shrinkage Thresholding + self.zt = tf.divide(self.global_step_t, + self.global_step_t + tf.cast(3, tf_dtype)) + cond1 = tf.cast(tf.greater(tf.subtract(self.slack, self.timg), + self.beta_t), tf_dtype) + cond2 = tf.cast(tf.less_equal(tf.abs(tf.subtract(self.slack, + self.timg)), + self.beta_t), tf_dtype) + cond3 = tf.cast(tf.less(tf.subtract(self.slack, self.timg), + tf.negative(self.beta_t)), tf_dtype) + + upper = tf.minimum(tf.subtract(self.slack, self.beta_t), + tf.cast(self.clip_max, tf_dtype)) + lower = tf.maximum(tf.add(self.slack, self.beta_t), + tf.cast(self.clip_min, tf_dtype)) + + self.assign_newimg = tf.multiply(cond1, upper) + self.assign_newimg += tf.multiply(cond2, self.timg) + self.assign_newimg += tf.multiply(cond3, lower) + + self.assign_slack = self.assign_newimg + self.assign_slack += tf.multiply(self.zt, + self.assign_newimg - self.newimg) + + # -------------------------------- + self.setter = tf.assign(self.newimg, self.assign_newimg) + self.setter_y = tf.assign(self.slack, self.assign_slack) + + # prediction BEFORE-SOFTMAX of the model + self.output = model.get_logits(self.newimg) + self.output_y = model.get_logits(self.slack) + + # distance to the input data + self.l2dist = reduce_sum(tf.square(self.newimg-self.timg), + list(range(1, len(shape)))) + self.l2dist_y = reduce_sum(tf.square(self.slack-self.timg), + list(range(1, len(shape)))) + self.l1dist = reduce_sum(tf.abs(self.newimg-self.timg), + list(range(1, len(shape)))) + self.l1dist_y = reduce_sum(tf.abs(self.slack-self.timg), + list(range(1, len(shape)))) + self.elasticdist = self.l2dist + tf.multiply(self.l1dist, + self.beta_t) + self.elasticdist_y = self.l2dist_y + tf.multiply(self.l1dist_y, + self.beta_t) + if self.decision_rule == 'EN': + self.crit = self.elasticdist + self.crit_p = 'Elastic' + else: + self.crit = self.l1dist + self.crit_p = 'L1' + + # compute the probability of the label class versus the maximum other + real = reduce_sum((self.tlab) * self.output, 1) + real_y = reduce_sum((self.tlab) * self.output_y, 1) + other = reduce_max((1 - self.tlab) * self.output - + (self.tlab * 10000), 1) + other_y = reduce_max((1 - self.tlab) * self.output_y - + (self.tlab * 10000), 1) + + if self.TARGETED: + # if targeted, optimize for making the other class most likely + loss1 = tf.maximum(ZERO(), other - real + self.CONFIDENCE) + loss1_y = tf.maximum(ZERO(), other_y - real_y + self.CONFIDENCE) + else: + # if untargeted, optimize for making this class least likely. + loss1 = tf.maximum(ZERO(), real - other + self.CONFIDENCE) + loss1_y = tf.maximum(ZERO(), real_y - other_y + self.CONFIDENCE) + + # sum up the losses + self.loss21 = reduce_sum(self.l1dist) + self.loss21_y = reduce_sum(self.l1dist_y) + self.loss2 = reduce_sum(self.l2dist) + self.loss2_y = reduce_sum(self.l2dist_y) + self.loss1 = reduce_sum(self.const * loss1) + self.loss1_y = reduce_sum(self.const * loss1_y) + self.loss_opt = self.loss1_y + self.loss2_y + self.loss = self.loss1+self.loss2+tf.multiply(self.beta_t, self.loss21) + + self.learning_rate = tf.train.polynomial_decay( + self.LEARNING_RATE, + self.global_step, + self.MAX_ITERATIONS, + 0, + power=0.5) + + # Setup the optimizer and keep track of variables we're creating + start_vars = set(x.name for x in tf.global_variables()) + optimizer = tf.train.GradientDescentOptimizer(self.learning_rate) + self.train = optimizer.minimize(self.loss_opt, + var_list=[self.slack], + global_step=self.global_step) + end_vars = tf.global_variables() + new_vars = [x for x in end_vars if x.name not in start_vars] + + # these are the variables to initialize when we run + self.setup = [] + self.setup.append(self.timg.assign(self.assign_timg)) + self.setup.append(self.tlab.assign(self.assign_tlab)) + self.setup.append(self.const.assign(self.assign_const)) + + var_list = [self.global_step]+[self.slack]+[self.newimg]+new_vars + self.init = tf.variables_initializer(var_list=var_list) + + def attack(self, imgs, targets): + """ + Perform the EAD attack on the given instance for the given targets. + + If self.targeted is true, then the targets represents the target labels + If self.targeted is false, then targets are the original class labels + """ + + batch_size = self.batch_size + r = [] + for i in range(0, len(imgs) // batch_size): + _logger.debug( + ("Running EAD attack on instance %s of %s", + i * batch_size, len(imgs))) + r.extend( + self.attack_batch( + imgs[i * batch_size:(i + 1) * batch_size], + targets[i * batch_size:(i + 1) * batch_size])) + if len(imgs) % batch_size != 0: + last_elements = len(imgs) - (len(imgs) % batch_size) + _logger.debug( + ("Running EAD attack on instance %s of %s", + last_elements, len(imgs))) + temp_imgs = np.zeros((batch_size, ) + imgs.shape[2:]) + temp_targets = np.zeros((batch_size, ) + targets.shape[2:]) + temp_imgs[:(len(imgs) % batch_size)] = imgs[last_elements:] + temp_targets[:(len(imgs) % batch_size)] = targets[last_elements:] + temp_data = self.attack_batch(temp_imgs, temp_targets) + r.extend(temp_data[:(len(imgs) % batch_size)], + targets[last_elements:]) + return np.array(r) + + def attack_batch(self, imgs, labs): + """ + Run the attack on a batch of instance and labels. + """ + + def compare(x, y): + if not isinstance(x, (float, int, np.int64)): + x = np.copy(x) + if self.TARGETED: + x[y] -= self.CONFIDENCE + else: + x[y] += self.CONFIDENCE + x = np.argmax(x) + if self.TARGETED: + return x == y + else: + return x != y + + batch_size = self.batch_size + + imgs = np.clip(imgs, self.clip_min, self.clip_max) + + # set the lower and upper bounds accordingly + lower_bound = np.zeros(batch_size) + CONST = np.ones(batch_size) * self.initial_const + upper_bound = np.ones(batch_size) * 1e10 + + # placeholders for the best en, score, and instance attack found so far + o_bestdst = [1e10] * batch_size + o_bestscore = [-1] * batch_size + o_bestattack = np.copy(imgs) + + for outer_step in range(self.BINARY_SEARCH_STEPS): + # completely reset the optimizer's internal state. + self.sess.run(self.init) + batch = imgs[:batch_size] + batchlab = labs[:batch_size] + + bestdst = [1e10] * batch_size + bestscore = [-1] * batch_size + _logger.debug(" Binary search step %s of %s", + outer_step, self.BINARY_SEARCH_STEPS) + + # The last iteration (if we run many steps) repeat the search once. + if self.repeat and outer_step == self.BINARY_SEARCH_STEPS - 1: + CONST = upper_bound + + # set the variables so that we don't have to send them over again + self.sess.run( + self.setup, { + self.assign_timg: batch, + self.assign_tlab: batchlab, + self.assign_const: CONST + }) + self.sess.run(self.setter, {self.assign_newimg: batch}) + self.sess.run(self.setter_y, {self.assign_slack: batch}) + prev = 1e6 + for iteration in range(self.MAX_ITERATIONS): + # perform the attack + self.sess.run([self.train]) + self.sess.run([self.setter, self.setter_y]) + l, l2s, l1s, crit, scores, nimg = self.sess.run([self.loss, + self.l2dist, + self.l1dist, + self.crit, + self.output, + self.newimg]) + if iteration % ((self.MAX_ITERATIONS // 10) or 1) == 0: + _logger.debug((" Iteration {} of {}: loss={:.3g} " + + "l2={:.3g} l1={:.3g} f={:.3g}").format( + iteration, self.MAX_ITERATIONS, l, + np.mean(l2s), np.mean(l1s), + np.mean(scores))) + + # check if we should abort search if we're getting nowhere. + if self.ABORT_EARLY and \ + iteration % ((self.MAX_ITERATIONS // 10) or 1) == 0: + if l > prev * .9999: + msg = " Failed to make progress; stop early" + _logger.debug(msg) + break + prev = l + + # adjust the best result found so far + for e, (dst, sc, ii) in enumerate(zip(crit, scores, nimg)): + lab = np.argmax(batchlab[e]) + if dst < bestdst[e] and compare(sc, lab): + bestdst[e] = dst + bestscore[e] = np.argmax(sc) + if dst < o_bestdst[e] and compare(sc, lab): + o_bestdst[e] = dst + o_bestscore[e] = np.argmax(sc) + o_bestattack[e] = ii + + # adjust the constant as needed + for e in range(batch_size): + if compare(bestscore[e], np.argmax(batchlab[e])) and \ + bestscore[e] != -1: + # success, divide const by two + upper_bound[e] = min(upper_bound[e], CONST[e]) + if upper_bound[e] < 1e9: + CONST[e] = (lower_bound[e] + upper_bound[e]) / 2 + else: + # failure, either multiply by 10 if no solution found yet + # or do binary search with the known upper bound + lower_bound[e] = max(lower_bound[e], CONST[e]) + if upper_bound[e] < 1e9: + CONST[e] = (lower_bound[e] + upper_bound[e]) / 2 + else: + CONST[e] *= 10 + _logger.debug(" Successfully generated adversarial examples " + + "on {} of {} instances.".format( + sum(upper_bound < 1e9), batch_size)) + o_bestdst = np.array(o_bestdst) + mean = np.mean(np.sqrt(o_bestdst[o_bestdst < 1e9])) + _logger.debug(self.crit_p + + " Mean successful distortion: {:.4g}".format(mean)) + + # return the best solution found + o_bestdst = np.array(o_bestdst) + return o_bestattack +
+ +
+ +
+
+ +
+
+ + + + \ No newline at end of file diff --git a/cleverhans_v3.1.0/docs/_modules/cleverhans/attacks/fast_feature_adversaries.html b/cleverhans_v3.1.0/docs/_modules/cleverhans/attacks/fast_feature_adversaries.html new file mode 100644 index 000000000..b88b72835 --- /dev/null +++ b/cleverhans_v3.1.0/docs/_modules/cleverhans/attacks/fast_feature_adversaries.html @@ -0,0 +1,256 @@ + + + + + + + + cleverhans.attacks.fast_feature_adversaries — CleverHans documentation + + + + + + + + + + + + + + + + + +
+
+
+ + +
+ +

Source code for cleverhans.attacks.fast_feature_adversaries

+"""
+The FastFeatureAdversaries attack
+"""
+# pylint: disable=missing-docstring
+import warnings
+
+import numpy as np
+import tensorflow as tf
+
+from cleverhans.attacks.attack import Attack
+from cleverhans.compat import reduce_sum
+from cleverhans.model import Model
+from cleverhans.utils_tf import clip_eta
+
+
+
[docs]class FastFeatureAdversaries(Attack): + """ + This is a fast implementation of "Feature Adversaries", an attack + against a target internal representation of a model. + "Feature adversaries" were originally introduced in (Sabour et al. 2016), + where the optimization was done using LBFGS. + Paper link: https://arxiv.org/abs/1511.05122 + + This implementation is similar to "Basic Iterative Method" + (Kurakin et al. 2016) but applied to the internal representations. + + :param model: cleverhans.model.Model + :param sess: optional tf.Session + :param dtypestr: dtype of the data + :param kwargs: passed through to super constructor + """ + + def __init__(self, model, sess=None, dtypestr='float32', **kwargs): + """ + Create a FastFeatureAdversaries instance. + """ + super(FastFeatureAdversaries, self).__init__(model, sess, dtypestr, + **kwargs) + self.feedable_kwargs = ('eps', 'eps_iter', 'clip_min', 'clip_max') + self.structural_kwargs = ['ord', 'nb_iter', 'layer'] + + assert isinstance(self.model, Model) + +
[docs] def parse_params(self, + layer=None, + eps=0.3, + eps_iter=0.05, + nb_iter=10, + ord=np.inf, + clip_min=None, + clip_max=None, + **kwargs): + """ + Take in a dictionary of parameters and applies attack-specific checks + before saving them as attributes. + + Attack-specific parameters: + + :param layer: (required str) name of the layer to target. + :param eps: (optional float) maximum distortion of adversarial example + compared to original input + :param eps_iter: (optional float) step size for each attack iteration + :param nb_iter: (optional int) Number of attack iterations. + :param ord: (optional) Order of the norm (mimics Numpy). + Possible values: np.inf, 1 or 2. + :param clip_min: (optional float) Minimum input component value + :param clip_max: (optional float) Maximum input component value + """ + + # Save attack-specific parameters + self.layer = layer + self.eps = eps + self.eps_iter = eps_iter + self.nb_iter = nb_iter + self.ord = ord + self.clip_min = clip_min + self.clip_max = clip_max + + # Check if order of the norm is acceptable given current implementation + if self.ord not in [np.inf, 1, 2]: + raise ValueError("Norm order must be either np.inf, 1, or 2.") + if len(kwargs.keys()) > 0: + warnings.warn("kwargs is unused and will be removed on or after " + "2019-04-26.") + + return True
+ +
[docs] def attack_single_step(self, x, eta, g_feat): + """ + TensorFlow implementation of the Fast Feature Gradient. This is a + single step attack similar to Fast Gradient Method that attacks an + internal representation. + + :param x: the input placeholder + :param eta: A tensor the same shape as x that holds the perturbation. + :param g_feat: model's internal tensor for guide + :return: a tensor for the adversarial example + """ + + adv_x = x + eta + a_feat = self.model.fprop(adv_x)[self.layer] + + # feat.shape = (batch, c) or (batch, w, h, c) + axis = list(range(1, len(a_feat.shape))) + + # Compute loss + # This is a targeted attack, hence the negative sign + loss = -reduce_sum(tf.square(a_feat - g_feat), axis) + + # Define gradient of loss wrt input + grad, = tf.gradients(loss, adv_x) + + # Multiply by constant epsilon + scaled_signed_grad = self.eps_iter * tf.sign(grad) + + # Add perturbation to original example to obtain adversarial example + adv_x = adv_x + scaled_signed_grad + + # If clipping is needed, + # reset all values outside of [clip_min, clip_max] + if (self.clip_min is not None) and (self.clip_max is not None): + adv_x = tf.clip_by_value(adv_x, self.clip_min, self.clip_max) + + adv_x = tf.stop_gradient(adv_x) + + eta = adv_x - x + eta = clip_eta(eta, self.ord, self.eps) + + return eta
+ +
[docs] def generate(self, x, g, **kwargs): + """ + Generate symbolic graph for adversarial examples and return. + + :param x: The model's symbolic inputs. + :param g: The target value of the symbolic representation + :param kwargs: See `parse_params` + """ + + # Parse and save attack-specific parameters + assert self.parse_params(**kwargs) + + g_feat = self.model.fprop(g)[self.layer] + + # Initialize loop variables + eta = tf.random_uniform( + tf.shape(x), -self.eps, self.eps, dtype=self.tf_dtype) + eta = clip_eta(eta, self.ord, self.eps) + + def cond(i, _): + return tf.less(i, self.nb_iter) + + def body(i, e): + new_eta = self.attack_single_step(x, e, g_feat) + return i + 1, new_eta + + _, eta = tf.while_loop(cond, body, (tf.zeros([]), eta), back_prop=True, + maximum_iterations=self.nb_iter) + + # Define adversarial example (and clip if necessary) + adv_x = x + eta + if self.clip_min is not None and self.clip_max is not None: + adv_x = tf.clip_by_value(adv_x, self.clip_min, self.clip_max) + + return adv_x
+
+ +
+ +
+
+ +
+
+ + + + \ No newline at end of file diff --git a/cleverhans_v3.1.0/docs/_modules/cleverhans/attacks/fast_gradient_method.html b/cleverhans_v3.1.0/docs/_modules/cleverhans/attacks/fast_gradient_method.html new file mode 100644 index 000000000..21e4de943 --- /dev/null +++ b/cleverhans_v3.1.0/docs/_modules/cleverhans/attacks/fast_gradient_method.html @@ -0,0 +1,356 @@ + + + + + + + + cleverhans.attacks.fast_gradient_method — CleverHans documentation + + + + + + + + + + + + + + + + + +
+
+
+ + +
+ +

Source code for cleverhans.attacks.fast_gradient_method

+"""
+The FastGradientMethod attack.
+"""
+
+import warnings
+
+import numpy as np
+import tensorflow as tf
+
+from cleverhans.attacks.attack import Attack
+from cleverhans.compat import reduce_max, reduce_sum, softmax_cross_entropy_with_logits
+from cleverhans import utils_tf
+
+
+
[docs]class FastGradientMethod(Attack): + """ + This attack was originally implemented by Goodfellow et al. (2014) with the + infinity norm (and is known as the "Fast Gradient Sign Method"). This + implementation extends the attack to other norms, and is therefore called + the Fast Gradient Method. + Paper link: https://arxiv.org/abs/1412.6572 + + :param model: cleverhans.model.Model + :param sess: optional tf.Session + :param dtypestr: dtype of the data + :param kwargs: passed through to super constructor + """ + + def __init__(self, model, sess=None, dtypestr='float32', **kwargs): + """ + Create a FastGradientMethod instance. + Note: the model parameter should be an instance of the + cleverhans.model.Model abstraction provided by CleverHans. + """ + + super(FastGradientMethod, self).__init__(model, sess, dtypestr, **kwargs) + self.feedable_kwargs = ('eps', 'y', 'y_target', 'clip_min', 'clip_max') + self.structural_kwargs = ['ord', 'sanity_checks', 'clip_grad', 'loss_fn'] + +
[docs] def generate(self, x, **kwargs): + """ + Returns the graph for Fast Gradient Method adversarial examples. + + :param x: The model's symbolic inputs. + :param kwargs: See `parse_params` + """ + # Parse and save attack-specific parameters + assert self.parse_params(**kwargs) + + labels, _nb_classes = self.get_or_guess_labels(x, kwargs) + + return fgm( + x, + self.model.get_logits(x), + y=labels, + eps=self.eps, + ord=self.ord, + loss_fn=self.loss_fn, + clip_min=self.clip_min, + clip_max=self.clip_max, + clip_grad=self.clip_grad, + targeted=(self.y_target is not None), + sanity_checks=self.sanity_checks)
+ +
[docs] def parse_params(self, + eps=0.3, + ord=np.inf, + loss_fn=softmax_cross_entropy_with_logits, + y=None, + y_target=None, + clip_min=None, + clip_max=None, + clip_grad=False, + sanity_checks=True, + **kwargs): + """ + Take in a dictionary of parameters and applies attack-specific checks + before saving them as attributes. + + Attack-specific parameters: + + :param eps: (optional float) attack step size (input variation) + :param ord: (optional) Order of the norm (mimics NumPy). + Possible values: np.inf, 1 or 2. + :param loss_fn: Loss function that takes (labels, logits) as arguments and returns loss + :param y: (optional) A tensor with the true labels. Only provide + this parameter if you'd like to use true labels when crafting + adversarial samples. Otherwise, model predictions are used as + labels to avoid the "label leaking" effect (explained in this + paper: https://arxiv.org/abs/1611.01236). Default is None. + Labels should be one-hot-encoded. + :param y_target: (optional) A tensor with the labels to target. Leave + y_target=None if y is also set. Labels should be + one-hot-encoded. + :param clip_min: (optional float) Minimum input component value + :param clip_max: (optional float) Maximum input component value + :param clip_grad: (optional bool) Ignore gradient components + at positions where the input is already at the boundary + of the domain, and the update step will get clipped out. + :param sanity_checks: bool, if True, include asserts + (Turn them off to use less runtime / memory or for unit tests that + intentionally pass strange input) + """ + # Save attack-specific parameters + + self.eps = eps + self.ord = ord + self.loss_fn = loss_fn + self.y = y + self.y_target = y_target + self.clip_min = clip_min + self.clip_max = clip_max + self.clip_grad = clip_grad + self.sanity_checks = sanity_checks + + if self.y is not None and self.y_target is not None: + raise ValueError("Must not set both y and y_target") + # Check if order of the norm is acceptable given current implementation + if self.ord not in [np.inf, int(1), int(2)]: + raise ValueError("Norm order must be either np.inf, 1, or 2.") + + if self.clip_grad and (self.clip_min is None or self.clip_max is None): + raise ValueError("Must set clip_min and clip_max if clip_grad is set") + + if len(kwargs.keys()) > 0: + warnings.warn("kwargs is unused and will be removed on or after " + "2019-04-26.") + + return True
+ + +
[docs]def fgm(x, + logits, + y=None, + eps=0.3, + ord=np.inf, + loss_fn=softmax_cross_entropy_with_logits, + clip_min=None, + clip_max=None, + clip_grad=False, + targeted=False, + sanity_checks=True): + """ + TensorFlow implementation of the Fast Gradient Method. + :param x: the input placeholder + :param logits: output of model.get_logits + :param y: (optional) A placeholder for the true labels. If targeted + is true, then provide the target label. Otherwise, only provide + this parameter if you'd like to use true labels when crafting + adversarial samples. Otherwise, model predictions are used as + labels to avoid the "label leaking" effect (explained in this + paper: https://arxiv.org/abs/1611.01236). Default is None. + Labels should be one-hot-encoded. + :param eps: the epsilon (input variation parameter) + :param ord: (optional) Order of the norm (mimics NumPy). + Possible values: np.inf, 1 or 2. + :param loss_fn: Loss function that takes (labels, logits) as arguments and returns loss + :param clip_min: Minimum float value for adversarial example components + :param clip_max: Maximum float value for adversarial example components + :param clip_grad: (optional bool) Ignore gradient components + at positions where the input is already at the boundary + of the domain, and the update step will get clipped out. + :param targeted: Is the attack targeted or untargeted? Untargeted, the + default, will try to make the label incorrect. Targeted + will instead try to move in the direction of being more + like y. + :return: a tensor for the adversarial example + """ + + asserts = [] + + # If a data range was specified, check that the input was in that range + if clip_min is not None: + asserts.append(utils_tf.assert_greater_equal( + x, tf.cast(clip_min, x.dtype))) + + if clip_max is not None: + asserts.append(utils_tf.assert_less_equal(x, tf.cast(clip_max, x.dtype))) + + # Make sure the caller has not passed probs by accident + assert logits.op.type != 'Softmax' + + if y is None: + # Using model predictions as ground truth to avoid label leaking + preds_max = reduce_max(logits, 1, keepdims=True) + y = tf.to_float(tf.equal(logits, preds_max)) + y = tf.stop_gradient(y) + y = y / reduce_sum(y, 1, keepdims=True) + + # Compute loss + loss = loss_fn(labels=y, logits=logits) + if targeted: + loss = -loss + + # Define gradient of loss wrt input + grad, = tf.gradients(loss, x) + + if clip_grad: + grad = utils_tf.zero_out_clipped_grads(grad, x, clip_min, clip_max) + + optimal_perturbation = optimize_linear(grad, eps, ord) + + # Add perturbation to original example to obtain adversarial example + adv_x = x + optimal_perturbation + + # If clipping is needed, reset all values outside of [clip_min, clip_max] + if (clip_min is not None) or (clip_max is not None): + # We don't currently support one-sided clipping + assert clip_min is not None and clip_max is not None + adv_x = utils_tf.clip_by_value(adv_x, clip_min, clip_max) + + if sanity_checks: + with tf.control_dependencies(asserts): + adv_x = tf.identity(adv_x) + + return adv_x
+ + +
[docs]def optimize_linear(grad, eps, ord=np.inf): + """ + Solves for the optimal input to a linear function under a norm constraint. + + Optimal_perturbation = argmax_{eta, ||eta||_{ord} < eps} dot(eta, grad) + + :param grad: tf tensor containing a batch of gradients + :param eps: float scalar specifying size of constraint region + :param ord: int specifying order of norm + :returns: + tf tensor containing optimal perturbation + """ + + # In Python 2, the `list` call in the following line is redundant / harmless. + # In Python 3, the `list` call is needed to convert the iterator returned by `range` into a list. + red_ind = list(range(1, len(grad.get_shape()))) + avoid_zero_div = 1e-12 + if ord == np.inf: + # Take sign of gradient + optimal_perturbation = tf.sign(grad) + # The following line should not change the numerical results. + # It applies only because `optimal_perturbation` is the output of + # a `sign` op, which has zero derivative anyway. + # It should not be applied for the other norms, where the + # perturbation has a non-zero derivative. + optimal_perturbation = tf.stop_gradient(optimal_perturbation) + elif ord == 1: + abs_grad = tf.abs(grad) + sign = tf.sign(grad) + max_abs_grad = tf.reduce_max(abs_grad, red_ind, keepdims=True) + tied_for_max = tf.to_float(tf.equal(abs_grad, max_abs_grad)) + num_ties = tf.reduce_sum(tied_for_max, red_ind, keepdims=True) + optimal_perturbation = sign * tied_for_max / num_ties + elif ord == 2: + square = tf.maximum(avoid_zero_div, + reduce_sum(tf.square(grad), + reduction_indices=red_ind, + keepdims=True)) + optimal_perturbation = grad / tf.sqrt(square) + else: + raise NotImplementedError("Only L-inf, L1 and L2 norms are " + "currently implemented.") + + # Scale perturbation to be the solution for the norm=eps rather than + # norm=1 problem + scaled_perturbation = utils_tf.mul(eps, optimal_perturbation) + return scaled_perturbation
+
+ +
+ +
+
+ +
+
+ + + + \ No newline at end of file diff --git a/cleverhans_v3.1.0/docs/_modules/cleverhans/attacks/hop_skip_jump_attack.html b/cleverhans_v3.1.0/docs/_modules/cleverhans/attacks/hop_skip_jump_attack.html new file mode 100644 index 000000000..51a1d243b --- /dev/null +++ b/cleverhans_v3.1.0/docs/_modules/cleverhans/attacks/hop_skip_jump_attack.html @@ -0,0 +1,634 @@ + + + + + + + + cleverhans.attacks.hop_skip_jump_attack — CleverHans documentation + + + + + + + + + + + + + + + + + +
+
+
+ + +
+ +

Source code for cleverhans.attacks.hop_skip_jump_attack

+""" Boundary Attack++
+"""
+import logging
+import numpy as np
+import tensorflow as tf
+from warnings import warn
+from cleverhans.attacks import Attack
+from cleverhans.model import CallableModelWrapper, Model, wrapper_warning_logits
+from cleverhans import utils, utils_tf
+
+np_dtype = np.dtype('float32')
+tf_dtype = tf.as_dtype('float32')
+
+_logger = utils.create_logger("cleverhans.attacks.hop_skip_jump_attack")
+_logger.setLevel(logging.INFO)
+
+
+
[docs]class HopSkipJumpAttack(Attack): + """ + HopSkipJumpAttack was originally proposed by Chen, Jordan and Wainwright. + It is a decision-based attack that requires access to output + labels of a model alone. + Paper link: https://arxiv.org/abs/1904.02144 + At a high level, this attack is an iterative attack composed of three + steps: Binary search to approach the boundary; gradient estimation; + stepsize search. HopSkipJumpAttack requires fewer model queries than + Boundary Attack which was based on rejective sampling. + :param model: cleverhans.model.Model + :param sess: tf.Session + :param dtypestr: dtype of the data + :param kwargs: passed through to super constructor. + see parse_params for details. + """ + + def __init__(self, model, sess, dtypestr='float32', **kwargs): + """ + Note: the model parameter should be an instance of the + cleverhans.model.Model abstraction provided by CleverHans. + """ + if not isinstance(model, Model): + wrapper_warning_logits() + model = CallableModelWrapper(model, 'logits') + + super(HopSkipJumpAttack, self).__init__(model, sess, + dtypestr, **kwargs) + + self.feedable_kwargs = ('y_target', 'image_target') + + self.structural_kwargs = [ + 'stepsize_search', + 'clip_min', + 'clip_max', + 'constraint', + 'num_iterations', + 'initial_num_evals', + 'max_num_evals', + 'batch_size', + 'verbose', + 'gamma', + ] + +
[docs] def generate(self, x, **kwargs): + """ + Return a tensor that constructs adversarial examples for the given + input. Generate uses tf.py_func in order to operate over tensors. + :param x: A tensor with the inputs. + :param kwargs: See `parse_params` + """ + self.parse_params(**kwargs) + shape = [int(i) for i in x.get_shape().as_list()[1:]] + + assert self.sess is not None, \ + 'Cannot use `generate` when no `sess` was provided' + _check_first_dimension(x, 'input') + if self.y_target is not None: + _check_first_dimension(self.y_target, 'y_target') + assert self.image_target is not None, \ + 'Require a target image for targeted attack.' + _check_first_dimension(self.image_target, 'image_target') + + # Set shape and d. + self.shape = shape + self.d = int(np.prod(shape)) + + # Set binary search threshold. + if self.constraint == 'l2': + self.theta = self.gamma / (np.sqrt(self.d) * self.d) + else: + self.theta = self.gamma / (self.d * self.d) + + # Construct input placeholder and output for decision function. + self.input_ph = tf.placeholder( + tf_dtype, [None] + list(self.shape), name='input_image') + self.logits = self.model.get_logits(self.input_ph) + + def hsja_wrap(x, target_label, target_image): + """ Wrapper to use tensors as input and output. """ + return np.array(self._hsja(x, target_label, target_image), + dtype=self.np_dtype) + + if self.y_target is not None: + # targeted attack that requires target label and image. + wrap = tf.py_func(hsja_wrap, + [x[0], self.y_target[0], self.image_target[0]], + self.tf_dtype) + else: + if self.image_target is not None: + # untargeted attack with an initialized image. + wrap = tf.py_func(lambda x, target_image: hsja_wrap(x, + None, target_image), + [x[0], self.image_target[0]], + self.tf_dtype) + else: + # untargeted attack without an initialized image. + wrap = tf.py_func(lambda x: hsja_wrap(x, None, None), + [x[0]], + self.tf_dtype) + + wrap.set_shape(x.get_shape()) + + return wrap
+ +
[docs] def generate_np(self, x, **kwargs): + """ + Generate adversarial images in a for loop. + :param y: An array of shape (n, nb_classes) for true labels. + :param y_target: An array of shape (n, nb_classes) for target labels. + Required for targeted attack. + :param image_target: An array of shape (n, **image shape) for initial + target images. Required for targeted attack. + + See parse_params for other kwargs. + + """ + + x_adv = [] + + if 'image_target' in kwargs and kwargs['image_target'] is not None: + image_target = np.copy(kwargs['image_target']) + else: + image_target = None + if 'y_target' in kwargs and kwargs['y_target'] is not None: + y_target = np.copy(kwargs['y_target']) + else: + y_target = None + + for i, x_single in enumerate(x): + img = np.expand_dims(x_single, axis=0) + if image_target is not None: + single_img_target = np.expand_dims(image_target[i], axis=0) + kwargs['image_target'] = single_img_target + if y_target is not None: + single_y_target = np.expand_dims(y_target[i], axis=0) + kwargs['y_target'] = single_y_target + + adv_img = super(HopSkipJumpAttack, + self).generate_np(img, **kwargs) + x_adv.append(adv_img) + + return np.concatenate(x_adv, axis=0)
+ +
[docs] def parse_params(self, + y_target=None, + image_target=None, + initial_num_evals=100, + max_num_evals=10000, + stepsize_search='geometric_progression', + num_iterations=64, + gamma=1.0, + constraint='l2', + batch_size=128, + verbose=True, + clip_min=0, + clip_max=1): + """ + :param y: A tensor of shape (1, nb_classes) for true labels. + :param y_target: A tensor of shape (1, nb_classes) for target labels. + Required for targeted attack. + :param image_target: A tensor of shape (1, **image shape) for initial + target images. Required for targeted attack. + :param initial_num_evals: initial number of evaluations for + gradient estimation. + :param max_num_evals: maximum number of evaluations for gradient estimation. + :param stepsize_search: How to search for stepsize; choices are + 'geometric_progression', 'grid_search'. + 'geometric progression' initializes the stepsize + by ||x_t - x||_p / sqrt(iteration), and keep + decreasing by half until reaching the target + side of the boundary. 'grid_search' chooses the + optimal epsilon over a grid, in the scale of + ||x_t - x||_p. + :param num_iterations: The number of iterations. + :param gamma: The binary search threshold theta is gamma / d^{3/2} for + l2 attack and gamma / d^2 for linf attack. + :param constraint: The distance to optimize; choices are 'l2', 'linf'. + :param batch_size: batch_size for model prediction. + :param verbose: (boolean) Whether distance at each step is printed. + :param clip_min: (optional float) Minimum input component value + :param clip_max: (optional float) Maximum input component value + """ + + # ignore the y and y_target argument + self.y_target = y_target + self.image_target = image_target + self.initial_num_evals = initial_num_evals + self.max_num_evals = max_num_evals + self.stepsize_search = stepsize_search + self.num_iterations = num_iterations + self.gamma = gamma + self.constraint = constraint + self.batch_size = batch_size + self.clip_min = clip_min + self.clip_max = clip_max + self.verbose = verbose
+ + def _hsja(self, sample, target_label, target_image): + """ + Main algorithm for HopSkipJumpAttack. + + Return a tensor that constructs adversarial examples for the given + input. Generate uses tf.py_func in order to operate over tensors. + + :param sample: input image. Without the batchsize dimension. + :param target_label: integer for targeted attack, + None for nontargeted attack. Without the batchsize dimension. + :param target_image: an array with the same size as sample, or None. + Without the batchsize dimension. + + + Output: + perturbed image. + + """ + + # Original label required for untargeted attack. + if target_label is None: + original_label = np.argmax( + self.sess.run(self.logits, feed_dict={self.input_ph: sample[None]}) + ) + else: + target_label = np.argmax(target_label) + + def decision_function(images): + """ + Decision function output 1 on the desired side of the boundary, + 0 otherwise. + """ + images = clip_image(images, self.clip_min, self.clip_max) + prob = [] + for i in range(0, len(images), self.batch_size): + batch = images[i:i+self.batch_size] + prob_i = self.sess.run(self.logits, feed_dict={self.input_ph: batch}) + prob.append(prob_i) + prob = np.concatenate(prob, axis=0) + if target_label is None: + return np.argmax(prob, axis=1) != original_label + else: + return np.argmax(prob, axis=1) == target_label + + # Initialize. + if target_image is None: + perturbed = initialize(decision_function, sample, self.shape, + self.clip_min, self.clip_max) + else: + perturbed = target_image + + # Project the initialization to the boundary. + perturbed, dist_post_update = binary_search_batch(sample, + np.expand_dims(perturbed, 0), + decision_function, + self.shape, + self.constraint, + self.theta) + + dist = compute_distance(perturbed, sample, self.constraint) + + for j in np.arange(self.num_iterations): + current_iteration = j + 1 + + # Choose delta. + delta = select_delta(dist_post_update, current_iteration, + self.clip_max, self.clip_min, self.d, + self.theta, self.constraint) + + # Choose number of evaluations. + num_evals = int(min([self.initial_num_evals * np.sqrt(j+1), + self.max_num_evals])) + + # approximate gradient. + gradf = approximate_gradient(decision_function, perturbed, num_evals, + delta, self.constraint, self.shape, + self.clip_min, self.clip_max) + if self.constraint == 'linf': + update = np.sign(gradf) + else: + update = gradf + + # search step size. + if self.stepsize_search == 'geometric_progression': + # find step size. + epsilon = geometric_progression_for_stepsize(perturbed, + update, dist, decision_function, current_iteration) + + # Update the sample. + perturbed = clip_image(perturbed + epsilon * update, + self.clip_min, self.clip_max) + + # Binary search to return to the boundary. + perturbed, dist_post_update = binary_search_batch(sample, + perturbed[None], + decision_function, + self.shape, + self.constraint, + self.theta) + + elif self.stepsize_search == 'grid_search': + # Grid search for stepsize. + epsilons = np.logspace(-4, 0, num=20, endpoint=True) * dist + epsilons_shape = [20] + len(self.shape) * [1] + perturbeds = perturbed + epsilons.reshape(epsilons_shape) * update + perturbeds = clip_image(perturbeds, self.clip_min, self.clip_max) + idx_perturbed = decision_function(perturbeds) + + if np.sum(idx_perturbed) > 0: + # Select the perturbation that yields the minimum distance # after binary search. + perturbed, dist_post_update = binary_search_batch(sample, + perturbeds[idx_perturbed], + decision_function, + self.shape, + self.constraint, + self.theta) + + # compute new distance. + dist = compute_distance(perturbed, sample, self.constraint) + if self.verbose: + print('iteration: {:d}, {:s} distance {:.4E}'.format( + j+1, self.constraint, dist)) + + perturbed = np.expand_dims(perturbed, 0) + return perturbed
+ + +
[docs]def BoundaryAttackPlusPlus(model, sess, dtypestr='float32', **kwargs): + """ + A previous name used for HopSkipJumpAttack. + """ + warn("BoundaryAttackPlusPlus will be removed after 2019-12-08; use HopSkipJumpAttack.") + return HopSkipJumpAttack(model, sess, dtypestr, **kwargs)
+ +def _check_first_dimension(x, tensor_name): + message = "Tensor {} should have batch_size of 1.".format(tensor_name) + if x.get_shape().as_list()[0] is None: + check_batch = utils_tf.assert_equal(tf.shape(x)[0], 1, message=message) + with tf.control_dependencies([check_batch]): + x = tf.identity(x) + elif x.get_shape().as_list()[0] != 1: + raise ValueError(message) + + +def clip_image(image, clip_min, clip_max): + """ Clip an image, or an image batch, with upper and lower threshold. """ + return np.minimum(np.maximum(clip_min, image), clip_max) + + +def compute_distance(x_ori, x_pert, constraint='l2'): + """ Compute the distance between two images. """ + if constraint == 'l2': + dist = np.linalg.norm(x_ori - x_pert) + elif constraint == 'linf': + dist = np.max(abs(x_ori - x_pert)) + return dist + +def approximate_gradient(decision_function, sample, num_evals, + delta, constraint, shape, clip_min, clip_max): + """ Gradient direction estimation """ + # Generate random vectors. + noise_shape = [num_evals] + list(shape) + if constraint == 'l2': + rv = np.random.randn(*noise_shape) + elif constraint == 'linf': + rv = np.random.uniform(low=-1, high=1, size=noise_shape) + + axis = tuple(range(1, 1 + len(shape))) + rv = rv / np.sqrt(np.sum(rv ** 2, axis=axis, keepdims=True)) + perturbed = sample + delta * rv + perturbed = clip_image(perturbed, clip_min, clip_max) + rv = (perturbed - sample) / delta + + # query the model. + decisions = decision_function(perturbed) + decision_shape = [len(decisions)] + [1] * len(shape) + fval = 2 * decisions.astype(np_dtype).reshape(decision_shape) - 1.0 + + # Baseline subtraction (when fval differs) + if np.mean(fval) == 1.0: # label changes. + gradf = np.mean(rv, axis=0) + elif np.mean(fval) == -1.0: # label not change. + gradf = - np.mean(rv, axis=0) + else: + fval = fval - np.mean(fval) + gradf = np.mean(fval * rv, axis=0) + + # Get the gradient direction. + gradf = gradf / np.linalg.norm(gradf) + + return gradf + + +def project(original_image, perturbed_images, alphas, shape, constraint): + """ Projection onto given l2 / linf balls in a batch. """ + alphas_shape = [len(alphas)] + [1] * len(shape) + alphas = alphas.reshape(alphas_shape) + if constraint == 'l2': + projected = (1-alphas) * original_image + alphas * perturbed_images + elif constraint == 'linf': + projected = clip_image( + perturbed_images, + original_image - alphas, + original_image + alphas + ) + return projected + + +def binary_search_batch(original_image, perturbed_images, decision_function, + shape, constraint, theta): + """ Binary search to approach the boundary. """ + + # Compute distance between each of perturbed image and original image. + dists_post_update = np.array([ + compute_distance( + original_image, + perturbed_image, + constraint + ) + for perturbed_image in perturbed_images]) + + # Choose upper thresholds in binary searchs based on constraint. + if constraint == 'linf': + highs = dists_post_update + # Stopping criteria. + thresholds = np.minimum(dists_post_update * theta, theta) + else: + highs = np.ones(len(perturbed_images)) + thresholds = theta + + lows = np.zeros(len(perturbed_images)) + + while np.max((highs - lows) / thresholds) > 1: + # projection to mids. + mids = (highs + lows) / 2.0 + mid_images = project(original_image, perturbed_images, + mids, shape, constraint) + + # Update highs and lows based on model decisions. + decisions = decision_function(mid_images) + lows = np.where(decisions == 0, mids, lows) + highs = np.where(decisions == 1, mids, highs) + + out_images = project(original_image, perturbed_images, + highs, shape, constraint) + + # Compute distance of the output image to select the best choice. + # (only used when stepsize_search is grid_search.) + dists = np.array([ + compute_distance( + original_image, + out_image, + constraint + ) + for out_image in out_images]) + idx = np.argmin(dists) + + dist = dists_post_update[idx] + out_image = out_images[idx] + return out_image, dist + + +def initialize(decision_function, sample, shape, clip_min, clip_max): + """ + Efficient Implementation of BlendedUniformNoiseAttack in Foolbox. + """ + success = 0 + num_evals = 0 + + # Find a misclassified random noise. + while True: + random_noise = np.random.uniform(clip_min, clip_max, size=shape) + success = decision_function(random_noise[None])[0] + if success: + break + num_evals += 1 + message = "Initialization failed! Try to use a misclassified image as `target_image`" + assert num_evals < 1e4, message + + # Binary search to minimize l2 distance to original image. + low = 0.0 + high = 1.0 + while high - low > 0.001: + mid = (high + low) / 2.0 + blended = (1 - mid) * sample + mid * random_noise + success = decision_function(blended[None])[0] + if success: + high = mid + else: + low = mid + + initialization = (1 - high) * sample + high * random_noise + return initialization + + +def geometric_progression_for_stepsize(x, update, dist, decision_function, + current_iteration): + """ Geometric progression to search for stepsize. + Keep decreasing stepsize by half until reaching + the desired side of the boundary. + """ + epsilon = dist / np.sqrt(current_iteration) + while True: + updated = x + epsilon * update + success = decision_function(updated[None])[0] + if success: + break + else: + epsilon = epsilon / 2.0 + + return epsilon + + +def select_delta(dist_post_update, current_iteration, + clip_max, clip_min, d, theta, constraint): + """ + Choose the delta at the scale of distance + between x and perturbed sample. + """ + if current_iteration == 1: + delta = 0.1 * (clip_max - clip_min) + else: + if constraint == 'l2': + delta = np.sqrt(d) * theta * dist_post_update + elif constraint == 'linf': + delta = d * theta * dist_post_update + + return delta +
+ +
+ +
+
+ +
+
+ + + + \ No newline at end of file diff --git a/cleverhans_v3.1.0/docs/_modules/cleverhans/attacks/lbfgs.html b/cleverhans_v3.1.0/docs/_modules/cleverhans/attacks/lbfgs.html new file mode 100644 index 000000000..7f4fb61f2 --- /dev/null +++ b/cleverhans_v3.1.0/docs/_modules/cleverhans/attacks/lbfgs.html @@ -0,0 +1,372 @@ + + + + + + + + cleverhans.attacks.lbfgs — CleverHans documentation + + + + + + + + + + + + + + + + + +
+
+
+ + +
+ +

Source code for cleverhans.attacks.lbfgs

+"""The LBFGS attack
+"""
+
+import numpy as np
+import tensorflow as tf
+
+from cleverhans.attacks.attack import Attack
+from cleverhans.compat import reduce_sum, softmax_cross_entropy_with_logits
+from cleverhans.model import CallableModelWrapper, Model, wrapper_warning
+from cleverhans import utils
+from cleverhans import utils_tf
+
+_logger = utils.create_logger("cleverhans.attacks.lbfgs")
+tf_dtype = tf.as_dtype('float32')
+
+
+
[docs]class LBFGS(Attack): + """ + LBFGS is the first adversarial attack for convolutional neural networks, + and is a target & iterative attack. + Paper link: "https://arxiv.org/pdf/1312.6199.pdf" + :param model: cleverhans.model.Model + :param sess: tf.Session + :param dtypestr: dtype of the data + :param kwargs: passed through to super constructor + """ + + def __init__(self, model, sess, dtypestr='float32', **kwargs): + if not isinstance(model, Model): + wrapper_warning() + model = CallableModelWrapper(model, 'probs') + + super(LBFGS, self).__init__(model, sess, dtypestr, **kwargs) + + self.feedable_kwargs = ('y_target',) + self.structural_kwargs = [ + 'batch_size', 'binary_search_steps', 'max_iterations', + 'initial_const', 'clip_min', 'clip_max' + ] + +
[docs] def generate(self, x, **kwargs): + """ + Return a tensor that constructs adversarial examples for the given + input. Generate uses tf.py_func in order to operate over tensors. + :param x: (required) A tensor with the inputs. + :param kwargs: See `parse_params` + """ + assert self.sess is not None, \ + 'Cannot use `generate` when no `sess` was provided' + self.parse_params(**kwargs) + + if self.y_target is None: + self.y_target, nb_classes = self.get_or_guess_labels(x, kwargs) + self.targeted_attack = False + else: + _, nb_classes = self.get_or_guess_labels(x, kwargs) + self.targeted_attack = True + + attack = LBFGS_impl( + self.sess, x, self.model.get_logits(x), + self.y_target, self.targeted_attack, + self.binary_search_steps, self.max_iterations, self.initial_const, + self.clip_min, self.clip_max, nb_classes, self.batch_size) + + def lbfgs_wrap(x_val, y_val): + """ + Wrapper creating TensorFlow interface for use with py_func + """ + return np.array(attack.attack(x_val, y_val), dtype=self.np_dtype) + + wrap = tf.py_func(lbfgs_wrap, [x, self.y_target], self.tf_dtype) + wrap.set_shape(x.get_shape()) + + return wrap
+ +
[docs] def parse_params(self, + y_target=None, + batch_size=1, + binary_search_steps=5, + max_iterations=1000, + initial_const=1e-2, + clip_min=0, + clip_max=1): + """ + :param y_target: (optional) A tensor with the one-hot target labels. + :param batch_size: The number of inputs to include in a batch and + process simultaneously. + :param binary_search_steps: The number of times we perform binary + search to find the optimal tradeoff- + constant between norm of the purturbation + and cross-entropy loss of classification. + :param max_iterations: The maximum number of iterations. + :param initial_const: The initial tradeoff-constant to use to tune the + relative importance of size of the perturbation + and cross-entropy loss of the classification. + :param clip_min: (optional float) Minimum input component value + :param clip_max: (optional float) Maximum input component value + """ + self.y_target = y_target + self.batch_size = batch_size + self.binary_search_steps = binary_search_steps + self.max_iterations = max_iterations + self.initial_const = initial_const + self.clip_min = clip_min + self.clip_max = clip_max
+ + +class LBFGS_impl(object): + """ + Return a tensor that constructs adversarial examples for the given + input. Generate uses tf.py_func in order to operate over tensors. + :param sess: a TF session. + :param x: A tensor with the inputs. + :param logits: A tensor with model's output logits. + :param targeted_label: A tensor with the target labels. + :param binary_search_steps: The number of times we perform binary + search to find the optimal tradeoff- + constant between norm of the purturbation + and cross-entropy loss of classification. + :param max_iterations: The maximum number of iterations. + :param initial_const: The initial tradeoff-constant to use to tune the + relative importance of size of the purturbation + and cross-entropy loss of the classification. + :param clip_min: Minimum input component value + :param clip_max: Maximum input component value + :param num_labels: The number of classes in the model's output. + :param batch_size: Number of attacks to run simultaneously. + """ + + def __init__(self, sess, x, logits, targeted_label, targeted_attack, + binary_search_steps, max_iterations, initial_const, clip_min, + clip_max, nb_classes, batch_size): + self.sess = sess + self.x = x + self.logits = logits + assert logits.op.type != 'Softmax' + self.targeted_label = targeted_label + self.targeted_attack = targeted_attack + self.binary_search_steps = binary_search_steps + self.max_iterations = max_iterations + self.initial_const = initial_const + self.clip_min = clip_min + self.clip_max = clip_max + self.batch_size = batch_size + + self.repeat = self.binary_search_steps >= 10 + self.shape = tuple([self.batch_size] + + list(self.x.get_shape().as_list()[1:])) + self.ori_img = tf.Variable( + np.zeros(self.shape), dtype=tf_dtype, name='ori_img') + self.const = tf.Variable( + np.zeros(self.batch_size), dtype=tf_dtype, name='const') + + self.score = softmax_cross_entropy_with_logits( + labels=self.targeted_label, logits=self.logits) + self.l2dist = reduce_sum(tf.square(self.x - self.ori_img)) + # small self.const will result small adversarial perturbation + # targeted attack aims at minimize loss against target label + # untargeted attack aims at maximize loss against True label + if self.targeted_attack: + self.loss = reduce_sum(self.score * self.const) + self.l2dist + else: + self.loss = -reduce_sum(self.score * self.const) + self.l2dist + self.grad, = tf.gradients(self.loss, self.x) + + def attack(self, x_val, targets): + """ + Perform the attack on the given instance for the given targets. + """ + + def lbfgs_objective(adv_x, self, targets, oimgs, CONST): + """ returns the function value and the gradient for fmin_l_bfgs_b """ + loss = self.sess.run( + self.loss, + feed_dict={ + self.x: adv_x.reshape(oimgs.shape), + self.targeted_label: targets, + self.ori_img: oimgs, + self.const: CONST + }) + grad = self.sess.run( + self.grad, + feed_dict={ + self.x: adv_x.reshape(oimgs.shape), + self.targeted_label: targets, + self.ori_img: oimgs, + self.const: CONST + }) + return loss, grad.flatten().astype(float) + + def attack_success(out, target, targeted_attack): + """ returns attack result """ + if targeted_attack: + return out == target + else: + return out != target + + # begin the main part for the attack + from scipy.optimize import fmin_l_bfgs_b + oimgs = np.clip(x_val, self.clip_min, self.clip_max) + CONST = np.ones(self.batch_size) * self.initial_const + + # set the lower and upper bounds accordingly + lower_bound = np.zeros(self.batch_size) + upper_bound = np.ones(self.batch_size) * 1e10 + + # set the box constraints for the optimization function + clip_min = self.clip_min * np.ones(oimgs.shape[:]) + clip_max = self.clip_max * np.ones(oimgs.shape[:]) + clip_bound = list(zip(clip_min.flatten(), clip_max.flatten())) + + # placeholders for the best l2 and instance attack found so far + o_bestl2 = [1e10] * self.batch_size + o_bestattack = np.copy(oimgs) + + for outer_step in range(self.binary_search_steps): + _logger.debug(" Binary search step %s of %s", + outer_step, self.binary_search_steps) + + # The last iteration (if we run many steps) repeat the search once. + if self.repeat and outer_step == self.binary_search_steps - 1: + CONST = upper_bound + + # optimization function + adv_x, _, __ = fmin_l_bfgs_b( + lbfgs_objective, + oimgs.flatten().astype(float), + args=(self, targets, oimgs, CONST), + bounds=clip_bound, + maxiter=self.max_iterations, + iprint=0) + + adv_x = adv_x.reshape(oimgs.shape) + assert np.amax(adv_x) <= self.clip_max and \ + np.amin(adv_x) >= self.clip_min, \ + 'fmin_l_bfgs_b returns are invalid' + + # adjust the best result (i.e., the adversarial example with the + # smallest perturbation in terms of L_2 norm) found so far + preds = np.atleast_1d( + utils_tf.model_argmax(self.sess, self.x, self.logits, + adv_x)) + _logger.debug("predicted labels are %s", preds) + + l2s = np.zeros(self.batch_size) + for i in range(self.batch_size): + l2s[i] = np.sum(np.square(adv_x[i] - oimgs[i])) + + for e, (l2, pred, ii) in enumerate(zip(l2s, preds, adv_x)): + if l2 < o_bestl2[e] and attack_success(pred, np.argmax(targets[e]), + self.targeted_attack): + o_bestl2[e] = l2 + o_bestattack[e] = ii + + # adjust the constant as needed + for e in range(self.batch_size): + if attack_success(preds[e], np.argmax(targets[e]), + self.targeted_attack): + # success, divide const by two + upper_bound[e] = min(upper_bound[e], CONST[e]) + if upper_bound[e] < 1e9: + CONST[e] = (lower_bound[e] + upper_bound[e]) / 2 + else: + # failure, either multiply by 10 if no solution found yet + # or do binary search with the known upper bound + lower_bound[e] = max(lower_bound[e], CONST[e]) + if upper_bound[e] < 1e9: + CONST[e] = (lower_bound[e] + upper_bound[e]) / 2 + else: + CONST[e] *= 10 + + _logger.debug(" Successfully generated adversarial examples " + "on %s of %s instances.", + sum(upper_bound < 1e9), self.batch_size) + o_bestl2 = np.array(o_bestl2) + mean = np.mean(np.sqrt(o_bestl2[o_bestl2 < 1e9])) + _logger.debug(" Mean successful distortion: {:.4g}".format(mean)) + + # return the best solution found + o_bestl2 = np.array(o_bestl2) + return o_bestattack +
+ +
+ +
+
+ +
+
+ + + + \ No newline at end of file diff --git a/cleverhans_v3.1.0/docs/_modules/cleverhans/attacks/madry_et_al.html b/cleverhans_v3.1.0/docs/_modules/cleverhans/attacks/madry_et_al.html new file mode 100644 index 000000000..4dc2ea21c --- /dev/null +++ b/cleverhans_v3.1.0/docs/_modules/cleverhans/attacks/madry_et_al.html @@ -0,0 +1,107 @@ + + + + + + + + cleverhans.attacks.madry_et_al — CleverHans documentation + + + + + + + + + + + + + + + + + +
+
+
+ + +
+ +

Source code for cleverhans.attacks.madry_et_al

+"""
+The MadryEtAl attack
+"""
+
+from cleverhans.attacks.projected_gradient_descent import ProjectedGradientDescent
+
+
+
[docs]class MadryEtAl(ProjectedGradientDescent): + """ + The attack from Madry et al 2017 + """ + def __init__(self, model, sess=None, dtypestr='float32', **kwargs): + super(MadryEtAl, self).__init__(model, sess=sess, + dtypestr=dtypestr, + default_rand_init=True, + **kwargs)
+
+ +
+ +
+
+ +
+
+ + + + \ No newline at end of file diff --git a/cleverhans_v3.1.0/docs/_modules/cleverhans/attacks/max_confidence.html b/cleverhans_v3.1.0/docs/_modules/cleverhans/attacks/max_confidence.html new file mode 100644 index 000000000..f483d53d9 --- /dev/null +++ b/cleverhans_v3.1.0/docs/_modules/cleverhans/attacks/max_confidence.html @@ -0,0 +1,212 @@ + + + + + + + + cleverhans.attacks.max_confidence — CleverHans documentation + + + + + + + + + + + + + + + + + +
+
+
+ + +
+ +

Source code for cleverhans.attacks.max_confidence

+"""The MaxConfidence attack.
+"""
+import warnings
+
+import tensorflow as tf
+
+from cleverhans.attacks.attack import Attack
+from cleverhans.attacks.projected_gradient_descent import ProjectedGradientDescent
+from cleverhans.model import Model
+
+
+
[docs]class MaxConfidence(Attack): + """ + The MaxConfidence attack. + + An attack designed for use against models that use confidence thresholding + as a defense. + If the underlying optimizer is optimal, this attack procedure gives the + optimal failure rate for every confidence threshold t > 0.5. + + Publication: https://openreview.net/forum?id=H1g0piA9tQ + + :param model: cleverhans.model.Model + :param sess: optional tf.session.Session + :param base_attacker: cleverhans.attacks.Attack + """ + + def __init__(self, model, sess=None, base_attacker=None): + if not isinstance(model, Model): + raise TypeError("Model must be cleverhans.model.Model, got " + + str(type(model))) + + super(MaxConfidence, self).__init__(model, sess) + if base_attacker is None: + self.base_attacker = ProjectedGradientDescent(model, sess=sess) + else: + self.base_attacker = base_attacker + self.structural_kwargs = self.base_attacker.structural_kwargs + self.feedable_kwargs = self.base_attacker.feedable_kwargs + +
[docs] def generate(self, x, **kwargs): + """ + Generate symbolic graph for adversarial examples and return. + + :param x: The model's symbolic inputs. + :param kwargs: Keyword arguments for the base attacker + """ + + assert self.parse_params(**kwargs) + labels, _nb_classes = self.get_or_guess_labels(x, kwargs) + adv_x = self.attack(x, labels) + + return adv_x
+ +
[docs] def parse_params(self, y=None, nb_classes=10, **kwargs): + self.y = y + self.nb_classes = nb_classes + self.params = kwargs + if len(kwargs.keys()) > 0: + warnings.warn("kwargs is unused and will be removed on or after " + "2019-04-26.") + return True
+ +
[docs] def attack(self, x, true_y): + """ + Runs the untargeted attack. + :param x: The input + :param true_y: The correct label for `x`. This attack aims to produce misclassification. + """ + adv_x_cls = [] + prob_cls = [] + m = tf.shape(x)[0] + true_y_idx = tf.argmax(true_y, axis=1) + + expanded_x = tf.concat([x] * self.nb_classes, axis=0) + target_ys = [tf.to_float(tf.one_hot(tf.ones(m, dtype=tf.int32) * cls, + self.nb_classes)) + for cls in range(self.nb_classes)] + target_y = tf.concat(target_ys, axis=0) + adv_x_cls = self.attack_class(expanded_x, target_y) + expanded_all_probs = self.model.get_probs(adv_x_cls) + + adv_x_list = tf.split(adv_x_cls, self.nb_classes) + all_probs_list = tf.split(expanded_all_probs, self.nb_classes) + + for cls in range(self.nb_classes): + target_y = target_ys[cls] + all_probs = all_probs_list[cls] + # We don't actually care whether we hit the target class. + # We care about the probability of the most likely wrong class + cur_prob_cls = tf.reduce_max(all_probs - true_y, axis=1) + # Knock out examples that are correctly classified. + # This is not needed to be optimal for t >= 0.5, but may as well do it + # to get better failure rate at lower thresholds. + chosen_cls = tf.argmax(all_probs, axis=1) + eligible = tf.to_float(tf.not_equal(true_y_idx, chosen_cls)) + cur_prob_cls = cur_prob_cls * eligible + prob_cls.append(cur_prob_cls) + + probs = tf.concat([tf.expand_dims(e, 1) for e in prob_cls], axis=1) + # Don't need to censor here because we knocked out the true class above + # probs = probs - true_y + most_confident = tf.argmax(probs, axis=1) + fused_mask = tf.one_hot(most_confident, self.nb_classes) + masks = tf.split(fused_mask, num_or_size_splits=self.nb_classes, axis=1) + shape = [m] + [1] * (len(x.get_shape()) - 1) + reshaped_masks = [tf.reshape(mask, shape) for mask in masks] + out = sum(adv_x * rmask for adv_x, + rmask in zip(adv_x_list, reshaped_masks)) + return out
+ +
[docs] def attack_class(self, x, target_y): + """ + Run the attack on a specific target class. + :param x: tf Tensor. The input example. + :param target_y: tf Tensor. The attacker's desired target class. + Returns: + A targeted adversarial example, intended to be classified as the target class. + """ + adv = self.base_attacker.generate(x, y_target=target_y, **self.params) + return adv
+
+ +
+ +
+
+ +
+
+ + + + \ No newline at end of file diff --git a/cleverhans_v3.1.0/docs/_modules/cleverhans/attacks/momentum_iterative_method.html b/cleverhans_v3.1.0/docs/_modules/cleverhans/attacks/momentum_iterative_method.html new file mode 100644 index 000000000..1375c70f7 --- /dev/null +++ b/cleverhans_v3.1.0/docs/_modules/cleverhans/attacks/momentum_iterative_method.html @@ -0,0 +1,271 @@ + + + + + + + + cleverhans.attacks.momentum_iterative_method — CleverHans documentation + + + + + + + + + + + + + + + + + +
+
+
+ + +
+ +

Source code for cleverhans.attacks.momentum_iterative_method

+"""The MomentumIterativeMethod attack.
+"""
+
+import warnings
+
+import numpy as np
+import tensorflow as tf
+
+from cleverhans.attacks.attack import Attack
+from cleverhans.attacks.fast_gradient_method import optimize_linear
+from cleverhans.compat import reduce_sum, reduce_mean, softmax_cross_entropy_with_logits
+from cleverhans import utils_tf
+
+
+
[docs]class MomentumIterativeMethod(Attack): + """ + The Momentum Iterative Method (Dong et al. 2017). This method won + the first places in NIPS 2017 Non-targeted Adversarial Attacks and + Targeted Adversarial Attacks. The original paper used hard labels + for this attack; no label smoothing. + Paper link: https://arxiv.org/pdf/1710.06081.pdf + + :param model: cleverhans.model.Model + :param sess: optional tf.Session + :param dtypestr: dtype of the data + :param kwargs: passed through to super constructor + """ + + def __init__(self, model, sess=None, dtypestr='float32', **kwargs): + """ + Create a MomentumIterativeMethod instance. + Note: the model parameter should be an instance of the + cleverhans.model.Model abstraction provided by CleverHans. + """ + + super(MomentumIterativeMethod, self).__init__(model, sess, dtypestr, + **kwargs) + self.feedable_kwargs = ('eps', 'eps_iter', 'y', 'y_target', 'clip_min', + 'clip_max') + self.structural_kwargs = [ + 'ord', 'nb_iter', 'decay_factor', 'sanity_checks', 'clip_grad'] + +
[docs] def generate(self, x, **kwargs): + """ + Generate symbolic graph for adversarial examples and return. + + :param x: The model's symbolic inputs. + :param kwargs: Keyword arguments. See `parse_params` for documentation. + """ + # Parse and save attack-specific parameters + assert self.parse_params(**kwargs) + + asserts = [] + + # If a data range was specified, check that the input was in that range + if self.clip_min is not None: + asserts.append(utils_tf.assert_greater_equal(x, + tf.cast(self.clip_min, + x.dtype))) + + if self.clip_max is not None: + asserts.append(utils_tf.assert_less_equal(x, + tf.cast(self.clip_max, + x.dtype))) + + # Initialize loop variables + momentum = tf.zeros_like(x) + adv_x = x + + # Fix labels to the first model predictions for loss computation + y, _nb_classes = self.get_or_guess_labels(x, kwargs) + y = y / reduce_sum(y, 1, keepdims=True) + targeted = (self.y_target is not None) + + def cond(i, _, __): + """Iterate until number of iterations completed""" + return tf.less(i, self.nb_iter) + + def body(i, ax, m): + """Do a momentum step""" + logits = self.model.get_logits(ax) + loss = softmax_cross_entropy_with_logits(labels=y, logits=logits) + if targeted: + loss = -loss + + # Define gradient of loss wrt input + grad, = tf.gradients(loss, ax) + + # Normalize current gradient and add it to the accumulated gradient + red_ind = list(range(1, len(grad.get_shape()))) + avoid_zero_div = tf.cast(1e-12, grad.dtype) + grad = grad / tf.maximum( + avoid_zero_div, + reduce_mean(tf.abs(grad), red_ind, keepdims=True)) + m = self.decay_factor * m + grad + + optimal_perturbation = optimize_linear(m, self.eps_iter, self.ord) + if self.ord == 1: + raise NotImplementedError("This attack hasn't been tested for ord=1." + "It's not clear that FGM makes a good inner " + "loop step for iterative optimization since " + "it updates just one coordinate at a time.") + + # Update and clip adversarial example in current iteration + ax = ax + optimal_perturbation + ax = x + utils_tf.clip_eta(ax - x, self.ord, self.eps) + + if self.clip_min is not None and self.clip_max is not None: + ax = utils_tf.clip_by_value(ax, self.clip_min, self.clip_max) + + ax = tf.stop_gradient(ax) + + return i + 1, ax, m + + _, adv_x, _ = tf.while_loop( + cond, body, (tf.zeros([]), adv_x, momentum), back_prop=True, + maximum_iterations=self.nb_iter) + + if self.sanity_checks: + with tf.control_dependencies(asserts): + adv_x = tf.identity(adv_x) + + return adv_x
+ +
[docs] def parse_params(self, + eps=0.3, + eps_iter=0.06, + nb_iter=10, + y=None, + ord=np.inf, + decay_factor=1.0, + clip_min=None, + clip_max=None, + y_target=None, + sanity_checks=True, + **kwargs): + """ + Take in a dictionary of parameters and applies attack-specific checks + before saving them as attributes. + + Attack-specific parameters: + + :param eps: (optional float) maximum distortion of adversarial example + compared to original input + :param eps_iter: (optional float) step size for each attack iteration + :param nb_iter: (optional int) Number of attack iterations. + :param y: (optional) A tensor with the true labels. + :param y_target: (optional) A tensor with the labels to target. Leave + y_target=None if y is also set. Labels should be + one-hot-encoded. + :param ord: (optional) Order of the norm (mimics Numpy). + Possible values: np.inf, 1 or 2. + :param decay_factor: (optional) Decay factor for the momentum term. + :param clip_min: (optional float) Minimum input component value + :param clip_max: (optional float) Maximum input component value + """ + + # Save attack-specific parameters + self.eps = eps + self.eps_iter = eps_iter + self.nb_iter = nb_iter + self.y = y + self.y_target = y_target + self.ord = ord + self.decay_factor = decay_factor + self.clip_min = clip_min + self.clip_max = clip_max + self.sanity_checks = sanity_checks + + if self.y is not None and self.y_target is not None: + raise ValueError("Must not set both y and y_target") + # Check if order of the norm is acceptable given current implementation + if self.ord not in [np.inf, 1, 2]: + raise ValueError("Norm order must be either np.inf, 1, or 2.") + + if len(kwargs.keys()) > 0: + warnings.warn("kwargs is unused and will be removed on or after " + "2019-04-26.") + + return True
+
+ +
+ +
+
+ +
+
+ + + + \ No newline at end of file diff --git a/cleverhans_v3.1.0/docs/_modules/cleverhans/attacks/noise.html b/cleverhans_v3.1.0/docs/_modules/cleverhans/attacks/noise.html new file mode 100644 index 000000000..971047cb9 --- /dev/null +++ b/cleverhans_v3.1.0/docs/_modules/cleverhans/attacks/noise.html @@ -0,0 +1,183 @@ + + + + + + + + cleverhans.attacks.noise — CleverHans documentation + + + + + + + + + + + + + + + + + +
+
+
+ + +
+ +

Source code for cleverhans.attacks.noise

+"""The Noise attack
+
+"""
+import warnings
+
+import numpy as np
+import tensorflow as tf
+
+from cleverhans.attacks.attack import Attack
+
+
+
[docs]class Noise(Attack): + """ + A weak attack that just picks a random point in the attacker's action space. + When combined with an attack bundling function, this can be used to implement + random search. + + References: + https://arxiv.org/abs/1802.00420 recommends random search to help identify + gradient masking. + https://openreview.net/forum?id=H1g0piA9tQ recommends using noise as part + of an attack bundling recipe combining many different optimizers to yield + a stronger optimizer. + + :param model: cleverhans.model.Model + :param sess: optional tf.Session + :param dtypestr: dtype of the data + :param kwargs: passed through to super constructor + """ + + def __init__(self, model, sess=None, dtypestr='float32', + **kwargs): + + super(Noise, self).__init__(model, sess=sess, dtypestr=dtypestr, **kwargs) + self.feedable_kwargs = ('eps', 'clip_min', 'clip_max') + self.structural_kwargs = ['ord'] + +
[docs] def generate(self, x, **kwargs): + """ + Generate symbolic graph for adversarial examples and return. + + :param x: The model's symbolic inputs. + :param kwargs: See `parse_params` + """ + # Parse and save attack-specific parameters + assert self.parse_params(**kwargs) + + if self.ord != np.inf: + raise NotImplementedError(self.ord) + eta = tf.random_uniform(tf.shape(x), -self.eps, self.eps, + dtype=self.tf_dtype) + adv_x = x + eta + if self.clip_min is not None or self.clip_max is not None: + assert self.clip_min is not None and self.clip_max is not None + adv_x = tf.clip_by_value(adv_x, self.clip_min, self.clip_max) + + return adv_x
+ +
[docs] def parse_params(self, + eps=0.3, + ord=np.inf, + clip_min=None, + clip_max=None, + **kwargs): + """ + Take in a dictionary of parameters and applies attack-specific checks + before saving them as attributes. + + Attack-specific parameters: + + :param eps: (optional float) maximum distortion of adversarial example + compared to original input + :param ord: (optional) Order of the norm (mimics Numpy). + Possible values: np.inf + :param clip_min: (optional float) Minimum input component value + :param clip_max: (optional float) Maximum input component value + """ + + # Save attack-specific parameters + self.eps = eps + self.ord = ord + self.clip_min = clip_min + self.clip_max = clip_max + + # Check if order of the norm is acceptable given current implementation + if self.ord not in [np.inf]: + raise ValueError("Norm order must be np.inf") + if len(kwargs.keys()) > 0: + warnings.warn("kwargs is unused and will be removed on or after " + "2019-04-26.") + + return True
+
+ +
+ +
+
+ +
+
+ + + + \ No newline at end of file diff --git a/cleverhans_v3.1.0/docs/_modules/cleverhans/attacks/projected_gradient_descent.html b/cleverhans_v3.1.0/docs/_modules/cleverhans/attacks/projected_gradient_descent.html new file mode 100644 index 000000000..9c8ec1edb --- /dev/null +++ b/cleverhans_v3.1.0/docs/_modules/cleverhans/attacks/projected_gradient_descent.html @@ -0,0 +1,350 @@ + + + + + + + + cleverhans.attacks.projected_gradient_descent — CleverHans documentation + + + + + + + + + + + + + + + + + +
+
+
+ + +
+ +

Source code for cleverhans.attacks.projected_gradient_descent

+"""
+The ProjectedGradientDescent attack.
+"""
+
+import warnings
+
+import numpy as np
+import tensorflow as tf
+
+from cleverhans.attacks.attack import Attack
+from cleverhans.attacks.fast_gradient_method import FastGradientMethod
+from cleverhans import utils_tf
+from cleverhans.compat import softmax_cross_entropy_with_logits
+from cleverhans.utils_tf import clip_eta, random_lp_vector
+
+
+
[docs]class ProjectedGradientDescent(Attack): + """ + This class implements either the Basic Iterative Method + (Kurakin et al. 2016) when rand_init is set to 0. or the + Madry et al. (2017) method when rand_minmax is larger than 0. + Paper link (Kurakin et al. 2016): https://arxiv.org/pdf/1607.02533.pdf + Paper link (Madry et al. 2017): https://arxiv.org/pdf/1706.06083.pdf + + :param model: cleverhans.model.Model + :param sess: optional tf.Session + :param dtypestr: dtype of the data + :param default_rand_init: whether to use random initialization by default + :param kwargs: passed through to super constructor + """ + + FGM_CLASS = FastGradientMethod + + def __init__(self, model, sess=None, dtypestr='float32', + default_rand_init=True, **kwargs): + """ + Create a ProjectedGradientDescent instance. + Note: the model parameter should be an instance of the + cleverhans.model.Model abstraction provided by CleverHans. + """ + + super(ProjectedGradientDescent, self).__init__(model, sess=sess, + dtypestr=dtypestr, **kwargs) + self.feedable_kwargs = ('eps', 'eps_iter', 'y', 'y_target', 'clip_min', + 'clip_max') + self.structural_kwargs = ['ord', 'nb_iter', 'rand_init', 'clip_grad', + 'sanity_checks', 'loss_fn'] + self.default_rand_init = default_rand_init + +
[docs] def generate(self, x, **kwargs): + """ + Generate symbolic graph for adversarial examples and return. + + :param x: The model's symbolic inputs. + :param kwargs: See `parse_params` + """ + # Parse and save attack-specific parameters + assert self.parse_params(**kwargs) + + asserts = [] + + # If a data range was specified, check that the input was in that range + if self.clip_min is not None: + asserts.append(utils_tf.assert_greater_equal(x, + tf.cast(self.clip_min, + x.dtype))) + + if self.clip_max is not None: + asserts.append(utils_tf.assert_less_equal(x, + tf.cast(self.clip_max, + x.dtype))) + + # Initialize loop variables + if self.rand_init: + eta = random_lp_vector(tf.shape(x), self.ord, + tf.cast(self.rand_init_eps, x.dtype), + dtype=x.dtype) + else: + eta = tf.zeros(tf.shape(x)) + + # Clip eta + eta = clip_eta(eta, self.ord, self.eps) + adv_x = x + eta + if self.clip_min is not None or self.clip_max is not None: + adv_x = utils_tf.clip_by_value(adv_x, self.clip_min, self.clip_max) + + if self.y_target is not None: + y = self.y_target + targeted = True + elif self.y is not None: + y = self.y + targeted = False + else: + model_preds = self.model.get_probs(x) + preds_max = tf.reduce_max(model_preds, 1, keepdims=True) + y = tf.to_float(tf.equal(model_preds, preds_max)) + y = tf.stop_gradient(y) + targeted = False + del model_preds + + y_kwarg = 'y_target' if targeted else 'y' + + fgm_params = { + 'eps': self.eps_iter, + y_kwarg: y, + 'ord': self.ord, + 'loss_fn': self.loss_fn, + 'clip_min': self.clip_min, + 'clip_max': self.clip_max, + 'clip_grad': self.clip_grad + } + if self.ord == 1: + raise NotImplementedError("FGM is not a good inner loop step for PGD " + " when ord=1, because ord=1 FGM changes only " + " one pixel at a time. Use the SparseL1Descent " + " attack instead, which allows fine-grained " + " control over the sparsity of the gradient " + " updates.") + + # Use getattr() to avoid errors in eager execution attacks + FGM = self.FGM_CLASS( + self.model, + sess=getattr(self, 'sess', None), + dtypestr=self.dtypestr) + + def cond(i, _): + """Iterate until requested number of iterations is completed""" + return tf.less(i, self.nb_iter) + + def body(i, adv_x): + """Do a projected gradient step""" + adv_x = FGM.generate(adv_x, **fgm_params) + + # Clipping perturbation eta to self.ord norm ball + eta = adv_x - x + eta = clip_eta(eta, self.ord, self.eps) + adv_x = x + eta + + # Redo the clipping. + # FGM already did it, but subtracting and re-adding eta can add some + # small numerical error. + if self.clip_min is not None or self.clip_max is not None: + adv_x = utils_tf.clip_by_value(adv_x, self.clip_min, self.clip_max) + + return i + 1, adv_x + + _, adv_x = tf.while_loop(cond, body, (tf.zeros([]), adv_x), back_prop=True, + maximum_iterations=self.nb_iter) + + # Asserts run only on CPU. + # When multi-GPU eval code tries to force all PGD ops onto GPU, this + # can cause an error. + common_dtype = tf.float32 + asserts.append(utils_tf.assert_less_equal(tf.cast(self.eps_iter, + dtype=common_dtype), + tf.cast(self.eps, dtype=common_dtype))) + if self.ord == np.inf and self.clip_min is not None: + # The 1e-6 is needed to compensate for numerical error. + # Without the 1e-6 this fails when e.g. eps=.2, clip_min=.5, + # clip_max=.7 + asserts.append(utils_tf.assert_less_equal(tf.cast(self.eps, x.dtype), + 1e-6 + tf.cast(self.clip_max, + x.dtype) + - tf.cast(self.clip_min, + x.dtype))) + + if self.sanity_checks: + with tf.control_dependencies(asserts): + adv_x = tf.identity(adv_x) + + return adv_x
+ +
[docs] def parse_params(self, + eps=0.3, + eps_iter=0.05, + nb_iter=10, + y=None, + ord=np.inf, + loss_fn=softmax_cross_entropy_with_logits, + clip_min=None, + clip_max=None, + y_target=None, + rand_init=None, + rand_init_eps=None, + clip_grad=False, + sanity_checks=True, + **kwargs): + """ + Take in a dictionary of parameters and applies attack-specific checks + before saving them as attributes. + + Attack-specific parameters: + + :param eps: (optional float) maximum distortion of adversarial example + compared to original input + :param eps_iter: (optional float) step size for each attack iteration + :param nb_iter: (optional int) Number of attack iterations. + :param y: (optional) A tensor with the true labels. + :param y_target: (optional) A tensor with the labels to target. Leave + y_target=None if y is also set. Labels should be + one-hot-encoded. + :param ord: (optional) Order of the norm (mimics Numpy). + Possible values: np.inf, 1 or 2. + :param loss_fn: Loss function that takes (labels, logits) as arguments and returns loss + :param clip_min: (optional float) Minimum input component value + :param clip_max: (optional float) Maximum input component value + :param rand_init: (optional) Start the gradient descent from a point chosen + uniformly at random in the norm ball of radius + rand_init_eps + :param rand_init_eps: (optional float) size of the norm ball from which + the initial starting point is chosen. Defaults to eps + :param clip_grad: (optional bool) Ignore gradient components at positions + where the input is already at the boundary of the domain, + and the update step will get clipped out. + :param sanity_checks: bool Insert tf asserts checking values + (Some tests need to run with no sanity checks because the + tests intentionally configure the attack strangely) + """ + + # Save attack-specific parameters + self.eps = eps + if rand_init is None: + rand_init = self.default_rand_init + self.rand_init = rand_init + if rand_init_eps is None: + rand_init_eps = self.eps + self.rand_init_eps = rand_init_eps + + self.eps_iter = eps_iter + self.nb_iter = nb_iter + self.y = y + self.y_target = y_target + self.ord = ord + self.loss_fn = loss_fn + self.clip_min = clip_min + self.clip_max = clip_max + self.clip_grad = clip_grad + + if isinstance(eps, float) and isinstance(eps_iter, float): + # If these are both known at compile time, we can check before anything + # is run. If they are tf, we can't check them yet. + assert eps_iter <= eps, (eps_iter, eps) + + if self.y is not None and self.y_target is not None: + raise ValueError("Must not set both y and y_target") + # Check if order of the norm is acceptable given current implementation + if self.ord not in [np.inf, 1, 2]: + raise ValueError("Norm order must be either np.inf, 1, or 2.") + + if self.clip_grad and (self.clip_min is None or self.clip_max is None): + raise ValueError("Must set clip_min and clip_max if clip_grad is set") + + self.sanity_checks = sanity_checks + + if len(kwargs.keys()) > 0: + warnings.warn("kwargs is unused and will be removed on or after " + "2019-04-26.") + + return True
+
+ +
+ +
+
+ +
+
+ + + + \ No newline at end of file diff --git a/cleverhans_v3.1.0/docs/_modules/cleverhans/attacks/saliency_map_method.html b/cleverhans_v3.1.0/docs/_modules/cleverhans/attacks/saliency_map_method.html new file mode 100644 index 000000000..5b6c444db --- /dev/null +++ b/cleverhans_v3.1.0/docs/_modules/cleverhans/attacks/saliency_map_method.html @@ -0,0 +1,372 @@ + + + + + + + + cleverhans.attacks.saliency_map_method — CleverHans documentation + + + + + + + + + + + + + + + + + +
+
+
+ + +
+ +

Source code for cleverhans.attacks.saliency_map_method

+"""The SalienceMapMethod attack
+"""
+# pylint: disable=missing-docstring
+import warnings
+
+import numpy as np
+from six.moves import xrange
+import tensorflow as tf
+
+from cleverhans.attacks.attack import Attack
+from cleverhans.compat import reduce_sum, reduce_max, reduce_any
+
+tf_dtype = tf.as_dtype('float32')
+
+
+
[docs]class SaliencyMapMethod(Attack): + """ + The Jacobian-based Saliency Map Method (Papernot et al. 2016). + Paper link: https://arxiv.org/pdf/1511.07528.pdf + + :param model: cleverhans.model.Model + :param sess: optional tf.Session + :param dtypestr: dtype of the data + :param kwargs: passed through to super constructor + + :note: When not using symbolic implementation in `generate`, `sess` should + be provided + """ + + def __init__(self, model, sess=None, dtypestr='float32', **kwargs): + """ + Create a SaliencyMapMethod instance. + Note: the model parameter should be an instance of the + cleverhans.model.Model abstraction provided by CleverHans. + """ + + super(SaliencyMapMethod, self).__init__(model, sess, dtypestr, **kwargs) + + self.feedable_kwargs = ('y_target',) + self.structural_kwargs = [ + 'theta', 'gamma', 'clip_max', 'clip_min', 'symbolic_impl' + ] + +
[docs] def generate(self, x, **kwargs): + """ + Generate symbolic graph for adversarial examples and return. + + :param x: The model's symbolic inputs. + :param kwargs: See `parse_params` + """ + # Parse and save attack-specific parameters + assert self.parse_params(**kwargs) + + if self.symbolic_impl: + # Create random targets if y_target not provided + if self.y_target is None: + from random import randint + + def random_targets(gt): + result = gt.copy() + nb_s = gt.shape[0] + nb_classes = gt.shape[1] + + for i in range(nb_s): + result[i, :] = np.roll(result[i, :], + randint(1, nb_classes - 1)) + + return result + + labels, nb_classes = self.get_or_guess_labels(x, kwargs) + self.y_target = tf.py_func(random_targets, [labels], + self.tf_dtype) + self.y_target.set_shape([None, nb_classes]) + + x_adv = jsma_symbolic( + x, + model=self.model, + y_target=self.y_target, + theta=self.theta, + gamma=self.gamma, + clip_min=self.clip_min, + clip_max=self.clip_max) + else: + raise NotImplementedError("The jsma_batch function has been removed." + " The symbolic_impl argument to SaliencyMapMethod will be removed" + " on 2019-07-18 or after. Any code that depends on the non-symbolic" + " implementation of the JSMA should be revised. Consider using" + " SaliencyMapMethod.generate_np() instead.") + + return x_adv
+ +
[docs] def parse_params(self, + theta=1., + gamma=1., + clip_min=0., + clip_max=1., + y_target=None, + symbolic_impl=True, + **kwargs): + """ + Take in a dictionary of parameters and applies attack-specific checks + before saving them as attributes. + + Attack-specific parameters: + + :param theta: (optional float) Perturbation introduced to modified + components (can be positive or negative) + :param gamma: (optional float) Maximum percentage of perturbed features + :param clip_min: (optional float) Minimum component value for clipping + :param clip_max: (optional float) Maximum component value for clipping + :param y_target: (optional) Target tensor if the attack is targeted + """ + self.theta = theta + self.gamma = gamma + self.clip_min = clip_min + self.clip_max = clip_max + self.y_target = y_target + self.symbolic_impl = symbolic_impl + + if len(kwargs.keys()) > 0: + warnings.warn("kwargs is unused and will be removed on or after " + "2019-04-26.") + + return True
+ + +def jsma_batch(*args, **kwargs): + raise NotImplementedError( + "The jsma_batch function has been removed. Any code that depends on it should be revised.") + + +def jsma_symbolic(x, y_target, model, theta, gamma, clip_min, clip_max): + """ + TensorFlow implementation of the JSMA (see https://arxiv.org/abs/1511.07528 + for details about the algorithm design choices). + + :param x: the input placeholder + :param y_target: the target tensor + :param model: a cleverhans.model.Model object. + :param theta: delta for each feature adjustment + :param gamma: a float between 0 - 1 indicating the maximum distortion + percentage + :param clip_min: minimum value for components of the example returned + :param clip_max: maximum value for components of the example returned + :return: a tensor for the adversarial example + """ + + nb_classes = int(y_target.shape[-1].value) + nb_features = int(np.product(x.shape[1:]).value) + + if x.dtype == tf.float32 and y_target.dtype == tf.int64: + y_target = tf.cast(y_target, tf.int32) + + if x.dtype == tf.float32 and y_target.dtype == tf.float64: + warnings.warn("Downcasting labels---this should be harmless unless" + " they are smoothed") + y_target = tf.cast(y_target, tf.float32) + + max_iters = np.floor(nb_features * gamma / 2) + increase = bool(theta > 0) + + tmp = np.ones((nb_features, nb_features), int) + np.fill_diagonal(tmp, 0) + zero_diagonal = tf.constant(tmp, tf_dtype) + + # Compute our initial search domain. We optimize the initial search domain + # by removing all features that are already at their maximum values (if + # increasing input features---otherwise, at their minimum value). + if increase: + search_domain = tf.reshape( + tf.cast(x < clip_max, tf_dtype), [-1, nb_features]) + else: + search_domain = tf.reshape( + tf.cast(x > clip_min, tf_dtype), [-1, nb_features]) + + # Loop variables + # x_in: the tensor that holds the latest adversarial outputs that are in + # progress. + # y_in: the tensor for target labels + # domain_in: the tensor that holds the latest search domain + # cond_in: the boolean tensor to show if more iteration is needed for + # generating adversarial samples + def condition(x_in, y_in, domain_in, i_in, cond_in): + # Repeat the loop until we have achieved misclassification or + # reaches the maximum iterations + return tf.logical_and(tf.less(i_in, max_iters), cond_in) + + # Same loop variables as above + def body(x_in, y_in, domain_in, i_in, cond_in): + # Create graph for model logits and predictions + logits = model.get_logits(x_in) + preds = tf.nn.softmax(logits) + preds_onehot = tf.one_hot(tf.argmax(preds, axis=1), depth=nb_classes) + + # create the Jacobian graph + list_derivatives = [] + for class_ind in xrange(nb_classes): + derivatives = tf.gradients(logits[:, class_ind], x_in) + list_derivatives.append(derivatives[0]) + grads = tf.reshape( + tf.stack(list_derivatives), shape=[nb_classes, -1, nb_features]) + + # Compute the Jacobian components + # To help with the computation later, reshape the target_class + # and other_class to [nb_classes, -1, 1]. + # The last dimention is added to allow broadcasting later. + target_class = tf.reshape( + tf.transpose(y_in, perm=[1, 0]), shape=[nb_classes, -1, 1]) + other_classes = tf.cast(tf.not_equal(target_class, 1), tf_dtype) + + grads_target = reduce_sum(grads * target_class, axis=0) + grads_other = reduce_sum(grads * other_classes, axis=0) + + # Remove the already-used input features from the search space + # Subtract 2 times the maximum value from those value so that + # they won't be picked later + increase_coef = (4 * int(increase) - 2) \ + * tf.cast(tf.equal(domain_in, 0), tf_dtype) + + target_tmp = grads_target + target_tmp -= increase_coef \ + * reduce_max(tf.abs(grads_target), axis=1, keepdims=True) + target_sum = tf.reshape(target_tmp, shape=[-1, nb_features, 1]) \ + + tf.reshape(target_tmp, shape=[-1, 1, nb_features]) + + other_tmp = grads_other + other_tmp += increase_coef \ + * reduce_max(tf.abs(grads_other), axis=1, keepdims=True) + other_sum = tf.reshape(other_tmp, shape=[-1, nb_features, 1]) \ + + tf.reshape(other_tmp, shape=[-1, 1, nb_features]) + + # Create a mask to only keep features that match conditions + if increase: + scores_mask = ((target_sum > 0) & (other_sum < 0)) + else: + scores_mask = ((target_sum < 0) & (other_sum > 0)) + + # Create a 2D numpy array of scores for each pair of candidate features + scores = tf.cast(scores_mask, tf_dtype) \ + * (-target_sum * other_sum) * zero_diagonal + + # Extract the best two pixels + best = tf.argmax( + tf.reshape(scores, shape=[-1, nb_features * nb_features]), axis=1) + + p1 = tf.mod(best, nb_features) + p2 = tf.floordiv(best, nb_features) + p1_one_hot = tf.one_hot(p1, depth=nb_features) + p2_one_hot = tf.one_hot(p2, depth=nb_features) + + # Check if more modification is needed for each sample + mod_not_done = tf.equal(reduce_sum(y_in * preds_onehot, axis=1), 0) + cond = mod_not_done & (reduce_sum(domain_in, axis=1) >= 2) + + # Update the search domain + cond_float = tf.reshape(tf.cast(cond, tf_dtype), shape=[-1, 1]) + to_mod = (p1_one_hot + p2_one_hot) * cond_float + + domain_out = domain_in - to_mod + + # Apply the modification to the images + to_mod_reshape = tf.reshape( + to_mod, shape=([-1] + x_in.shape[1:].as_list())) + if increase: + x_out = tf.minimum(clip_max, x_in + to_mod_reshape * theta) + else: + x_out = tf.maximum(clip_min, x_in - to_mod_reshape * theta) + + # Increase the iterator, and check if all misclassifications are done + i_out = tf.add(i_in, 1) + cond_out = reduce_any(cond) + + return x_out, y_in, domain_out, i_out, cond_out + + # Run loop to do JSMA + x_adv, _, _, _, _ = tf.while_loop( + condition, + body, [x, y_target, search_domain, 0, True], + parallel_iterations=1) + + return x_adv +
+ +
+ +
+
+ +
+
+ + + + \ No newline at end of file diff --git a/cleverhans_v3.1.0/docs/_modules/cleverhans/attacks/semantic.html b/cleverhans_v3.1.0/docs/_modules/cleverhans/attacks/semantic.html new file mode 100644 index 000000000..475fd7ba5 --- /dev/null +++ b/cleverhans_v3.1.0/docs/_modules/cleverhans/attacks/semantic.html @@ -0,0 +1,130 @@ + + + + + + + + cleverhans.attacks.semantic — CleverHans documentation + + + + + + + + + + + + + + + + + +
+
+
+ + +
+ +

Source code for cleverhans.attacks.semantic

+"""Semantic adversarial examples
+"""
+
+from cleverhans.attacks.attack import Attack
+
+
+
[docs]class Semantic(Attack): + """ + Semantic adversarial examples + + https://arxiv.org/abs/1703.06857 + + Note: data must either be centered (so that the negative image can be + made by simple negation) or must be in the interval [-1, 1] + + :param model: cleverhans.model.Model + :param center: bool + If True, assumes data has 0 mean so the negative image is just negation. + If False, assumes data is in the interval [0, max_val] + :param max_val: float + Maximum value allowed in the input data + :param sess: optional tf.Session + :param dtypestr: dtype of data + :param kwargs: passed through to the super constructor + """ + + def __init__(self, model, center, max_val=1., sess=None, dtypestr='float32', + **kwargs): + super(Semantic, self).__init__(model, sess, dtypestr, **kwargs) + self.center = center + self.max_val = max_val + if hasattr(model, 'dataset_factory'): + if 'center' in model.dataset_factory.kwargs: + assert center == model.dataset_factory.kwargs['center'] + +
[docs] def generate(self, x, **kwargs): + if self.center: + return -x + return self.max_val - x
+
+ +
+ +
+
+ +
+
+ + + + \ No newline at end of file diff --git a/cleverhans_v3.1.0/docs/_modules/cleverhans/attacks/sparse_l1_descent.html b/cleverhans_v3.1.0/docs/_modules/cleverhans/attacks/sparse_l1_descent.html new file mode 100644 index 000000000..385e1aa24 --- /dev/null +++ b/cleverhans_v3.1.0/docs/_modules/cleverhans/attacks/sparse_l1_descent.html @@ -0,0 +1,437 @@ + + + + + + + + cleverhans.attacks.sparse_l1_descent — CleverHans documentation + + + + + + + + + + + + + + + + + +
+
+
+ + +
+ +

Source code for cleverhans.attacks.sparse_l1_descent

+"""
+The SparseL1Descent attack.
+"""
+
+import warnings
+from distutils.version import LooseVersion
+
+import tensorflow as tf
+
+from cleverhans.attacks.attack import Attack
+from cleverhans import utils_tf
+from cleverhans.utils_tf import clip_eta, random_lp_vector
+from cleverhans.compat import reduce_max, reduce_sum, \
+  softmax_cross_entropy_with_logits
+
+
+
[docs]class SparseL1Descent(Attack): + """ + This class implements a variant of Projected Gradient Descent for the l1-norm + (Tramer and Boneh 2019). The l1-norm case is more tricky than the l-inf and l2 + cases covered by the ProjectedGradientDescent class, because the steepest + descent direction for the l1-norm is too sparse (it updates a single + coordinate in the adversarial perturbation in each step). This attack has an + additional parameter that controls the sparsity of the update step. For + moderately sparse update steps, the attack vastly outperforms Projected + Steepest Descent and is competitive with other attacks targeted at the l1-norm + such as the ElasticNetMethod attack (which is much more computationally + expensive). + Paper link (Tramer and Boneh 2019): https://arxiv.org/pdf/1904.13000.pdf + + :param model: cleverhans.model.Model + :param sess: optional tf.Session + :param dtypestr: dtype of the data + :param kwargs: passed through to super constructor + """ + + def __init__(self, model, sess=None, dtypestr='float32', **kwargs): + """ + Create a SparseL1Descent instance. + Note: the model parameter should be an instance of the + cleverhans.model.Model abstraction provided by CleverHans. + """ + + super(SparseL1Descent, self).__init__(model, sess=sess, + dtypestr=dtypestr, **kwargs) + self.feedable_kwargs = ('eps', 'eps_iter', 'y', 'y_target', 'clip_min', + 'clip_max', 'grad_sparsity') + self.structural_kwargs = ['nb_iter', 'rand_init', 'clip_grad', + 'sanity_checks'] + +
[docs] def generate(self, x, **kwargs): + """ + Generate symbolic graph for adversarial examples and return. + + :param x: The model's symbolic inputs. + :param kwargs: See `parse_params` + """ + # Parse and save attack-specific parameters + assert self.parse_params(**kwargs) + + asserts = [] + + # If a data range was specified, check that the input was in that range + if self.clip_min is not None: + asserts.append(utils_tf.assert_greater_equal(x, + tf.cast(self.clip_min, + x.dtype))) + + if self.clip_max is not None: + asserts.append(utils_tf.assert_less_equal(x, + tf.cast(self.clip_max, + x.dtype))) + + # Initialize loop variables + if self.rand_init: + eta = random_lp_vector(tf.shape(x), ord=1, + eps=tf.cast(self.eps, x.dtype), dtype=x.dtype) + else: + eta = tf.zeros(tf.shape(x)) + + # Clip eta + eta = clip_eta(eta, ord=1, eps=self.eps) + adv_x = x + eta + if self.clip_min is not None or self.clip_max is not None: + adv_x = utils_tf.clip_by_value(adv_x, self.clip_min, self.clip_max) + + if self.y_target is not None: + y = self.y_target + targeted = True + elif self.y is not None: + y = self.y + targeted = False + else: + model_preds = self.model.get_probs(x) + preds_max = tf.reduce_max(model_preds, 1, keepdims=True) + y = tf.to_float(tf.equal(model_preds, preds_max)) + y = tf.stop_gradient(y) + targeted = False + del model_preds + + y_kwarg = 'y_target' if targeted else 'y' + + def cond(i, _): + """Iterate until requested number of iterations is completed""" + return tf.less(i, self.nb_iter) + + def body(i, adv_x): + """Do a projected gradient step""" + + labels, _ = self.get_or_guess_labels(adv_x, {y_kwarg: y}) + logits = self.model.get_logits(adv_x) + + adv_x = sparse_l1_descent(adv_x, + logits, + y=labels, + eps=self.eps_iter, + q=self.grad_sparsity, + clip_min=self.clip_min, + clip_max=self.clip_max, + clip_grad=self.clip_grad, + targeted=(self.y_target is not None), + sanity_checks=self.sanity_checks) + + # Clipping perturbation eta to the l1-ball + eta = adv_x - x + eta = clip_eta(eta, ord=1, eps=self.eps) + adv_x = x + eta + + # Redo the clipping. + # Subtracting and re-adding eta can add some small numerical error. + if self.clip_min is not None or self.clip_max is not None: + adv_x = utils_tf.clip_by_value(adv_x, self.clip_min, self.clip_max) + + return i + 1, adv_x + + _, adv_x = tf.while_loop(cond, body, (tf.zeros([]), adv_x), back_prop=True, + maximum_iterations=self.nb_iter) + + # Asserts run only on CPU. + # When multi-GPU eval code tries to force all PGD ops onto GPU, this + # can cause an error. + common_dtype = tf.float32 + asserts.append(utils_tf.assert_less_equal(tf.cast(self.eps_iter, + dtype=common_dtype), + tf.cast(self.eps, dtype=common_dtype))) + + if self.sanity_checks: + with tf.control_dependencies(asserts): + adv_x = tf.identity(adv_x) + + return adv_x
+ +
[docs] def parse_params(self, + eps=10.0, + eps_iter=1.0, + nb_iter=20, + y=None, + clip_min=None, + clip_max=None, + y_target=None, + rand_init=False, + clip_grad=False, + grad_sparsity=99, + sanity_checks=True, + **kwargs): + """ + Take in a dictionary of parameters and applies attack-specific checks + before saving them as attributes. + + Attack-specific parameters: + + :param eps: (optional float) maximum distortion of adversarial example + compared to original input + :param eps_iter: (optional float) step size for each attack iteration + :param nb_iter: (optional int) Number of attack iterations. + :param y: (optional) A tensor with the true labels. + :param y_target: (optional) A tensor with the labels to target. Leave + y_target=None if y is also set. Labels should be + one-hot-encoded. + :param clip_min: (optional float) Minimum input component value + :param clip_max: (optional float) Maximum input component value + :param clip_grad: (optional bool) Ignore gradient components + at positions where the input is already at the boundary + of the domain, and the update step will get clipped out. + :param grad_sparsity (optional) Relative sparsity of the gradient update + step, in percent. Only gradient values larger + than this percentile are retained. This parameter can + be a scalar, or a vector of the same length as the + input batch dimension. + :param sanity_checks: bool Insert tf asserts checking values + (Some tests need to run with no sanity checks because the + tests intentionally configure the attack strangely) + """ + + # Save attack-specific parameters + self.eps = eps + self.rand_init = rand_init + self.eps_iter = eps_iter + self.nb_iter = nb_iter + self.y = y + self.y_target = y_target + self.clip_min = clip_min + self.clip_max = clip_max + self.clip_grad = clip_grad + self.grad_sparsity = grad_sparsity + + if isinstance(eps, float) and isinstance(eps_iter, float): + # If these are both known at compile time, we can check before anything + # is run. If they are tf, we can't check them yet. + assert eps_iter <= eps, (eps_iter, eps) + + if self.y is not None and self.y_target is not None: + raise ValueError("Must not set both y and y_target") + + if self.clip_grad and (self.clip_min is None or self.clip_max is None): + raise ValueError("Must set clip_min and clip_max if clip_grad is set") + + # The grad_sparsity argument governs the sparsity of the gradient + # update. It indicates the percentile value above which gradient entries + # are retained. It can be specified as a scalar or as a 1-dimensional + # vector of the same size as the input's batch dimension. + if isinstance(self.grad_sparsity, int) or \ + isinstance(self.grad_sparsity, float): + if not 0 < self.grad_sparsity < 100: + raise ValueError("grad_sparsity should be in (0, 100)") + else: + self.grad_sparsity = tf.convert_to_tensor(self.grad_sparsity) + if len(self.grad_sparsity.shape) > 1: + raise ValueError("grad_sparsity should either be a scalar or a vector") + + + self.sanity_checks = sanity_checks + + if len(kwargs.keys()) > 0: + warnings.warn("kwargs is unused and will be removed on or after " + "2019-04-26.") + + return True
+ + +def sparse_l1_descent(x, + logits, + y=None, + eps=1.0, + q=99, + clip_min=None, + clip_max=None, + clip_grad=False, + targeted=False, + sanity_checks=True): + """ + TensorFlow implementation of the Dense L1 Descent Method. + :param x: the input placeholder + :param logits: output of model.get_logits + :param y: (optional) A placeholder for the true labels. If targeted + is true, then provide the target label. Otherwise, only provide + this parameter if you'd like to use true labels when crafting + adversarial samples. Otherwise, model predictions are used as + labels to avoid the "label leaking" effect (explained in this + paper: https://arxiv.org/abs/1611.01236). Default is None. + Labels should be one-hot-encoded. + :param eps: the epsilon (input variation parameter) + :param q: the percentile above which gradient values are retained. Either a + scalar or a vector of same length as the input batch dimension. + :param clip_min: Minimum float value for adversarial example components + :param clip_max: Maximum float value for adversarial example components + :param clip_grad: (optional bool) Ignore gradient components + at positions where the input is already at the boundary + of the domain, and the update step will get clipped out. + :param targeted: Is the attack targeted or untargeted? Untargeted, the + default, will try to make the label incorrect. Targeted + will instead try to move in the direction of being more + like y. + :return: a tensor for the adversarial example + """ + + asserts = [] + + # If a data range was specified, check that the input was in that range + if clip_min is not None: + asserts.append(utils_tf.assert_greater_equal( + x, tf.cast(clip_min, x.dtype))) + + if clip_max is not None: + asserts.append(utils_tf.assert_less_equal(x, tf.cast(clip_max, x.dtype))) + + # Make sure the caller has not passed probs by accident + assert logits.op.type != 'Softmax' + + if y is None: + # Using model predictions as ground truth to avoid label leaking + preds_max = reduce_max(logits, 1, keepdims=True) + y = tf.to_float(tf.equal(logits, preds_max)) + y = tf.stop_gradient(y) + y = y / reduce_sum(y, 1, keepdims=True) + + # Compute loss + loss = softmax_cross_entropy_with_logits(labels=y, logits=logits) + if targeted: + loss = -loss + + # Define gradient of loss wrt input + grad, = tf.gradients(loss, x) + + if clip_grad: + grad = utils_tf.zero_out_clipped_grads(grad, x, clip_min, clip_max) + + red_ind = list(range(1, len(grad.get_shape()))) + dim = tf.reduce_prod(tf.shape(x)[1:]) + + abs_grad = tf.reshape(tf.abs(grad), (-1, dim)) + + # if q is a scalar, broadcast it to a vector of same length as the batch dim + q = tf.cast(tf.broadcast_to(q, tf.shape(x)[0:1]), tf.float32) + k = tf.cast(tf.floor(q / 100 * tf.cast(dim, tf.float32)), tf.int32) + + # `tf.sort` is much faster than `tf.contrib.distributions.percentile`. + # For TF <= 1.12, use `tf.nn.top_k` as `tf.sort` is not implemented. + if LooseVersion(tf.__version__) <= LooseVersion('1.12.0'): + # `tf.sort` is only available in TF 1.13 onwards + sorted_grad = -tf.nn.top_k(-abs_grad, k=dim, sorted=True)[0] + else: + sorted_grad = tf.sort(abs_grad, axis=-1) + + idx = tf.stack((tf.range(tf.shape(abs_grad)[0]), k), -1) + percentiles = tf.gather_nd(sorted_grad, idx) + tied_for_max = tf.greater_equal(abs_grad, tf.expand_dims(percentiles, -1)) + tied_for_max = tf.reshape(tf.cast(tied_for_max, x.dtype), tf.shape(grad)) + num_ties = tf.reduce_sum(tied_for_max, red_ind, keepdims=True) + + optimal_perturbation = tf.sign(grad) * tied_for_max / num_ties + + # Add perturbation to original example to obtain adversarial example + adv_x = x + utils_tf.mul(eps, optimal_perturbation) + + # If clipping is needed, reset all values outside of [clip_min, clip_max] + if (clip_min is not None) or (clip_max is not None): + # We don't currently support one-sided clipping + assert clip_min is not None and clip_max is not None + adv_x = utils_tf.clip_by_value(adv_x, clip_min, clip_max) + + if sanity_checks: + with tf.control_dependencies(asserts): + adv_x = tf.identity(adv_x) + + return adv_x +
+ +
+ +
+
+ +
+
+ + + + \ No newline at end of file diff --git a/cleverhans_v3.1.0/docs/_modules/cleverhans/attacks/spatial_transformation_method.html b/cleverhans_v3.1.0/docs/_modules/cleverhans/attacks/spatial_transformation_method.html new file mode 100644 index 000000000..7ffc53308 --- /dev/null +++ b/cleverhans_v3.1.0/docs/_modules/cleverhans/attacks/spatial_transformation_method.html @@ -0,0 +1,197 @@ + + + + + + + + cleverhans.attacks.spatial_transformation_method — CleverHans documentation + + + + + + + + + + + + + + + + + +
+
+
+ + +
+ +

Source code for cleverhans.attacks.spatial_transformation_method

+"""The SpatialTransformationMethod attack
+"""
+import warnings
+
+from cleverhans.attacks.attack import Attack
+
+
+
[docs]class SpatialTransformationMethod(Attack): + """ + Spatial transformation attack + """ + + def __init__(self, model, sess=None, dtypestr='float32', **kwargs): + """ + Create a SpatialTransformationMethod instance. + Note: the model parameter should be an instance of the + cleverhans.model.Model abstraction provided by CleverHans. + + :param model: cleverhans.model.Model + :param sess: optional tf.Session + :param dtypestr: dtype of the data + :param kwargs: passed through to super constructor + """ + + super(SpatialTransformationMethod, self).__init__( + model, sess, dtypestr, **kwargs) + self.feedable_kwargs = ('n_samples', 'dx_min', 'dx_max', 'n_dxs', 'dy_min', + 'dy_max', 'n_dys', 'angle_min', 'angle_max', + 'n_angles', 'black_border_size') + +
[docs] def generate(self, x, **kwargs): + """ + Generate symbolic graph for adversarial examples and return. + :param x: The model's symbolic inputs. + :param kwargs: See `parse_params` + """ + # Parse and save attack-specific parameters + assert self.parse_params(**kwargs) + + from cleverhans.attacks_tf import spm + + labels, _ = self.get_or_guess_labels(x, kwargs) + + return spm( + x, + self.model, + y=labels, + n_samples=self.n_samples, + dx_min=self.dx_min, dx_max=self.dx_max, n_dxs=self.n_dxs, + dy_min=self.dy_min, dy_max=self.dy_max, n_dys=self.n_dys, + angle_min=self.angle_min, angle_max=self.angle_max, + n_angles=self.n_angles, black_border_size=self.black_border_size)
+ +
[docs] def parse_params(self, + n_samples=None, + dx_min=-0.1, + dx_max=0.1, + n_dxs=2, + dy_min=-0.1, + dy_max=0.1, + n_dys=2, + angle_min=-30, + angle_max=30, + n_angles=6, + black_border_size=0, + **kwargs): + """ + Take in a dictionary of parameters and applies attack-specific checks + before saving them as attributes. + :param n_samples: (optional) The number of transformations sampled to + construct the attack. Set it to None to run + full grid attack. + :param dx_min: (optional float) Minimum translation ratio along x-axis. + :param dx_max: (optional float) Maximum translation ratio along x-axis. + :param n_dxs: (optional int) Number of discretized translation ratios + along x-axis. + :param dy_min: (optional float) Minimum translation ratio along y-axis. + :param dy_max: (optional float) Maximum translation ratio along y-axis. + :param n_dys: (optional int) Number of discretized translation ratios + along y-axis. + :param angle_min: (optional float) Largest counter-clockwise rotation + angle. + :param angle_max: (optional float) Largest clockwise rotation angle. + :param n_angles: (optional int) Number of discretized angles. + :param black_border_size: (optional int) size of the black border in pixels. + """ + self.n_samples = n_samples + self.dx_min = dx_min + self.dx_max = dx_max + self.n_dxs = n_dxs + self.dy_min = dy_min + self.dy_max = dy_max + self.n_dys = n_dys + self.angle_min = angle_min + self.angle_max = angle_max + self.n_angles = n_angles + self.black_border_size = black_border_size + + if self.dx_min < -1 or self.dy_min < -1 or \ + self.dx_max > 1 or self.dy_max > 1: + raise ValueError("The value of translation must be bounded " + "within [-1, 1]") + if len(kwargs.keys()) > 0: + warnings.warn("kwargs is unused and will be removed on or after " + "2019-04-26.") + return True
+
+ +
+ +
+
+ +
+
+ + + + \ No newline at end of file diff --git a/cleverhans_v3.1.0/docs/_modules/cleverhans/attacks/spsa.html b/cleverhans_v3.1.0/docs/_modules/cleverhans/attacks/spsa.html new file mode 100644 index 000000000..68b128dde --- /dev/null +++ b/cleverhans_v3.1.0/docs/_modules/cleverhans/attacks/spsa.html @@ -0,0 +1,850 @@ + + + + + + + + cleverhans.attacks.spsa — CleverHans documentation + + + + + + + + + + + + + + + + + +
+
+
+ + +
+ +

Source code for cleverhans.attacks.spsa

+"""The SPSA attack
+"""
+# pylint: disable=missing-docstring
+import warnings
+
+import numpy as np
+from six.moves import xrange
+import tensorflow as tf
+# import tensorflow_addons as tfa
+
+from cleverhans.attacks.attack import Attack
+from cleverhans.compat import reduce_mean, reduce_sum, reduce_max
+from cleverhans.model import Model
+from cleverhans import utils_tf
+
+tf_dtype = tf.as_dtype('float32')
+
+
+
[docs]class SPSA(Attack): + """ + This implements the SPSA adversary, as in https://arxiv.org/abs/1802.05666 + (Uesato et al. 2018). SPSA is a gradient-free optimization method, which + is useful when the model is non-differentiable, or more generally, the + gradients do not point in useful directions. + + :param model: cleverhans.model.Model + :param sess: optional tf.Session + :param dtypestr: dtype of the data + :param kwargs: passed through to super constructor + """ + + DEFAULT_SPSA_SAMPLES = 128 + DEFAULT_SPSA_ITERS = 1 + DEFAULT_DELTA = 0.01 + DEFAULT_LEARNING_RATE = 0.01 + + def __init__(self, model, sess=None, dtypestr='float32', **kwargs): + super(SPSA, self).__init__(model, sess, dtypestr, **kwargs) + + self.feedable_kwargs = ('eps', 'clip_min', 'clip_max', 'y', 'y_target') + self.structural_kwargs = [ + 'nb_iter', + 'spsa_samples', + 'spsa_iters', + 'early_stop_loss_threshold', + 'is_debug', + 'is_targeted', + ] + + assert isinstance(self.model, Model) + +
[docs] def generate(self, + x, + y=None, + y_target=None, + eps=None, + clip_min=None, + clip_max=None, + nb_iter=None, + is_targeted=None, + early_stop_loss_threshold=None, + learning_rate=DEFAULT_LEARNING_RATE, + delta=DEFAULT_DELTA, + spsa_samples=DEFAULT_SPSA_SAMPLES, + batch_size=None, + spsa_iters=DEFAULT_SPSA_ITERS, + is_debug=False, + epsilon=None, + num_steps=None): + """ + Generate symbolic graph for adversarial examples. + + :param x: The model's symbolic inputs. Must be a batch of size 1. + :param y: A Tensor or None. The index of the correct label. + :param y_target: A Tensor or None. The index of the target label in a + targeted attack. + :param eps: The size of the maximum perturbation, measured in the + L-infinity norm. + :param clip_min: If specified, the minimum input value + :param clip_max: If specified, the maximum input value + :param nb_iter: The number of optimization steps. + :param early_stop_loss_threshold: A float or None. If specified, the + attack will end as soon as the loss + is below `early_stop_loss_threshold`. + :param learning_rate: Learning rate of ADAM optimizer. + :param delta: Perturbation size used for SPSA approximation. + :param spsa_samples: Number of inputs to evaluate at a single time. + The true batch size (the number of evaluated + inputs for each update) is `spsa_samples * + spsa_iters` + :param batch_size: Deprecated param that is an alias for spsa_samples + :param spsa_iters: Number of model evaluations before performing an + update, where each evaluation is on `spsa_samples` + different inputs. + :param is_debug: If True, print the adversarial loss after each update. + :param epsilon: Deprecated alias for `eps` + :param num_steps: Deprecated alias for `nb_iter`. + :param is_targeted: Deprecated argument. Ignored. + """ + + if epsilon is not None: + if eps is not None: + raise ValueError("Should not specify both eps and its deprecated " + "alias, epsilon") + warnings.warn("`epsilon` is deprecated. Switch to `eps`. `epsilon` may " + "be removed on or after 2019-04-15.") + eps = epsilon + del epsilon + + if num_steps is not None: + if nb_iter is not None: + raise ValueError("Should not specify both nb_iter and its deprecated " + "alias, num_steps") + warnings.warn("`num_steps` is deprecated. Switch to `nb_iter`. " + "`num_steps` may be removed on or after 2019-04-15.") + nb_iter = num_steps + del num_steps + assert nb_iter is not None + + if (y is not None) + (y_target is not None) != 1: + raise ValueError("Must specify exactly one of y (untargeted attack, " + "cause the input not to be classified as this true " + "label) and y_target (targeted attack, cause the " + "input to be classified as this target label).") + + if is_targeted is not None: + warnings.warn("`is_targeted` is deprecated. Simply do not specify it." + " It may become an error to specify it on or after " + "2019-04-15.") + assert is_targeted == y_target is not None + + is_targeted = y_target is not None + + if x.get_shape().as_list()[0] is None: + check_batch = utils_tf.assert_equal(tf.shape(x)[0], 1) + with tf.control_dependencies([check_batch]): + x = tf.identity(x) + elif x.get_shape().as_list()[0] != 1: + raise ValueError("For SPSA, input tensor x must have batch_size of 1.") + + if batch_size is not None: + warnings.warn( + 'The "batch_size" argument to SPSA is deprecated, and will ' + 'be removed on 2019-03-17. ' + 'Please use spsa_samples instead.') + spsa_samples = batch_size + + optimizer = SPSAAdam( + lr=learning_rate, + delta=delta, + num_samples=spsa_samples, + num_iters=spsa_iters) + + def loss_fn(x, label): + """ + Margin logit loss, with correct sign for targeted vs untargeted loss. + """ + logits = self.model.get_logits(x) + loss_multiplier = 1 if is_targeted else -1 + return loss_multiplier * margin_logit_loss( + logits, label, + nb_classes=self.model.nb_classes or logits.get_shape()[-1]) + + y_attack = y_target if is_targeted else y + adv_x = projected_optimization( + loss_fn, + x, + y_attack, + eps, + num_steps=nb_iter, + optimizer=optimizer, + early_stop_loss_threshold=early_stop_loss_threshold, + is_debug=is_debug, + clip_min=clip_min, + clip_max=clip_max + ) + return adv_x
+ +
[docs] def generate_np(self, x_val, **kwargs): + if "epsilon" in kwargs: + warnings.warn("Using deprecated argument: see `generate`") + assert "eps" not in kwargs + kwargs["eps"] = kwargs["epsilon"] + del kwargs["epsilon"] + assert "eps" in kwargs + + if "num_steps" in kwargs: + warnings.warn("Using deprecated argument: see `generate`") + assert "nb_iter" not in kwargs + kwargs["nb_iter"] = kwargs["num_steps"] + del kwargs["num_steps"] + + if 'y' in kwargs and kwargs['y'] is not None: + assert kwargs['y'].dtype in [np.int32, np.int64] + if 'y_target' in kwargs and kwargs['y_target'] is not None: + assert kwargs['y_target'].dtype in [np.int32, np.int64] + + # Call self.generate() sequentially for each image in the batch + x_adv = [] + batch_size = x_val.shape[0] + y = kwargs.pop('y', [None] * batch_size) + assert len(x_val) == len(y), '# of images and labels should match' + for x_single, y_single in zip(x_val, y): + x = np.expand_dims(x_single, axis=0) + adv_img = super(SPSA, self).generate_np(x, y=y_single, **kwargs) + x_adv.append(adv_img) + return np.concatenate(x_adv, axis=0)
+ + +def _project_perturbation(perturbation, epsilon, input_image, clip_min=None, + clip_max=None): + """Project `perturbation` onto L-infinity ball of radius `epsilon`. + Also project into hypercube such that the resulting adversarial example + is between clip_min and clip_max, if applicable. + """ + + if clip_min is None or clip_max is None: + raise NotImplementedError("_project_perturbation currently has clipping " + "hard-coded in.") + + # Ensure inputs are in the correct range + with tf.control_dependencies([ + utils_tf.assert_less_equal(input_image, + tf.cast(clip_max, input_image.dtype)), + utils_tf.assert_greater_equal(input_image, + tf.cast(clip_min, input_image.dtype)) + ]): + clipped_perturbation = utils_tf.clip_by_value( + perturbation, -epsilon, epsilon) + new_image = utils_tf.clip_by_value( + input_image + clipped_perturbation, clip_min, clip_max) + return new_image - input_image + + +class TensorOptimizer(object): + """Optimizer for Tensors rather than tf.Variables. + + TensorOptimizers implement optimizers where the values being optimized + are ordinary Tensors, rather than Variables. TF Variables can have strange + behaviors when being assigned multiple times within a single sess.run() + call, particularly in Distributed TF, so this avoids thinking about those + issues. These are helper classes for the `projected_optimization` + method. Apart from not using Variables, they follow an interface very + similar to tf.Optimizer. + """ + + def _compute_gradients(self, loss_fn, x, unused_optim_state): + """Compute a new value of `x` to minimize `loss_fn`. + + Args: + loss_fn: a callable that takes `x`, a batch of images, and returns + a batch of loss values. `x` will be optimized to minimize + `loss_fn(x)`. + x: A list of Tensors, the values to be updated. This is analogous + to the `var_list` argument in standard TF Optimizer. + unused_optim_state: A (possibly nested) dict, containing any state + info needed for the optimizer. + + Returns: + new_x: A list of Tensors, the same length as `x`, which are updated + new_optim_state: A dict, with the same structure as `optim_state`, + which have been updated. + """ + + # Assumes `x` is a list, + # and contains a tensor representing a batch of images + assert len(x) == 1 and isinstance(x, list), \ + 'x should be a list and contain only one image tensor' + x = x[0] + loss = reduce_mean(loss_fn(x), axis=0) + return tf.gradients(loss, x) + + def _apply_gradients(self, grads, x, optim_state): + """ + Given a gradient, make one optimization step. + + :param grads: list of tensors, same length as `x`, containing the corresponding gradients + :param x: list of tensors to update + :param optim_state: dict + + Returns: + new_x: list of tensors, updated version of `x` + new_optim_state: dict, updated version of `optim_state` + """ + raise NotImplementedError( + "_apply_gradients should be defined in each subclass") + + def minimize(self, loss_fn, x, optim_state): + """ + Analogous to tf.Optimizer.minimize + + :param loss_fn: tf Tensor, representing the loss to minimize + :param x: list of Tensor, analogous to tf.Optimizer's var_list + :param optim_state: A possibly nested dict, containing any optimizer state. + + Returns: + new_x: list of Tensor, updated version of `x` + new_optim_state: dict, updated version of `optim_state` + """ + grads = self._compute_gradients(loss_fn, x, optim_state) + return self._apply_gradients(grads, x, optim_state) + + def init_state(self, x): + """Returns the initial state of the optimizer. + + Args: + x: A list of Tensors, which will be optimized. + + Returns: + A dictionary, representing the initial state of the optimizer. + """ + raise NotImplementedError( + "init_state should be defined in each subclass") + + +class TensorGradientDescent(TensorOptimizer): + """Vanilla Gradient Descent TensorOptimizer.""" + + def __init__(self, lr): + self._lr = lr + + def init_state(self, x): + return {} + + def _apply_gradients(self, grads, x, optim_state): + new_x = [None] * len(x) + for i in xrange(len(x)): + new_x[i] = x[i] - self._lr * grads[i] + return new_x, optim_state + + +class TensorAdam(TensorOptimizer): + """The Adam optimizer defined in https://arxiv.org/abs/1412.6980.""" + + def __init__(self, lr=0.001, beta1=0.9, beta2=0.999, epsilon=1e-9): + self._lr = lr + self._beta1 = beta1 + self._beta2 = beta2 + self._epsilon = epsilon + + def init_state(self, x): + """ + Initialize t, m, and u + """ + optim_state = {} + optim_state["t"] = 0. + optim_state["m"] = [tf.zeros_like(v) for v in x] + optim_state["u"] = [tf.zeros_like(v) for v in x] + return optim_state + + def _apply_gradients(self, grads, x, optim_state): + """Refer to parent class documentation.""" + new_x = [None] * len(x) + new_optim_state = { + "t": optim_state["t"] + 1., + "m": [None] * len(x), + "u": [None] * len(x) + } + t = new_optim_state["t"] + for i in xrange(len(x)): + g = grads[i] + m_old = optim_state["m"][i] + u_old = optim_state["u"][i] + new_optim_state["m"][i] = ( + self._beta1 * m_old + (1. - self._beta1) * g) + new_optim_state["u"][i] = ( + self._beta2 * u_old + (1. - self._beta2) * g * g) + m_hat = new_optim_state["m"][i] / (1. - tf.pow(self._beta1, t)) + u_hat = new_optim_state["u"][i] / (1. - tf.pow(self._beta2, t)) + new_x[i] = ( + x[i] - self._lr * m_hat / (tf.sqrt(u_hat) + self._epsilon)) + return new_x, new_optim_state + + +class SPSAAdam(TensorAdam): + """Optimizer for gradient-free attacks in https://arxiv.org/abs/1802.05666. + + Gradients estimates are computed using Simultaneous Perturbation Stochastic + Approximation (SPSA), combined with the ADAM update rule. + """ + + def __init__(self, + lr=0.01, + delta=0.01, + num_samples=128, + num_iters=1, + compare_to_analytic_grad=False): + super(SPSAAdam, self).__init__(lr=lr) + assert num_samples % 2 == 0, "number of samples must be even" + self._delta = delta + self._num_samples = num_samples // 2 # Since we mirror +/- delta later + self._num_iters = num_iters + self._compare_to_analytic_grad = compare_to_analytic_grad + + def _get_delta(self, x, delta): + x_shape = x.get_shape().as_list() + delta_x = delta * tf.sign( + tf.random_uniform( + [self._num_samples] + x_shape[1:], + minval=-1., + maxval=1., + dtype=tf_dtype)) + return delta_x + + def _compute_gradients(self, loss_fn, x, unused_optim_state): + """Compute gradient estimates using SPSA.""" + # Assumes `x` is a list, containing a [1, H, W, C] image + # If static batch dimension is None, tf.reshape to batch size 1 + # so that static shape can be inferred + assert len(x) == 1 + static_x_shape = x[0].get_shape().as_list() + if static_x_shape[0] is None: + x[0] = tf.reshape(x[0], [1] + static_x_shape[1:]) + assert x[0].get_shape().as_list()[0] == 1 + x = x[0] + x_shape = x.get_shape().as_list() + + def body(i, grad_array): + delta = self._delta + delta_x = self._get_delta(x, delta) + delta_x = tf.concat([delta_x, -delta_x], axis=0) + loss_vals = tf.reshape( + loss_fn(x + delta_x), + [2 * self._num_samples] + [1] * (len(x_shape) - 1)) + avg_grad = reduce_mean(loss_vals * delta_x, axis=0) / delta + avg_grad = tf.expand_dims(avg_grad, axis=0) + new_grad_array = grad_array.write(i, avg_grad) + return i + 1, new_grad_array + + def cond(i, _): + return i < self._num_iters + + _, all_grads = tf.while_loop( + cond, + body, + loop_vars=[ + 0, tf.TensorArray(size=self._num_iters, dtype=tf_dtype) + ], + back_prop=False, + parallel_iterations=1) + avg_grad = reduce_sum(all_grads.stack(), axis=0) + return [avg_grad] + + +def margin_logit_loss(model_logits, label, nb_classes=10, num_classes=None): + """Computes difference between logit for `label` and next highest logit. + + The loss is high when `label` is unlikely (targeted by default). + This follows the same interface as `loss_fn` for TensorOptimizer and + projected_optimization, i.e. it returns a batch of loss values. + """ + if num_classes is not None: + warnings.warn("`num_classes` is depreciated. Switch to `nb_classes`." + " `num_classes` may be removed on or after 2019-04-23.") + nb_classes = num_classes + del num_classes + if 'int' in str(label.dtype): + logit_mask = tf.one_hot(label, depth=nb_classes, axis=-1) + else: + logit_mask = label + if 'int' in str(logit_mask.dtype): + logit_mask = tf.to_float(logit_mask) + try: + label_logits = reduce_sum(logit_mask * model_logits, axis=-1) + except TypeError: + raise TypeError("Could not take row-wise dot product between " + "logit mask, of dtype " + str(logit_mask.dtype) + + " and model_logits, of dtype " + + str(model_logits.dtype)) + logits_with_target_label_neg_inf = model_logits - logit_mask * 99999 + highest_nonlabel_logits = reduce_max( + logits_with_target_label_neg_inf, axis=-1) + loss = highest_nonlabel_logits - label_logits + return loss + + +def _apply_black_border(x, border_size): + orig_height = x.get_shape().as_list()[1] + orig_width = x.get_shape().as_list()[2] + x = tf.image.resize_images(x, (orig_width - 2*border_size, + orig_height - 2*border_size)) + + return tf.pad(x, [[0, 0], + [border_size, border_size], + [border_size, border_size], + [0, 0]], 'CONSTANT') + + +def _apply_transformation(inputs): + x, trans = inputs[0], inputs[1] + dx, dy, angle = trans[0], trans[1], trans[2] + height = x.get_shape().as_list()[1] + width = x.get_shape().as_list()[2] + + # Pad the image to prevent two-step rotation / translation from truncating + # corners + max_dist_from_center = np.sqrt(height**2+width**2) / 2 + min_edge_from_center = float(np.min([height, width])) / 2 + padding = np.ceil(max_dist_from_center - + min_edge_from_center).astype(np.int32) + x = tf.pad(x, [[0, 0], + [padding, padding], + [padding, padding], + [0, 0]], + 'CONSTANT') + + # Apply rotation + angle *= np.pi / 180 + x = tfa.image.rotate(x, angle, interpolation='BILINEAR') + + # Apply translation + dx_in_px = -dx * height + dy_in_px = -dy * width + translation = tf.convert_to_tensor([dx_in_px, dy_in_px]) + + try: + x = tfa.image.translate(x, translation, interpolation='BILINEAR') + except AttributeError as e: + print("WARNING: SpatialAttack requires tf 1.6 or higher") + raise e + x = tfa.image.translate(x, translation, interpolation='BILINEAR') + return tf.image.resize_image_with_crop_or_pad(x, height, width) + + +def spm(x, model, y=None, n_samples=None, dx_min=-0.1, + dx_max=0.1, n_dxs=5, dy_min=-0.1, dy_max=0.1, n_dys=5, + angle_min=-30, angle_max=30, n_angles=31, black_border_size=0): + """ + TensorFlow implementation of the Spatial Transformation Method. + :return: a tensor for the adversarial example + """ + if y is None: + preds = model.get_probs(x) + # Using model predictions as ground truth to avoid label leaking + preds_max = reduce_max(preds, 1, keepdims=True) + y = tf.to_float(tf.equal(preds, preds_max)) + y = tf.stop_gradient(y) + del preds + y = y / reduce_sum(y, 1, keepdims=True) + + # Define the range of transformations + dxs = np.linspace(dx_min, dx_max, n_dxs) + dys = np.linspace(dy_min, dy_max, n_dys) + angles = np.linspace(angle_min, angle_max, n_angles) + + if n_samples is None: + import itertools + transforms = list(itertools.product(*[dxs, dys, angles])) + else: + sampled_dxs = np.random.choice(dxs, n_samples) + sampled_dys = np.random.choice(dys, n_samples) + sampled_angles = np.random.choice(angles, n_samples) + transforms = zip(sampled_dxs, sampled_dys, sampled_angles) + transformed_ims = parallel_apply_transformations( + x, transforms, black_border_size) + + def _compute_xent(x): + preds = model.get_logits(x) + return tf.nn.softmax_cross_entropy_with_logits_v2( + labels=y, logits=preds) + + all_xents = tf.map_fn( + _compute_xent, + transformed_ims, + parallel_iterations=1) # Must be 1 to avoid keras race conditions + + # Return the adv_x with worst accuracy + + # all_xents is n_total_samples x batch_size (SB) + all_xents = tf.stack(all_xents) # SB + + # We want the worst case sample, with the largest xent_loss + worst_sample_idx = tf.argmax(all_xents, axis=0) # B + + batch_size = tf.shape(x)[0] + keys = tf.stack([ + tf.range(batch_size, dtype=tf.int32), + tf.cast(worst_sample_idx, tf.int32) + ], axis=1) + transformed_ims_bshwc = tf.einsum('sbhwc->bshwc', transformed_ims) + after_lookup = tf.gather_nd(transformed_ims_bshwc, keys) # BHWC + return after_lookup + + +def parallel_apply_transformations(x, transforms, black_border_size=0): + """ + Apply image transformations in parallel. + :param transforms: TODO + :param black_border_size: int, size of black border to apply + Returns: + Transformed images + """ + transforms = tf.convert_to_tensor(transforms, dtype=tf.float32) + x = _apply_black_border(x, black_border_size) + + num_transforms = transforms.get_shape().as_list()[0] + im_shape = x.get_shape().as_list()[1:] + + # Pass a copy of x and a transformation to each iteration of the map_fn + # callable + tiled_x = tf.reshape( + tf.tile(x, [num_transforms, 1, 1, 1]), + [num_transforms, -1] + im_shape) + elems = [tiled_x, transforms] + transformed_ims = tf.map_fn( + _apply_transformation, + elems, + dtype=tf.float32, + parallel_iterations=1, # Must be 1 to avoid keras race conditions + ) + return transformed_ims + + +
[docs]def projected_optimization(loss_fn, + input_image, + label, + epsilon, + num_steps, + clip_min=None, + clip_max=None, + optimizer=TensorAdam(), + project_perturbation=_project_perturbation, + early_stop_loss_threshold=None, + is_debug=False): + """Generic projected optimization, generalized to work with approximate + gradients. Used for e.g. the SPSA attack. + + Args: + :param loss_fn: A callable which takes `input_image` and `label` as + arguments, and returns a batch of loss values. Same + interface as TensorOptimizer. + :param input_image: Tensor, a batch of images + :param label: Tensor, a batch of labels + :param epsilon: float, the L-infinity norm of the maximum allowable + perturbation + :param num_steps: int, the number of steps of gradient descent + :param clip_min: float, minimum pixel value + :param clip_max: float, maximum pixel value + :param optimizer: A `TensorOptimizer` object + :param project_perturbation: A function, which will be used to enforce + some constraint. It should have the same + signature as `_project_perturbation`. + :param early_stop_loss_threshold: A float or None. If specified, the attack will end if the loss is below + `early_stop_loss_threshold`. + Enabling this option can have several different effects: + - Setting the threshold to 0. guarantees that if a successful attack is found, it is returned. + This increases the attack success rate, because without early stopping the optimizer can accidentally + bounce back to a point where the attack fails. + - Early stopping can make the attack run faster because it may run for fewer steps. + - Early stopping can make the attack run slower because the loss must be calculated at each step. + The loss is not calculated as part of the normal SPSA optimization procedure. + For most reasonable choices of hyperparameters, early stopping makes the attack much faster because + it decreases the number of steps dramatically. + :param is_debug: A bool. If True, print debug info for attack progress. + + Returns: + adversarial version of `input_image`, with L-infinity difference less than + epsilon, which tries to minimize loss_fn. + + Note that this function is not intended as an Attack by itself. Rather, it + is designed as a helper function which you can use to write your own attack + methods. The method uses a tf.while_loop to optimize a loss function in + a single sess.run() call. + """ + assert num_steps is not None + if is_debug: + with tf.device("/cpu:0"): + input_image = tf.Print( + input_image, [], + "Starting PGD attack with epsilon: %s" % epsilon) + + init_perturbation = tf.random_uniform( + tf.shape(input_image), + minval=tf.cast(-epsilon, input_image.dtype), + maxval=tf.cast(epsilon, input_image.dtype), + dtype=input_image.dtype) + init_perturbation = project_perturbation(init_perturbation, epsilon, + input_image, clip_min=clip_min, + clip_max=clip_max) + init_optim_state = optimizer.init_state([init_perturbation]) + nest = tf.nest + + def loop_body(i, perturbation, flat_optim_state): + """Update perturbation to input image.""" + optim_state = nest.pack_sequence_as( + structure=init_optim_state, flat_sequence=flat_optim_state) + + def wrapped_loss_fn(x): + return loss_fn(input_image + x, label) + + new_perturbation_list, new_optim_state = optimizer.minimize( + wrapped_loss_fn, [perturbation], optim_state) + projected_perturbation = project_perturbation(new_perturbation_list[0], + epsilon, input_image, + clip_min=clip_min, + clip_max=clip_max) + + # Be careful with this bool. A value of 0. is a valid threshold but evaluates to False, so we must explicitly + # check whether the value is None. + early_stop = early_stop_loss_threshold is not None + compute_loss = is_debug or early_stop + # Don't waste time building the loss graph if we're not going to use it + if compute_loss: + # NOTE: this step is not actually redundant with the optimizer step. + # SPSA calculates the loss at randomly perturbed points but doesn't calculate the loss at the current point. + loss = reduce_mean(wrapped_loss_fn(projected_perturbation), axis=0) + + if is_debug: + with tf.device("/cpu:0"): + loss = tf.Print(loss, [loss], "Total batch loss") + + if early_stop: + i = tf.cond(tf.less(loss, early_stop_loss_threshold), + lambda: float(num_steps), lambda: i) + + return i + 1, projected_perturbation, nest.flatten(new_optim_state) + + def cond(i, *_): + return tf.less(i, num_steps) + + flat_init_optim_state = nest.flatten(init_optim_state) + _, final_perturbation, _ = tf.while_loop( + cond, + loop_body, + loop_vars=(tf.constant(0.), init_perturbation, flat_init_optim_state), + parallel_iterations=1, + back_prop=False, + maximum_iterations=num_steps) + if project_perturbation is _project_perturbation: + # TODO: this assert looks totally wrong. + # Not bothering to fix it now because it's only an assert. + # 1) Multiplying by 1.1 gives a huge margin of error. This should probably + # take the difference and allow a tolerance of 1e-6 or something like + # that. + # 2) I think it should probably check the *absolute value* of + # final_perturbation + perturbation_max = epsilon * 1.1 + check_diff = utils_tf.assert_less_equal( + final_perturbation, + tf.cast(perturbation_max, final_perturbation.dtype), + message="final_perturbation must change no pixel by more than " + "%s" % perturbation_max) + else: + # TODO: let caller pass in a check_diff function as well as + # project_perturbation + check_diff = tf.no_op() + + if clip_min is None or clip_max is None: + raise NotImplementedError("This function only supports clipping for now") + check_range = [utils_tf.assert_less_equal(input_image, + tf.cast(clip_max, + input_image.dtype)), + utils_tf.assert_greater_equal(input_image, + tf.cast(clip_min, + input_image.dtype))] + + with tf.control_dependencies([check_diff] + check_range): + adversarial_image = input_image + final_perturbation + return tf.stop_gradient(adversarial_image)
+
+ +
+ +
+
+ +
+
+ + + + \ No newline at end of file diff --git a/cleverhans_v3.1.0/docs/_modules/cleverhans/attacks/virtual_adversarial_method.html b/cleverhans_v3.1.0/docs/_modules/cleverhans/attacks/virtual_adversarial_method.html new file mode 100644 index 000000000..11859a449 --- /dev/null +++ b/cleverhans_v3.1.0/docs/_modules/cleverhans/attacks/virtual_adversarial_method.html @@ -0,0 +1,236 @@ + + + + + + + + cleverhans.attacks.virtual_adversarial_method — CleverHans documentation + + + + + + + + + + + + + + + + + +
+
+
+ + +
+ +

Source code for cleverhans.attacks.virtual_adversarial_method

+"""The VirtualAdversarialMethod attack
+
+"""
+
+import warnings
+
+import tensorflow as tf
+
+from cleverhans.attacks.attack import Attack
+from cleverhans.model import Model, CallableModelWrapper
+from cleverhans.model import wrapper_warning_logits
+from cleverhans import utils_tf
+
+tf_dtype = tf.as_dtype('float32')
+
+
[docs]class VirtualAdversarialMethod(Attack): + """ + This attack was originally proposed by Miyato et al. (2016) and was used + for virtual adversarial training. + Paper link: https://arxiv.org/abs/1507.00677 + + :param model: cleverhans.model.Model + :param sess: optional tf.Session + :param dtypestr: dtype of the data + :param kwargs: passed through to super constructor + """ + + def __init__(self, model, sess=None, dtypestr='float32', **kwargs): + """ + Note: the model parameter should be an instance of the + cleverhans.model.Model abstraction provided by CleverHans. + """ + if not isinstance(model, Model): + wrapper_warning_logits() + model = CallableModelWrapper(model, 'logits') + + super(VirtualAdversarialMethod, self).__init__(model, sess, dtypestr, + **kwargs) + + self.feedable_kwargs = ('eps', 'xi', 'clip_min', 'clip_max') + self.structural_kwargs = ['num_iterations'] + +
[docs] def generate(self, x, **kwargs): + """ + Generate symbolic graph for adversarial examples and return. + + :param x: The model's symbolic inputs. + :param kwargs: See `parse_params` + """ + # Parse and save attack-specific parameters + assert self.parse_params(**kwargs) + + return vatm( + self.model, + x, + self.model.get_logits(x), + eps=self.eps, + num_iterations=self.num_iterations, + xi=self.xi, + clip_min=self.clip_min, + clip_max=self.clip_max)
+ +
[docs] def parse_params(self, + eps=2.0, + nb_iter=None, + xi=1e-6, + clip_min=None, + clip_max=None, + num_iterations=None, + **kwargs): + """ + Take in a dictionary of parameters and applies attack-specific checks + before saving them as attributes. + + Attack-specific parameters: + + :param eps: (optional float )the epsilon (input variation parameter) + :param nb_iter: (optional) the number of iterations + Defaults to 1 if not specified + :param xi: (optional float) the finite difference parameter + :param clip_min: (optional float) Minimum input component value + :param clip_max: (optional float) Maximum input component value + :param num_iterations: Deprecated alias for `nb_iter` + """ + # Save attack-specific parameters + self.eps = eps + if num_iterations is not None: + warnings.warn("`num_iterations` is deprecated. Switch to `nb_iter`." + " The old name will be removed on or after 2019-04-26.") + # Note: when we remove the deprecated alias, we can put the default + # value of 1 for nb_iter back in the method signature + assert nb_iter is None + nb_iter = num_iterations + del num_iterations + if nb_iter is None: + nb_iter = 1 + self.num_iterations = nb_iter + self.xi = xi + self.clip_min = clip_min + self.clip_max = clip_max + if len(kwargs.keys()) > 0: + warnings.warn("kwargs is unused and will be removed on or after " + "2019-04-26.") + return True
+ + +
[docs]def vatm(model, + x, + logits, + eps, + num_iterations=1, + xi=1e-6, + clip_min=None, + clip_max=None, + scope=None): + """ + Tensorflow implementation of the perturbation method used for virtual + adversarial training: https://arxiv.org/abs/1507.00677 + :param model: the model which returns the network unnormalized logits + :param x: the input placeholder + :param logits: the model's unnormalized output tensor (the input to + the softmax layer) + :param eps: the epsilon (input variation parameter) + :param num_iterations: the number of iterations + :param xi: the finite difference parameter + :param clip_min: optional parameter that can be used to set a minimum + value for components of the example returned + :param clip_max: optional parameter that can be used to set a maximum + value for components of the example returned + :param seed: the seed for random generator + :return: a tensor for the adversarial example + """ + with tf.name_scope(scope, "virtual_adversarial_perturbation"): + d = tf.random_normal(tf.shape(x), dtype=tf_dtype) + for _ in range(num_iterations): + d = xi * utils_tf.l2_batch_normalize(d) + logits_d = model.get_logits(x + d) + kl = utils_tf.kl_with_logits(logits, logits_d) + Hd = tf.gradients(kl, d)[0] + d = tf.stop_gradient(Hd) + d = eps * utils_tf.l2_batch_normalize(d) + adv_x = x + d + if (clip_min is not None) and (clip_max is not None): + adv_x = tf.clip_by_value(adv_x, clip_min, clip_max) + return adv_x
+
+ +
+ +
+
+ +
+
+ + + + \ No newline at end of file diff --git a/cleverhans_v3.1.0/docs/_modules/cleverhans/compat.html b/cleverhans_v3.1.0/docs/_modules/cleverhans/compat.html new file mode 100644 index 000000000..92f9660b8 --- /dev/null +++ b/cleverhans_v3.1.0/docs/_modules/cleverhans/compat.html @@ -0,0 +1,172 @@ + + + + + + + + cleverhans.compat — CleverHans documentation + + + + + + + + + + + + + + + + + +
+
+
+ + +
+ +

Source code for cleverhans.compat

+"""
+Wrapper functions for writing code that is compatible with many versions
+of TensorFlow.
+"""
+import warnings
+import tensorflow as tf
+# The following 2 imports are not used in this module. They are imported so that users of cleverhans.compat can
+# get access to device_lib, app, and flags. A pylint bug makes these imports cause errors when using python3+tf1.8.
+# Doing the sanitized import here once makes it possible to do "from cleverhans.compat import flags" throughout the
+# library without needing to repeat the pylint boilerplate.
+from tensorflow.python.client import device_lib # pylint: disable=no-name-in-module,unused-import
+from tensorflow.python.platform import app, flags # pylint: disable=no-name-in-module,unused-import
+
+def _wrap(f):
+  """
+  Wraps a callable `f` in a function that warns that the function is deprecated.
+  """
+  def wrapper(*args, **kwargs):
+    """
+    Issues a deprecation warning and passes through the arguments.
+    """
+    warnings.warn(str(f) + " is deprecated. Switch to calling the equivalent function in tensorflow. "
+                  " This function was originally needed as a compatibility layer for old versions of tensorflow, "
+                  " but support for those versions has now been dropped.")
+    return f(*args, **kwargs)
+  return wrapper
+
+reduce_sum = _wrap(tf.reduce_sum)
+reduce_max = _wrap(tf.reduce_max)
+reduce_min = _wrap(tf.reduce_min)
+reduce_mean = _wrap(tf.reduce_mean)
+reduce_prod = _wrap(tf.reduce_prod)
+reduce_any = _wrap(tf.reduce_any)
+
+def reduce_function(op_func, input_tensor, axis=None, keepdims=None,
+                    name=None, reduction_indices=None):
+  """
+  This function used to be needed to support tf 1.4 and early, but support for tf 1.4 and earlier is now dropped.
+  :param op_func: expects the function to handle eg: tf.reduce_sum.
+  :param input_tensor: The tensor to reduce. Should have numeric type.
+  :param axis: The dimensions to reduce. If None (the default),
+          reduces all dimensions. Must be in the range
+          [-rank(input_tensor), rank(input_tensor)).
+  :param keepdims: If true, retains reduced dimensions with length 1.
+  :param name: A name for the operation (optional).
+  :param reduction_indices: The old (deprecated) name for axis.
+  :return: outputs same value as op_func.
+  """
+
+  warnings.warn("`reduce_function` is deprecated and may be removed on or after 2019-09-08.")
+
+  out = op_func(input_tensor, axis=axis, keepdims=keepdims, name=name, reduction_indices=reduction_indices)
+
+  return out
+
+
[docs]def softmax_cross_entropy_with_logits(sentinel=None, + labels=None, + logits=None, + dim=-1): + """ + Wrapper around tf.nn.softmax_cross_entropy_with_logits_v2 to handle + deprecated warning + """ + # Make sure that all arguments were passed as named arguments. + if sentinel is not None: + name = "softmax_cross_entropy_with_logits" + raise ValueError("Only call `%s` with " + "named arguments (labels=..., logits=..., ...)" + % name) + if labels is None or logits is None: + raise ValueError("Both labels and logits must be provided.") + + try: + f = tf.nn.softmax_cross_entropy_with_logits_v2 + except AttributeError: + raise RuntimeError("This version of TensorFlow is no longer supported. See cleverhans/README.md") + + labels = tf.stop_gradient(labels) + loss = f(labels=labels, logits=logits, dim=dim) + + return loss
+
+ +
+ +
+
+ +
+
+ + + + \ No newline at end of file diff --git a/cleverhans_v3.1.0/docs/_modules/cleverhans/model.html b/cleverhans_v3.1.0/docs/_modules/cleverhans/model.html new file mode 100644 index 000000000..8b5a2a465 --- /dev/null +++ b/cleverhans_v3.1.0/docs/_modules/cleverhans/model.html @@ -0,0 +1,379 @@ + + + + + + + + cleverhans.model — CleverHans documentation + + + + + + + + + + + + + + + + + +
+
+
+ + +
+ +

Source code for cleverhans.model

+"""
+The Model class and related functionality.
+"""
+from abc import ABCMeta
+import warnings
+
+import tensorflow as tf
+
+from cleverhans import utils_tf
+
+
+
[docs]class Model(object): + """ + An abstract interface for model wrappers that exposes model symbols + needed for making an attack. This abstraction removes the dependency on + any specific neural network package (e.g. Keras) from the core + code of CleverHans. It can also simplify exposing the hidden features of a + model when a specific package does not directly expose them. + """ + __metaclass__ = ABCMeta + O_LOGITS, O_PROBS, O_FEATURES = 'logits probs features'.split() + + def __init__(self, scope=None, nb_classes=None, hparams=None, + needs_dummy_fprop=False): + """ + Constructor. + :param scope: str, the name of model. + :param nb_classes: integer, the number of classes. + :param hparams: dict, hyper-parameters for the model. + :needs_dummy_fprop: bool, if True the model's parameters are not + created until fprop is called. + """ + self.scope = scope or self.__class__.__name__ + self.nb_classes = nb_classes + self.hparams = hparams or {} + self.needs_dummy_fprop = needs_dummy_fprop + + def __call__(self, *args, **kwargs): + """ + For compatibility with functions used as model definitions (taking + an input tensor and returning the tensor giving the output + of the model on that input). + """ + + warnings.warn("Model.__call__ is deprecated. " + "The call is ambiguous as to whether the output should " + "be logits or probabilities, and getting the wrong one " + "can cause serious problems. " + "The output actually is probabilities, which are a very " + "dangerous thing to use as part of any interface for " + "cleverhans, because softmax probabilities are prone " + "to gradient masking." + "On or after 2019-04-24, this method will change to raise " + "an exception explaining why Model.__call__ should not be " + "used.") + + return self.get_probs(*args, **kwargs) + +
[docs] def get_logits(self, x, **kwargs): + """ + :param x: A symbolic representation (Tensor) of the network input + :return: A symbolic representation (Tensor) of the output logits + (i.e., the values fed as inputs to the softmax layer). + """ + outputs = self.fprop(x, **kwargs) + if self.O_LOGITS in outputs: + return outputs[self.O_LOGITS] + raise NotImplementedError(str(type(self)) + "must implement `get_logits`" + " or must define a " + self.O_LOGITS + + " output in `fprop`")
+ +
[docs] def get_predicted_class(self, x, **kwargs): + """ + :param x: A symbolic representation (Tensor) of the network input + :return: A symbolic representation (Tensor) of the predicted label + """ + return tf.argmax(self.get_logits(x, **kwargs), axis=1)
+ +
[docs] def get_probs(self, x, **kwargs): + """ + :param x: A symbolic representation (Tensor) of the network input + :return: A symbolic representation (Tensor) of the output + probabilities (i.e., the output values produced by the softmax layer). + """ + d = self.fprop(x, **kwargs) + if self.O_PROBS in d: + output = d[self.O_PROBS] + min_prob = tf.reduce_min(output) + max_prob = tf.reduce_max(output) + asserts = [utils_tf.assert_greater_equal(min_prob, + tf.cast(0., min_prob.dtype)), + utils_tf.assert_less_equal(max_prob, + tf.cast(1., min_prob.dtype))] + with tf.control_dependencies(asserts): + output = tf.identity(output) + return output + elif self.O_LOGITS in d: + return tf.nn.softmax(logits=d[self.O_LOGITS]) + else: + raise ValueError('Cannot find probs or logits.')
+ +
[docs] def fprop(self, x, **kwargs): + """ + Forward propagation to compute the model outputs. + :param x: A symbolic representation of the network input + :return: A dictionary mapping layer names to the symbolic + representation of their output. + """ + raise NotImplementedError('`fprop` not implemented.')
+ +
[docs] def get_params(self): + """ + Provides access to the model's parameters. + :return: A list of all Variables defining the model parameters. + """ + + if hasattr(self, 'params'): + return list(self.params) + + # Catch eager execution and assert function overload. + try: + if tf.executing_eagerly(): + raise NotImplementedError("For Eager execution - get_params " + "must be overridden.") + except AttributeError: + pass + + # For graph-based execution + scope_vars = tf.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES, + self.scope + "/") + + if len(scope_vars) == 0: + self.make_params() + scope_vars = tf.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES, + self.scope + "/") + assert len(scope_vars) > 0 + + # Make sure no parameters have been added or removed + if hasattr(self, "num_params"): + if self.num_params != len(scope_vars): + print("Scope: ", self.scope) + print("Expected " + str(self.num_params) + " variables") + print("Got " + str(len(scope_vars))) + for var in scope_vars: + print("\t" + str(var)) + assert False + else: + self.num_params = len(scope_vars) + + return scope_vars
+ +
[docs] def make_params(self): + """ + Create all Variables to be returned later by get_params. + By default this is a no-op. + Models that need their fprop to be called for their params to be + created can set `needs_dummy_fprop=True` in the constructor. + """ + + if self.needs_dummy_fprop: + if hasattr(self, "_dummy_input"): + return + self._dummy_input = self.make_input_placeholder() + self.fprop(self._dummy_input)
+ +
[docs] def get_layer_names(self): + """Return the list of exposed layers for this model.""" + raise NotImplementedError
+ +
[docs] def get_layer(self, x, layer, **kwargs): + """Return a layer output. + :param x: tensor, the input to the network. + :param layer: str, the name of the layer to compute. + :param **kwargs: dict, extra optional params to pass to self.fprop. + :return: the content of layer `layer` + """ + return self.fprop(x, **kwargs)[layer]
+ +
[docs] def make_input_placeholder(self): + """Create and return a placeholder representing an input to the model. + + This method should respect context managers (e.g. "with tf.device") + and should not just return a reference to a single pre-created + placeholder. + """ + + raise NotImplementedError(str(type(self)) + " does not implement " + "make_input_placeholder")
+ +
[docs] def make_label_placeholder(self): + """Create and return a placeholder representing class labels. + + This method should respect context managers (e.g. "with tf.device") + and should not just return a reference to a single pre-created + placeholder. + """ + + raise NotImplementedError(str(type(self)) + " does not implement " + "make_label_placeholder")
+ + def __hash__(self): + return hash(id(self)) + + def __eq__(self, other): + return self is other
+ + +
[docs]class CallableModelWrapper(Model): + """A wrapper that turns a callable into a valid Model""" + + def __init__(self, callable_fn, output_layer): + """ + Wrap a callable function that takes a tensor as input and returns + a tensor as output with the given layer name. + :param callable_fn: The callable function taking a tensor and + returning a given layer as output. + :param output_layer: A string of the output layer returned by the + function. (Usually either "probs" or "logits".) + """ + + super(CallableModelWrapper, self).__init__() + self.output_layer = output_layer + self.callable_fn = callable_fn + +
[docs] def fprop(self, x, **kwargs): + output = self.callable_fn(x, **kwargs) + + # Do some sanity checking to reduce the chance that probs are used + # as logits accidentally or vice versa + if self.output_layer == 'probs': + assert output.op.type == "Softmax" + min_prob = tf.reduce_min(output) + max_prob = tf.reduce_max(output) + asserts = [utils_tf.assert_greater_equal(min_prob, + tf.cast(0., min_prob.dtype)), + utils_tf.assert_less_equal(max_prob, + tf.cast(1., max_prob.dtype))] + with tf.control_dependencies(asserts): + output = tf.identity(output) + elif self.output_layer == 'logits': + assert output.op.type != 'Softmax' + + return {self.output_layer: output}
+ +
[docs]def wrapper_warning(): + """ + Issue a deprecation warning. Used in multiple places that implemented + attacks by automatically wrapping a user-supplied callable with a + CallableModelWrapper with output_layer="probs". + Using "probs" as any part of the attack interface is dangerous. + We can't just change output_layer to logits because: + - that would be a silent interface change. We'd have no way of detecting + code that still means to use probs. Note that we can't just check whether + the final output op is a softmax---for example, Inception puts a reshape + after the softmax. + - automatically wrapping user-supplied callables with output_layer='logits' + is even worse, see `wrapper_warning_logits` + Note: this function will be removed at the same time as the code that + calls it. + """ + warnings.warn("Passing a callable is deprecated, because using" + " probabilities is dangerous. It has a high risk " + " of causing gradient masking due to loss of precision " + " in the softmax op. Passing a callable rather than a " + " Model subclass will become an error on or after " + " 2019-04-24.")
+ +
[docs]def wrapper_warning_logits(): + """ + Issue a deprecation warning. Used in multiple places that implemented + attacks by automatically wrapping a user-supplied callable with a + CallableModelWrapper with output_layer="logits". + This is dangerous because it is under-the-hood automagic that the user + may not realize has been invoked for them. If they pass a callable + that actually outputs probs, the probs will be treated as logits, + resulting in an incorrect cross-entropy loss and severe gradient + masking. + """ + warnings.warn("Passing a callable is deprecated, because it runs the " + "risk of accidentally using probabilities in the place " + "of logits. Please switch to passing a Model subclass " + "so that you clearly specify which values are the logits. " + "Passing a callable rather than a Model subclass will become " + "an error on or after 2019-04-24.")
+ + +
[docs]class NoSuchLayerError(ValueError): + """Raised when a layer that does not exist is requested."""
+
+ +
+ +
+
+ +
+
+ + + + \ No newline at end of file diff --git a/cleverhans_v3.1.0/docs/_modules/cleverhans/utils_tf.html b/cleverhans_v3.1.0/docs/_modules/cleverhans/utils_tf.html new file mode 100644 index 000000000..c82a9e01a --- /dev/null +++ b/cleverhans_v3.1.0/docs/_modules/cleverhans/utils_tf.html @@ -0,0 +1,926 @@ + + + + + + + + cleverhans.utils_tf — CleverHans documentation + + + + + + + + + + + + + + + + + +
+
+
+ + +
+ +

Source code for cleverhans.utils_tf

+"""Utility functions for writing TensorFlow code"""
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+from __future__ import unicode_literals
+
+import logging
+import math
+import os
+import time
+import warnings
+
+import numpy as np
+import six
+from six.moves import xrange
+import tensorflow as tf
+
+from cleverhans.compat import device_lib
+from cleverhans.compat import reduce_sum, reduce_mean
+from cleverhans.compat import reduce_max
+from cleverhans.compat import softmax_cross_entropy_with_logits
+from cleverhans.utils import batch_indices, _ArgsWrapper, create_logger
+
+_logger = create_logger("cleverhans.utils.tf")
+_logger.setLevel(logging.INFO)
+
+
+def model_loss(y, model, mean=True):
+  """
+  Define loss of TF graph
+  :param y: correct labels
+  :param model: output of the model
+  :param mean: boolean indicating whether should return mean of loss
+               or vector of losses for each input of the batch
+  :return: return mean of loss if True, otherwise return vector with per
+           sample loss
+  """
+  warnings.warn("This function is deprecated and will be removed on or after"
+                " 2019-04-05. Switch to cleverhans.train.train.")
+  op = model.op
+  if op.type == "Softmax":
+    logits, = op.inputs
+  else:
+    logits = model
+
+  out = softmax_cross_entropy_with_logits(logits=logits, labels=y)
+
+  if mean:
+    out = reduce_mean(out)
+  return out
+
+
+def initialize_uninitialized_global_variables(sess):
+  """
+  Only initializes the variables of a TensorFlow session that were not
+  already initialized.
+  :param sess: the TensorFlow session
+  :return:
+  """
+  # List all global variables
+  global_vars = tf.global_variables()
+
+  # Find initialized status for all variables
+  is_var_init = [tf.is_variable_initialized(var) for var in global_vars]
+  is_initialized = sess.run(is_var_init)
+
+  # List all variables that were not initialized previously
+  not_initialized_vars = [var for (var, init) in
+                          zip(global_vars, is_initialized) if not init]
+
+  # Initialize all uninitialized variables found, if any
+  if len(not_initialized_vars):
+    sess.run(tf.variables_initializer(not_initialized_vars))
+
+
+def train(sess, loss, x, y, X_train, Y_train, save=False,
+          init_all=False, evaluate=None, feed=None, args=None,
+          rng=None, var_list=None, fprop_args=None, optimizer=None):
+  """
+  Train a TF graph.
+  This function is deprecated. Prefer cleverhans.train.train when possible.
+  cleverhans.train.train supports multiple GPUs but this function is still
+  needed to support legacy models that do not support calling fprop more
+  than once.
+
+  :param sess: TF session to use when training the graph
+  :param loss: tensor, the model training loss.
+  :param x: input placeholder
+  :param y: output placeholder (for labels)
+  :param X_train: numpy array with training inputs
+  :param Y_train: numpy array with training outputs
+  :param save: boolean controlling the save operation
+  :param init_all: (boolean) If set to true, all TF variables in the session
+                   are (re)initialized, otherwise only previously
+                   uninitialized variables are initialized before training.
+  :param evaluate: function that is run after each training iteration
+                   (typically to display the test/validation accuracy).
+  :param feed: An optional dictionary that is appended to the feeding
+               dictionary before the session runs. Can be used to feed
+               the learning phase of a Keras model for instance.
+  :param args: dict or argparse `Namespace` object.
+               Should contain `nb_epochs`, `learning_rate`,
+               `batch_size`
+               If save is True, should also contain 'train_dir'
+               and 'filename'
+  :param rng: Instance of numpy.random.RandomState
+  :param var_list: Optional list of parameters to train.
+  :param fprop_args: dict, extra arguments to pass to fprop (loss and model).
+  :param optimizer: Optimizer to be used for training
+  :return: True if model trained
+  """
+  warnings.warn("This function is deprecated and will be removed on or after"
+                " 2019-04-05. Switch to cleverhans.train.train.")
+
+  args = _ArgsWrapper(args or {})
+  fprop_args = fprop_args or {}
+
+  # Check that necessary arguments were given (see doc above)
+  assert args.nb_epochs, "Number of epochs was not given in args dict"
+  if optimizer is None:
+    assert args.learning_rate is not None, ("Learning rate was not given "
+                                            "in args dict")
+  assert args.batch_size, "Batch size was not given in args dict"
+
+  if save:
+    assert args.train_dir, "Directory for save was not given in args dict"
+    assert args.filename, "Filename for save was not given in args dict"
+
+  if rng is None:
+    rng = np.random.RandomState()
+
+  # Define optimizer
+  loss_value = loss.fprop(x, y, **fprop_args)
+  if optimizer is None:
+    optimizer = tf.train.AdamOptimizer(learning_rate=args.learning_rate)
+  else:
+    if not isinstance(optimizer, tf.train.Optimizer):
+      raise ValueError("optimizer object must be from a child class of "
+                       "tf.train.Optimizer")
+  # Trigger update operations within the default graph (such as batch_norm).
+  with tf.control_dependencies(tf.get_collection(tf.GraphKeys.UPDATE_OPS)):
+    train_step = optimizer.minimize(loss_value, var_list=var_list)
+
+  with sess.as_default():
+    if hasattr(tf, "global_variables_initializer"):
+      if init_all:
+        tf.global_variables_initializer().run()
+      else:
+        initialize_uninitialized_global_variables(sess)
+    else:
+      warnings.warn("Update your copy of tensorflow; future versions of "
+                    "CleverHans may drop support for this version.")
+      sess.run(tf.initialize_all_variables())
+
+    for epoch in xrange(args.nb_epochs):
+      # Compute number of batches
+      nb_batches = int(math.ceil(float(len(X_train)) / args.batch_size))
+      assert nb_batches * args.batch_size >= len(X_train)
+
+      # Indices to shuffle training set
+      index_shuf = list(range(len(X_train)))
+      rng.shuffle(index_shuf)
+
+      prev = time.time()
+      for batch in range(nb_batches):
+
+        # Compute batch start and end indices
+        start, end = batch_indices(
+            batch, len(X_train), args.batch_size)
+
+        # Perform one training step
+        feed_dict = {x: X_train[index_shuf[start:end]],
+                     y: Y_train[index_shuf[start:end]]}
+        if feed is not None:
+          feed_dict.update(feed)
+        train_step.run(feed_dict=feed_dict)
+      assert end >= len(X_train)  # Check that all examples were used
+      cur = time.time()
+      _logger.info("Epoch " + str(epoch) + " took " +
+                   str(cur - prev) + " seconds")
+      if evaluate is not None:
+        evaluate()
+
+    if save:
+      save_path = os.path.join(args.train_dir, args.filename)
+      saver = tf.train.Saver()
+      saver.save(sess, save_path)
+      _logger.info("Completed model training and saved at: " +
+                   str(save_path))
+    else:
+      _logger.info("Completed model training.")
+
+  return True
+
+
+def model_eval(sess, x, y, predictions, X_test=None, Y_test=None,
+               feed=None, args=None):
+  """
+  Compute the accuracy of a TF model on some data
+  :param sess: TF session to use
+  :param x: input placeholder
+  :param y: output placeholder (for labels)
+  :param predictions: model output predictions
+  :param X_test: numpy array with training inputs
+  :param Y_test: numpy array with training outputs
+  :param feed: An optional dictionary that is appended to the feeding
+           dictionary before the session runs. Can be used to feed
+           the learning phase of a Keras model for instance.
+  :param args: dict or argparse `Namespace` object.
+               Should contain `batch_size`
+  :return: a float with the accuracy value
+  """
+  global _model_eval_cache
+  args = _ArgsWrapper(args or {})
+
+  assert args.batch_size, "Batch size was not given in args dict"
+  if X_test is None or Y_test is None:
+    raise ValueError("X_test argument and Y_test argument "
+                     "must be supplied.")
+
+  # Define accuracy symbolically
+  key = (y, predictions)
+  if key in _model_eval_cache:
+    correct_preds = _model_eval_cache[key]
+  else:
+    correct_preds = tf.equal(tf.argmax(y, axis=-1),
+                             tf.argmax(predictions, axis=-1))
+    _model_eval_cache[key] = correct_preds
+
+  # Init result var
+  accuracy = 0.0
+
+  with sess.as_default():
+    # Compute number of batches
+    nb_batches = int(math.ceil(float(len(X_test)) / args.batch_size))
+    assert nb_batches * args.batch_size >= len(X_test)
+
+    X_cur = np.zeros((args.batch_size,) + X_test.shape[1:],
+                     dtype=X_test.dtype)
+    Y_cur = np.zeros((args.batch_size,) + Y_test.shape[1:],
+                     dtype=Y_test.dtype)
+    for batch in range(nb_batches):
+      if batch % 100 == 0 and batch > 0:
+        _logger.debug("Batch " + str(batch))
+
+      # Must not use the `batch_indices` function here, because it
+      # repeats some examples.
+      # It's acceptable to repeat during training, but not eval.
+      start = batch * args.batch_size
+      end = min(len(X_test), start + args.batch_size)
+
+      # The last batch may be smaller than all others. This should not
+      # affect the accuarcy disproportionately.
+      cur_batch_size = end - start
+      X_cur[:cur_batch_size] = X_test[start:end]
+      Y_cur[:cur_batch_size] = Y_test[start:end]
+      feed_dict = {x: X_cur, y: Y_cur}
+      if feed is not None:
+        feed_dict.update(feed)
+      cur_corr_preds = correct_preds.eval(feed_dict=feed_dict)
+
+      accuracy += cur_corr_preds[:cur_batch_size].sum()
+
+    assert end >= len(X_test)
+
+    # Divide by number of examples to get final value
+    accuracy /= len(X_test)
+
+  return accuracy
+
+_model_eval_cache = {}
+
+
+def tf_model_load(sess, file_path=None):
+  """
+
+  :param sess: the session object to restore
+  :param file_path: path to the restored session, if None is
+                    taken from FLAGS.train_dir and FLAGS.filename
+  :return:
+  """
+  with sess.as_default():
+    saver = tf.train.Saver()
+    if file_path is None:
+      error = 'file_path argument is missing.'
+      raise ValueError(error)
+    saver.restore(sess, file_path)
+
+  return True
+
+
+def batch_eval(*args, **kwargs):
+  """
+  Wrapper around deprecated function.
+  """
+  # Inside function to avoid circular import
+  from cleverhans.evaluation import batch_eval as new_batch_eval
+  warnings.warn("batch_eval has moved to cleverhans.evaluation. "
+                "batch_eval will be removed from utils_tf on or after "
+                "2019-03-09.")
+  return new_batch_eval(*args, **kwargs)
+
+
+def model_argmax(sess, x, predictions, samples, feed=None):
+  """
+  Helper function that computes the current class prediction
+  :param sess: TF session
+  :param x: the input placeholder
+  :param predictions: the model's symbolic output
+  :param samples: numpy array with input samples (dims must match x)
+  :param feed: An optional dictionary that is appended to the feeding
+           dictionary before the session runs. Can be used to feed
+           the learning phase of a Keras model for instance.
+  :return: the argmax output of predictions, i.e. the current predicted class
+  """
+  feed_dict = {x: samples}
+  if feed is not None:
+    feed_dict.update(feed)
+  probabilities = sess.run(predictions, feed_dict)
+
+  if samples.shape[0] == 1:
+    return np.argmax(probabilities)
+  else:
+    return np.argmax(probabilities, axis=1)
+
+
+def l2_batch_normalize(x, epsilon=1e-12, scope=None):
+  """
+  Helper function to normalize a batch of vectors.
+  :param x: the input placeholder
+  :param epsilon: stabilizes division
+  :return: the batch of l2 normalized vector
+  """
+  with tf.name_scope(scope, "l2_batch_normalize") as name_scope:
+    x_shape = tf.shape(x)
+    x = tf.contrib.layers.flatten(x)
+    x /= (epsilon + reduce_max(tf.abs(x), 1, keepdims=True))
+    square_sum = reduce_sum(tf.square(x), 1, keepdims=True)
+    x_inv_norm = tf.rsqrt(np.sqrt(epsilon) + square_sum)
+    x_norm = tf.multiply(x, x_inv_norm)
+    return tf.reshape(x_norm, x_shape, name_scope)
+
+
+def kl_with_logits(p_logits, q_logits, scope=None,
+                   loss_collection=tf.GraphKeys.REGULARIZATION_LOSSES):
+  """Helper function to compute kl-divergence KL(p || q)
+  """
+  with tf.name_scope(scope, "kl_divergence") as name:
+    p = tf.nn.softmax(p_logits)
+    p_log = tf.nn.log_softmax(p_logits)
+    q_log = tf.nn.log_softmax(q_logits)
+    loss = reduce_mean(reduce_sum(p * (p_log - q_log), axis=1),
+                       name=name)
+    tf.losses.add_loss(loss, loss_collection)
+    return loss
+
+
+
[docs]def clip_eta(eta, ord, eps): + """ + Helper function to clip the perturbation to epsilon norm ball. + :param eta: A tensor with the current perturbation. + :param ord: Order of the norm (mimics Numpy). + Possible values: np.inf, 1 or 2. + :param eps: Epsilon, bound of the perturbation. + """ + + # Clipping perturbation eta to self.ord norm ball + if ord not in [np.inf, 1, 2]: + raise ValueError('ord must be np.inf, 1, or 2.') + reduc_ind = list(xrange(1, len(eta.get_shape()))) + avoid_zero_div = 1e-12 + if ord == np.inf: + eta = clip_by_value(eta, -eps, eps) + elif ord == 1: + # Implements a projection algorithm onto the l1-ball from + # (Duchi et al. 2008) that runs in time O(d*log(d)) where d is the + # input dimension. + # Paper link (Duchi et al. 2008): https://dl.acm.org/citation.cfm?id=1390191 + + eps = tf.cast(eps, eta.dtype) + + dim = tf.reduce_prod(tf.shape(eta)[1:]) + eta_flat = tf.reshape(eta, (-1, dim)) + abs_eta = tf.abs(eta_flat) + + if 'sort' in dir(tf): + mu = -tf.sort(-abs_eta, axis=-1) + else: + # `tf.sort` is only available in TF 1.13 onwards + mu = tf.nn.top_k(abs_eta, k=dim, sorted=True)[0] + cumsums = tf.cumsum(mu, axis=-1) + js = tf.cast(tf.divide(1, tf.range(1, dim + 1)), eta.dtype) + t = tf.cast(tf.greater(mu - js * (cumsums - eps), 0), eta.dtype) + + rho = tf.argmax(t * cumsums, axis=-1) + rho_val = tf.reduce_max(t * cumsums, axis=-1) + theta = tf.divide(rho_val - eps, tf.cast(1 + rho, eta.dtype)) + + eta_sgn = tf.sign(eta_flat) + eta_proj = eta_sgn * tf.maximum(abs_eta - theta[:, tf.newaxis], 0) + eta_proj = tf.reshape(eta_proj, tf.shape(eta)) + + norm = tf.reduce_sum(tf.abs(eta), reduc_ind) + eta = tf.where(tf.greater(norm, eps), eta_proj, eta) + + elif ord == 2: + # avoid_zero_div must go inside sqrt to avoid a divide by zero + # in the gradient through this operation + norm = tf.sqrt(tf.maximum(avoid_zero_div, + reduce_sum(tf.square(eta), + reduc_ind, + keepdims=True))) + # We must *clip* to within the norm ball, not *normalize* onto the + # surface of the ball + factor = tf.minimum(1., div(eps, norm)) + eta = eta * factor + return eta
+ + +def zero_out_clipped_grads(grad, x, clip_min, clip_max): + """ + Helper function to erase entries in the gradient where the update would be + clipped. + :param grad: The gradient + :param x: The current input + :param clip_min: Minimum input component value + :param clip_max: Maximum input component value + """ + signed_grad = tf.sign(grad) + + # Find input components that lie at the boundary of the input range, and + # where the gradient points in the wrong direction. + clip_low = tf.logical_and(tf.less_equal(x, tf.cast(clip_min, x.dtype)), + tf.less(signed_grad, 0)) + clip_high = tf.logical_and(tf.greater_equal(x, tf.cast(clip_max, x.dtype)), + tf.greater(signed_grad, 0)) + clip = tf.logical_or(clip_low, clip_high) + grad = tf.where(clip, mul(grad, 0), grad) + + return grad + + +def random_exponential(shape, rate=1.0, dtype=tf.float32, seed=None): + """ + Helper function to sample from the exponential distribution, which is not + included in core TensorFlow. + """ + return tf.random_gamma(shape, alpha=1, beta=1. / rate, dtype=dtype, seed=seed) + + +def random_laplace(shape, loc=0.0, scale=1.0, dtype=tf.float32, seed=None): + """ + Helper function to sample from the Laplace distribution, which is not + included in core TensorFlow. + """ + z1 = random_exponential(shape, loc, dtype=dtype, seed=seed) + z2 = random_exponential(shape, scale, dtype=dtype, seed=seed) + return z1 - z2 + + +def random_lp_vector(shape, ord, eps, dtype=tf.float32, seed=None): + """ + Helper function to generate uniformly random vectors from a norm ball of + radius epsilon. + :param shape: Output shape of the random sample. The shape is expected to be + of the form `(n, d1, d2, ..., dn)` where `n` is the number of + i.i.d. samples that will be drawn from a norm ball of dimension + `d1*d1*...*dn`. + :param ord: Order of the norm (mimics Numpy). + Possible values: np.inf, 1 or 2. + :param eps: Epsilon, radius of the norm ball. + """ + if ord not in [np.inf, 1, 2]: + raise ValueError('ord must be np.inf, 1, or 2.') + + if ord == np.inf: + r = tf.random_uniform(shape, -eps, eps, dtype=dtype, seed=seed) + else: + + # For ord=1 and ord=2, we use the generic technique from + # (Calafiore et al. 1998) to sample uniformly from a norm ball. + # Paper link (Calafiore et al. 1998): + # https://ieeexplore.ieee.org/stamp/stamp.jsp?arnumber=758215&tag=1 + # We first sample from the surface of the norm ball, and then scale by + # a factor `w^(1/d)` where `w~U[0,1]` is a standard uniform random variable + # and `d` is the dimension of the ball. In high dimensions, this is roughly + # equivalent to sampling from the surface of the ball. + + dim = tf.reduce_prod(shape[1:]) + + if ord == 1: + x = random_laplace((shape[0], dim), loc=1.0, scale=1.0, dtype=dtype, + seed=seed) + norm = tf.reduce_sum(tf.abs(x), axis=-1, keepdims=True) + elif ord == 2: + x = tf.random_normal((shape[0], dim), dtype=dtype, seed=seed) + norm = tf.sqrt(tf.reduce_sum(tf.square(x), axis=-1, keepdims=True)) + else: + raise ValueError('ord must be np.inf, 1, or 2.') + + w = tf.pow(tf.random.uniform((shape[0], 1), dtype=dtype, seed=seed), + 1.0 / tf.cast(dim, dtype)) + r = eps * tf.reshape(w * x / norm, shape) + + return r + + +def model_train(sess, x, y, predictions, X_train, Y_train, save=False, + predictions_adv=None, init_all=True, evaluate=None, + feed=None, args=None, rng=None, var_list=None): + """ + Train a TF graph + :param sess: TF session to use when training the graph + :param x: input placeholder + :param y: output placeholder (for labels) + :param predictions: model output predictions + :param X_train: numpy array with training inputs + :param Y_train: numpy array with training outputs + :param save: boolean controlling the save operation + :param predictions_adv: if set with the adversarial example tensor, + will run adversarial training + :param init_all: (boolean) If set to true, all TF variables in the session + are (re)initialized, otherwise only previously + uninitialized variables are initialized before training. + :param evaluate: function that is run after each training iteration + (typically to display the test/validation accuracy). + :param feed: An optional dictionary that is appended to the feeding + dictionary before the session runs. Can be used to feed + the learning phase of a Keras model for instance. + :param args: dict or argparse `Namespace` object. + Should contain `nb_epochs`, `learning_rate`, + `batch_size` + If save is True, should also contain 'train_dir' + and 'filename' + :param rng: Instance of numpy.random.RandomState + :param var_list: Optional list of parameters to train. + :return: True if model trained + """ + warnings.warn("This function is deprecated and will be removed on or after" + " 2019-04-05. Switch to cleverhans.train.train.") + args = _ArgsWrapper(args or {}) + + # Check that necessary arguments were given (see doc above) + assert args.nb_epochs, "Number of epochs was not given in args dict" + assert args.learning_rate, "Learning rate was not given in args dict" + assert args.batch_size, "Batch size was not given in args dict" + + if save: + assert args.train_dir, "Directory for save was not given in args dict" + assert args.filename, "Filename for save was not given in args dict" + + if rng is None: + rng = np.random.RandomState() + + # Define loss + loss = model_loss(y, predictions) + if predictions_adv is not None: + loss = (loss + model_loss(y, predictions_adv)) / 2 + + train_step = tf.train.AdamOptimizer(learning_rate=args.learning_rate) + train_step = train_step.minimize(loss, var_list=var_list) + + with sess.as_default(): + if hasattr(tf, "global_variables_initializer"): + if init_all: + tf.global_variables_initializer().run() + else: + initialize_uninitialized_global_variables(sess) + else: + warnings.warn("Update your copy of tensorflow; future versions of " + "CleverHans may drop support for this version.") + sess.run(tf.initialize_all_variables()) + + for epoch in xrange(args.nb_epochs): + # Compute number of batches + nb_batches = int(math.ceil(float(len(X_train)) / args.batch_size)) + assert nb_batches * args.batch_size >= len(X_train) + + # Indices to shuffle training set + index_shuf = list(range(len(X_train))) + rng.shuffle(index_shuf) + + prev = time.time() + for batch in range(nb_batches): + + # Compute batch start and end indices + start, end = batch_indices( + batch, len(X_train), args.batch_size) + + # Perform one training step + feed_dict = {x: X_train[index_shuf[start:end]], + y: Y_train[index_shuf[start:end]]} + if feed is not None: + feed_dict.update(feed) + train_step.run(feed_dict=feed_dict) + assert end >= len(X_train) # Check that all examples were used + cur = time.time() + _logger.info("Epoch " + str(epoch) + " took " + + str(cur - prev) + " seconds") + if evaluate is not None: + evaluate() + + if save: + save_path = os.path.join(args.train_dir, args.filename) + saver = tf.train.Saver() + saver.save(sess, save_path) + _logger.info("Completed model training and saved at: " + + str(save_path)) + else: + _logger.info("Completed model training.") + + return True + + +def infer_devices(devices=None): + """ + Returns the list of devices that multi-replica code should use. + :param devices: list of string device names, e.g. ["/GPU:0"] + If the user specifies this, `infer_devices` checks that it is + valid, and then uses this user-specified list. + If the user does not specify this, infer_devices uses: + - All available GPUs, if there are any + - CPU otherwise + """ + if devices is None: + devices = get_available_gpus() + if len(devices) == 0: + warnings.warn("No GPUS, running on CPU") + # Set device to empy string, tf will figure out whether to use + # XLA or not, etc., automatically + devices = [""] + else: + assert len(devices) > 0 + for device in devices: + assert isinstance(device, six.string_types), type(device) + return devices + + +def get_available_gpus(): + """ + Returns a list of string names of all available GPUs + """ + local_device_protos = device_lib.list_local_devices() + return [x.name for x in local_device_protos if x.device_type == 'GPU'] + + +def silence(): + """ + Silences tensorflaw's default printed messages + """ + os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3' + +def clip_by_value(t, clip_value_min, clip_value_max, name=None): + """ + A wrapper for clip_by_value that casts the clipping range if needed. + """ + def cast_clip(clip): + """ + Cast clipping range argument if needed. + """ + if t.dtype in (tf.float32, tf.float64): + if hasattr(clip, 'dtype'): + # Convert to tf dtype in case this is a numpy dtype + clip_dtype = tf.as_dtype(clip.dtype) + if clip_dtype != t.dtype: + return tf.cast(clip, t.dtype) + return clip + + clip_value_min = cast_clip(clip_value_min) + clip_value_max = cast_clip(clip_value_max) + + return tf.clip_by_value(t, clip_value_min, clip_value_max, name) + +def mul(a, b): + """ + A wrapper around tf multiplication that does more automatic casting of + the input. + """ + def multiply(a, b): + """Multiplication""" + return a * b + return op_with_scalar_cast(a, b, multiply) + +def div(a, b): + """ + A wrapper around tf division that does more automatic casting of + the input. + """ + def divide(a, b): + """Division""" + return a / b + return op_with_scalar_cast(a, b, divide) + +def op_with_scalar_cast(a, b, f): + """ + Builds the graph to compute f(a, b). + If only one of the two arguments is a scalar and the operation would + cause a type error without casting, casts the scalar to match the + tensor. + :param a: a tf-compatible array or scalar + :param b: a tf-compatible array or scalar + """ + + try: + return f(a, b) + except (TypeError, ValueError): + pass + + def is_scalar(x): + """Return True if `x` is a scalar""" + if hasattr(x, "get_shape"): + shape = x.get_shape() + return shape.ndims == 0 + if hasattr(x, "ndim"): + return x.ndim == 0 + assert isinstance(x, (int, float)) + return True + + a_scalar = is_scalar(a) + b_scalar = is_scalar(b) + + if a_scalar and b_scalar: + raise TypeError("Trying to apply " + str(f) + " with mixed types") + + if a_scalar and not b_scalar: + a = tf.cast(a, b.dtype) + + if b_scalar and not a_scalar: + b = tf.cast(b, a.dtype) + + return f(a, b) + +def assert_less_equal(*args, **kwargs): + """ + Wrapper for tf.assert_less_equal + Overrides tf.device so that the assert always goes on CPU. + The unwrapped version raises an exception if used with tf.device("/GPU:x"). + """ + with tf.device("/CPU:0"): + return tf.assert_less_equal(*args, **kwargs) + +def assert_greater_equal(*args, **kwargs): + """ + Wrapper for tf.assert_greater_equal. + Overrides tf.device so that the assert always goes on CPU. + The unwrapped version raises an exception if used with tf.device("/GPU:x"). + """ + with tf.device("/CPU:0"): + return tf.assert_greater_equal(*args, **kwargs) + +def assert_equal(*args, **kwargs): + """ + Wrapper for tf.assert_equal. + Overrides tf.device so that the assert always goes on CPU. + The unwrapped version raises an exception if used with tf.device("/GPU:x"). + """ + with tf.device("/CPU:0"): + return tf.assert_equal(*args, **kwargs) + +def jacobian_graph(predictions, x, nb_classes): + """ + Create the Jacobian graph to be ran later in a TF session + :param predictions: the model's symbolic output (linear output, + pre-softmax) + :param x: the input placeholder + :param nb_classes: the number of classes the model has + :return: + """ + + # This function will return a list of TF gradients + list_derivatives = [] + + # Define the TF graph elements to compute our derivatives for each class + for class_ind in xrange(nb_classes): + derivatives, = tf.gradients(predictions[:, class_ind], x) + list_derivatives.append(derivatives) + + return list_derivatives + +def jacobian_augmentation(sess, + x, + X_sub_prev, + Y_sub, + grads, + lmbda, + aug_batch_size=512, + feed=None): + """ + Augment an adversary's substitute training set using the Jacobian + of a substitute model to generate new synthetic inputs. + See https://arxiv.org/abs/1602.02697 for more details. + See cleverhans_tutorials/mnist_blackbox.py for example use case + :param sess: TF session in which the substitute model is defined + :param x: input TF placeholder for the substitute model + :param X_sub_prev: substitute training data available to the adversary + at the previous iteration + :param Y_sub: substitute training labels available to the adversary + at the previous iteration + :param grads: Jacobian symbolic graph for the substitute + (should be generated using utils_tf.jacobian_graph) + :return: augmented substitute data (will need to be labeled by oracle) + """ + assert len(x.get_shape()) == len(np.shape(X_sub_prev)) + assert len(grads) >= np.max(Y_sub) + 1 + assert len(X_sub_prev) == len(Y_sub) + + aug_batch_size = min(aug_batch_size, X_sub_prev.shape[0]) + + # Prepare input_shape (outside loop) for feeding dictionary below + input_shape = list(x.get_shape()) + input_shape[0] = 1 + + # Create new numpy array for adversary training data + # with twice as many components on the first dimension. + X_sub = np.vstack([X_sub_prev, X_sub_prev]) + num_samples = X_sub_prev.shape[0] + + # Creating and processing as batch + for p_idxs in range(0, num_samples, aug_batch_size): + X_batch = X_sub_prev[p_idxs:p_idxs + aug_batch_size, ...] + feed_dict = {x: X_batch} + if feed is not None: + feed_dict.update(feed) + + # Compute sign matrix + grad_val = sess.run([tf.sign(grads)], feed_dict=feed_dict)[0] + + # Create new synthetic point in adversary substitute training set + for (indx, ind) in zip(range(p_idxs, p_idxs + X_batch.shape[0]), + range(X_batch.shape[0])): + X_sub[num_samples + indx] = ( + X_batch[ind] + lmbda * grad_val[Y_sub[indx], ind, ...]) + + # Return augmented training data (needs to be labeled afterwards) + return X_sub +
+ +
+ +
+
+ +
+
+ + + + \ No newline at end of file diff --git a/cleverhans_v3.1.0/docs/_modules/index.html b/cleverhans_v3.1.0/docs/_modules/index.html new file mode 100644 index 000000000..c42397cf1 --- /dev/null +++ b/cleverhans_v3.1.0/docs/_modules/index.html @@ -0,0 +1,114 @@ + + + + + + + + Overview: module code — CleverHans documentation + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/cleverhans_v3.1.0/docs/_sources/README.md.txt b/cleverhans_v3.1.0/docs/_sources/README.md.txt new file mode 100644 index 000000000..92ef60b43 --- /dev/null +++ b/cleverhans_v3.1.0/docs/_sources/README.md.txt @@ -0,0 +1,59 @@ +# Generate documentation + +To generate the documentation do: +`make github` + +The documentation files will be copied to the `cleverhans/docs` directory. + +### Preparation + +Please do: +`pip install sphinx` + +Add a `.nojekyll` file in the `cleverhans/docs` directory. When GitHub sees +a `.nojekyll` file, it serves the root `index.html` file. The `.nojekyll` file +indicates that we are not using Jekyll as our static site generator in this +repository. + +### Enable GitHub Pages for the GitHub repository + +1. Go to the repository on the GitHub website and make sure you are logged in. +2. Add a /docs directory to the master branch. Otherwise you do not get the + master branch /docs folder for the Source option in the drop-down list. +3. Click the Settings tab. You first go to the Options section. +4. Scroll down to the GitHub Pages section and choose the drop-down list under + Source. Note: Your choices will differ based on whether you’re in a User repo + or an Org repository. +5. To keep source and output HTML separate, choose master branch /docs folder + for Source. + +### Build Sphinx locally and publish on GitHub Pages + +We keep the source docsource and output docs separate, but still are able to +publish on GitHub Pages and preview builds locally. + +We have the following option in the Makefile: + +``` + github: + @make html + @cp -a _build/html/. ../docs +``` + +Thus, we can run `make github` from the `docsource` directory to generate a +local preview and move the docs where GitHub wants to serve them from. + +### Hacks + +If you cannot build the docs for attacks, uncomment +`import tensorflow_addons as tfa` in `cleverhans/attacks/spsa.py`. + +Otherwise: + +```angular2html +WARNING: autodoc: failed to import module 'attacks' from module 'cleverhans'; the following exception was raised: +cannot import name 'keras_tensor' +``` + +It is convenient to create a virtual environment to install all the specific +libraries (e.g. virutalen cleverhans). diff --git a/cleverhans_v3.1.0/docs/_sources/index.md.txt b/cleverhans_v3.1.0/docs/_sources/index.md.txt new file mode 100644 index 000000000..117b95383 --- /dev/null +++ b/cleverhans_v3.1.0/docs/_sources/index.md.txt @@ -0,0 +1,35 @@ +.. CleverHans documentation master file, created by + sphinx-quickstart on Wed Sep 20 15:14:07 2017. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + + WARNING: This file has a markdown suffix, but is in fact .rst + +CleverHans Documentation +====================================== + + +This documentation is auto-generated from the docstrings of modules of the current `master` branch of `cleverhans +`_. + +To get started, we recommend reading the `github readme +`_. Afterwards, you can learn more by looking at the following modules: + + +.. toctree:: + :maxdepth: 4 + + source/attacks + + + source/model + + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` + diff --git a/cleverhans_v3.1.0/docs/_sources/source/attacks.md.txt b/cleverhans_v3.1.0/docs/_sources/source/attacks.md.txt new file mode 100644 index 000000000..e242b5638 --- /dev/null +++ b/cleverhans_v3.1.0/docs/_sources/source/attacks.md.txt @@ -0,0 +1,8 @@ +`attacks` module +-------------------------- + +.. automodule:: cleverhans.attacks + :members: + :imported-members: + :undoc-members: + :show-inheritance: diff --git a/cleverhans_v3.1.0/docs/_sources/source/devtools.md.txt b/cleverhans_v3.1.0/docs/_sources/source/devtools.md.txt new file mode 100644 index 000000000..07f3c8277 --- /dev/null +++ b/cleverhans_v3.1.0/docs/_sources/source/devtools.md.txt @@ -0,0 +1,7 @@ +`devtools` module +------------------------ + +.. automodule:: cleverhans.devtools + :members: + :undoc-members: + :show-inheritance: diff --git a/cleverhans_v3.1.0/docs/_sources/source/future.md.txt b/cleverhans_v3.1.0/docs/_sources/source/future.md.txt new file mode 100644 index 000000000..df369011b --- /dev/null +++ b/cleverhans_v3.1.0/docs/_sources/source/future.md.txt @@ -0,0 +1,7 @@ +`future` module +------------------------ + +.. automodule:: cleverhans.devtools + :members: + :undoc-members: + :show-inheritance: diff --git a/cleverhans_v3.1.0/docs/_sources/source/model.md.txt b/cleverhans_v3.1.0/docs/_sources/source/model.md.txt new file mode 100644 index 000000000..67f01a1ef --- /dev/null +++ b/cleverhans_v3.1.0/docs/_sources/source/model.md.txt @@ -0,0 +1,7 @@ +`model` module +------------------------ + +.. automodule:: cleverhans.model + :members: + :undoc-members: + :show-inheritance: diff --git a/cleverhans_v3.1.0/docs/_static/alabaster.css b/cleverhans_v3.1.0/docs/_static/alabaster.css new file mode 100644 index 000000000..0eddaeb07 --- /dev/null +++ b/cleverhans_v3.1.0/docs/_static/alabaster.css @@ -0,0 +1,701 @@ +@import url("basic.css"); + +/* -- page layout ----------------------------------------------------------- */ + +body { + font-family: Georgia, serif; + font-size: 17px; + background-color: #fff; + color: #000; + margin: 0; + padding: 0; +} + + +div.document { + width: 940px; + margin: 30px auto 0 auto; +} + +div.documentwrapper { + float: left; + width: 100%; +} + +div.bodywrapper { + margin: 0 0 0 220px; +} + +div.sphinxsidebar { + width: 220px; + font-size: 14px; + line-height: 1.5; +} + +hr { + border: 1px solid #B1B4B6; +} + +div.body { + background-color: #fff; + color: #3E4349; + padding: 0 30px 0 30px; +} + +div.body > .section { + text-align: left; +} + +div.footer { + width: 940px; + margin: 20px auto 30px auto; + font-size: 14px; + color: #888; + text-align: right; +} + +div.footer a { + color: #888; +} + +p.caption { + font-family: inherit; + font-size: inherit; +} + + +div.relations { + display: none; +} + + +div.sphinxsidebar a { + color: #444; + text-decoration: none; + border-bottom: 1px dotted #999; +} + +div.sphinxsidebar a:hover { + border-bottom: 1px solid #999; +} + +div.sphinxsidebarwrapper { + padding: 18px 10px; +} + +div.sphinxsidebarwrapper p.logo { + padding: 0; + margin: -10px 0 0 0px; + text-align: center; +} + +div.sphinxsidebarwrapper h1.logo { + margin-top: -10px; + text-align: center; + margin-bottom: 5px; + text-align: left; +} + +div.sphinxsidebarwrapper h1.logo-name { + margin-top: 0px; +} + +div.sphinxsidebarwrapper p.blurb { + margin-top: 0; + font-style: normal; +} + +div.sphinxsidebar h3, +div.sphinxsidebar h4 { + font-family: Georgia, serif; + color: #444; + font-size: 24px; + font-weight: normal; + margin: 0 0 5px 0; + padding: 0; +} + +div.sphinxsidebar h4 { + font-size: 20px; +} + +div.sphinxsidebar h3 a { + color: #444; +} + +div.sphinxsidebar p.logo a, +div.sphinxsidebar h3 a, +div.sphinxsidebar p.logo a:hover, +div.sphinxsidebar h3 a:hover { + border: none; +} + +div.sphinxsidebar p { + color: #555; + margin: 10px 0; +} + +div.sphinxsidebar ul { + margin: 10px 0; + padding: 0; + color: #000; +} + +div.sphinxsidebar ul li.toctree-l1 > a { + font-size: 120%; +} + +div.sphinxsidebar ul li.toctree-l2 > a { + font-size: 110%; +} + +div.sphinxsidebar input { + border: 1px solid #CCC; + font-family: Georgia, serif; + font-size: 1em; +} + +div.sphinxsidebar hr { + border: none; + height: 1px; + color: #AAA; + background: #AAA; + + text-align: left; + margin-left: 0; + width: 50%; +} + +div.sphinxsidebar .badge { + border-bottom: none; +} + +div.sphinxsidebar .badge:hover { + border-bottom: none; +} + +/* To address an issue with donation coming after search */ +div.sphinxsidebar h3.donation { + margin-top: 10px; +} + +/* -- body styles ----------------------------------------------------------- */ + +a { + color: #004B6B; + text-decoration: underline; +} + +a:hover { + color: #6D4100; + text-decoration: underline; +} + +div.body h1, +div.body h2, +div.body h3, +div.body h4, +div.body h5, +div.body h6 { + font-family: Georgia, serif; + font-weight: normal; + margin: 30px 0px 10px 0px; + padding: 0; +} + +div.body h1 { margin-top: 0; padding-top: 0; font-size: 240%; } +div.body h2 { font-size: 180%; } +div.body h3 { font-size: 150%; } +div.body h4 { font-size: 130%; } +div.body h5 { font-size: 100%; } +div.body h6 { font-size: 100%; } + +a.headerlink { + color: #DDD; + padding: 0 4px; + text-decoration: none; +} + +a.headerlink:hover { + color: #444; + background: #EAEAEA; +} + +div.body p, div.body dd, div.body li { + line-height: 1.4em; +} + +div.admonition { + margin: 20px 0px; + padding: 10px 30px; + background-color: #EEE; + border: 1px solid #CCC; +} + +div.admonition tt.xref, div.admonition code.xref, div.admonition a tt { + background-color: #FBFBFB; + border-bottom: 1px solid #fafafa; +} + +div.admonition p.admonition-title { + font-family: Georgia, serif; + font-weight: normal; + font-size: 24px; + margin: 0 0 10px 0; + padding: 0; + line-height: 1; +} + +div.admonition p.last { + margin-bottom: 0; +} + +div.highlight { + background-color: #fff; +} + +dt:target, .highlight { + background: #FAF3E8; +} + +div.warning { + background-color: #FCC; + border: 1px solid #FAA; +} + +div.danger { + background-color: #FCC; + border: 1px solid #FAA; + -moz-box-shadow: 2px 2px 4px #D52C2C; + -webkit-box-shadow: 2px 2px 4px #D52C2C; + box-shadow: 2px 2px 4px #D52C2C; +} + +div.error { + background-color: #FCC; + border: 1px solid #FAA; + -moz-box-shadow: 2px 2px 4px #D52C2C; + -webkit-box-shadow: 2px 2px 4px #D52C2C; + box-shadow: 2px 2px 4px #D52C2C; +} + +div.caution { + background-color: #FCC; + border: 1px solid #FAA; +} + +div.attention { + background-color: #FCC; + border: 1px solid #FAA; +} + +div.important { + background-color: #EEE; + border: 1px solid #CCC; +} + +div.note { + background-color: #EEE; + border: 1px solid #CCC; +} + +div.tip { + background-color: #EEE; + border: 1px solid #CCC; +} + +div.hint { + background-color: #EEE; + border: 1px solid #CCC; +} + +div.seealso { + background-color: #EEE; + border: 1px solid #CCC; +} + +div.topic { + background-color: #EEE; +} + +p.admonition-title { + display: inline; +} + +p.admonition-title:after { + content: ":"; +} + +pre, tt, code { + font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace; + font-size: 0.9em; +} + +.hll { + background-color: #FFC; + margin: 0 -12px; + padding: 0 12px; + display: block; +} + +img.screenshot { +} + +tt.descname, tt.descclassname, code.descname, code.descclassname { + font-size: 0.95em; +} + +tt.descname, code.descname { + padding-right: 0.08em; +} + +img.screenshot { + -moz-box-shadow: 2px 2px 4px #EEE; + -webkit-box-shadow: 2px 2px 4px #EEE; + box-shadow: 2px 2px 4px #EEE; +} + +table.docutils { + border: 1px solid #888; + -moz-box-shadow: 2px 2px 4px #EEE; + -webkit-box-shadow: 2px 2px 4px #EEE; + box-shadow: 2px 2px 4px #EEE; +} + +table.docutils td, table.docutils th { + border: 1px solid #888; + padding: 0.25em 0.7em; +} + +table.field-list, table.footnote { + border: none; + -moz-box-shadow: none; + -webkit-box-shadow: none; + box-shadow: none; +} + +table.footnote { + margin: 15px 0; + width: 100%; + border: 1px solid #EEE; + background: #FDFDFD; + font-size: 0.9em; +} + +table.footnote + table.footnote { + margin-top: -15px; + border-top: none; +} + +table.field-list th { + padding: 0 0.8em 0 0; +} + +table.field-list td { + padding: 0; +} + +table.field-list p { + margin-bottom: 0.8em; +} + +/* Cloned from + * https://github.com/sphinx-doc/sphinx/commit/ef60dbfce09286b20b7385333d63a60321784e68 + */ +.field-name { + -moz-hyphens: manual; + -ms-hyphens: manual; + -webkit-hyphens: manual; + hyphens: manual; +} + +table.footnote td.label { + width: .1px; + padding: 0.3em 0 0.3em 0.5em; +} + +table.footnote td { + padding: 0.3em 0.5em; +} + +dl { + margin: 0; + padding: 0; +} + +dl dd { + margin-left: 30px; +} + +blockquote { + margin: 0 0 0 30px; + padding: 0; +} + +ul, ol { + /* Matches the 30px from the narrow-screen "li > ul" selector below */ + margin: 10px 0 10px 30px; + padding: 0; +} + +pre { + background: #EEE; + padding: 7px 30px; + margin: 15px 0px; + line-height: 1.3em; +} + +div.viewcode-block:target { + background: #ffd; +} + +dl pre, blockquote pre, li pre { + margin-left: 0; + padding-left: 30px; +} + +tt, code { + background-color: #ecf0f3; + color: #222; + /* padding: 1px 2px; */ +} + +tt.xref, code.xref, a tt { + background-color: #FBFBFB; + border-bottom: 1px solid #fff; +} + +a.reference { + text-decoration: none; + border-bottom: 1px dotted #004B6B; +} + +/* Don't put an underline on images */ +a.image-reference, a.image-reference:hover { + border-bottom: none; +} + +a.reference:hover { + border-bottom: 1px solid #6D4100; +} + +a.footnote-reference { + text-decoration: none; + font-size: 0.7em; + vertical-align: top; + border-bottom: 1px dotted #004B6B; +} + +a.footnote-reference:hover { + border-bottom: 1px solid #6D4100; +} + +a:hover tt, a:hover code { + background: #EEE; +} + + +@media screen and (max-width: 870px) { + + div.sphinxsidebar { + display: none; + } + + div.document { + width: 100%; + + } + + div.documentwrapper { + margin-left: 0; + margin-top: 0; + margin-right: 0; + margin-bottom: 0; + } + + div.bodywrapper { + margin-top: 0; + margin-right: 0; + margin-bottom: 0; + margin-left: 0; + } + + ul { + margin-left: 0; + } + + li > ul { + /* Matches the 30px from the "ul, ol" selector above */ + margin-left: 30px; + } + + .document { + width: auto; + } + + .footer { + width: auto; + } + + .bodywrapper { + margin: 0; + } + + .footer { + width: auto; + } + + .github { + display: none; + } + + + +} + + + +@media screen and (max-width: 875px) { + + body { + margin: 0; + padding: 20px 30px; + } + + div.documentwrapper { + float: none; + background: #fff; + } + + div.sphinxsidebar { + display: block; + float: none; + width: 102.5%; + margin: 50px -30px -20px -30px; + padding: 10px 20px; + background: #333; + color: #FFF; + } + + div.sphinxsidebar h3, div.sphinxsidebar h4, div.sphinxsidebar p, + div.sphinxsidebar h3 a { + color: #fff; + } + + div.sphinxsidebar a { + color: #AAA; + } + + div.sphinxsidebar p.logo { + display: none; + } + + div.document { + width: 100%; + margin: 0; + } + + div.footer { + display: none; + } + + div.bodywrapper { + margin: 0; + } + + div.body { + min-height: 0; + padding: 0; + } + + .rtd_doc_footer { + display: none; + } + + .document { + width: auto; + } + + .footer { + width: auto; + } + + .footer { + width: auto; + } + + .github { + display: none; + } +} + + +/* misc. */ + +.revsys-inline { + display: none!important; +} + +/* Make nested-list/multi-paragraph items look better in Releases changelog + * pages. Without this, docutils' magical list fuckery causes inconsistent + * formatting between different release sub-lists. + */ +div#changelog > div.section > ul > li > p:only-child { + margin-bottom: 0; +} + +/* Hide fugly table cell borders in ..bibliography:: directive output */ +table.docutils.citation, table.docutils.citation td, table.docutils.citation th { + border: none; + /* Below needed in some edge cases; if not applied, bottom shadows appear */ + -moz-box-shadow: none; + -webkit-box-shadow: none; + box-shadow: none; +} + + +/* relbar */ + +.related { + line-height: 30px; + width: 100%; + font-size: 0.9rem; +} + +.related.top { + border-bottom: 1px solid #EEE; + margin-bottom: 20px; +} + +.related.bottom { + border-top: 1px solid #EEE; +} + +.related ul { + padding: 0; + margin: 0; + list-style: none; +} + +.related li { + display: inline; +} + +nav#rellinks { + float: right; +} + +nav#rellinks li+li:before { + content: "|"; +} + +nav#breadcrumbs li+li:before { + content: "\00BB"; +} + +/* Hide certain items when printing */ +@media print { + div.related { + display: none; + } +} \ No newline at end of file diff --git a/cleverhans_v3.1.0/docs/_static/basic.css b/cleverhans_v3.1.0/docs/_static/basic.css new file mode 100644 index 000000000..be19270e4 --- /dev/null +++ b/cleverhans_v3.1.0/docs/_static/basic.css @@ -0,0 +1,856 @@ +/* + * basic.css + * ~~~~~~~~~ + * + * Sphinx stylesheet -- basic theme. + * + * :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/* -- main layout ----------------------------------------------------------- */ + +div.clearer { + clear: both; +} + +div.section::after { + display: block; + content: ''; + clear: left; +} + +/* -- relbar ---------------------------------------------------------------- */ + +div.related { + width: 100%; + font-size: 90%; +} + +div.related h3 { + display: none; +} + +div.related ul { + margin: 0; + padding: 0 0 0 10px; + list-style: none; +} + +div.related li { + display: inline; +} + +div.related li.right { + float: right; + margin-right: 5px; +} + +/* -- sidebar --------------------------------------------------------------- */ + +div.sphinxsidebarwrapper { + padding: 10px 5px 0 10px; +} + +div.sphinxsidebar { + float: left; + width: 230px; + margin-left: -100%; + font-size: 90%; + word-wrap: break-word; + overflow-wrap : break-word; +} + +div.sphinxsidebar ul { + list-style: none; +} + +div.sphinxsidebar ul ul, +div.sphinxsidebar ul.want-points { + margin-left: 20px; + list-style: square; +} + +div.sphinxsidebar ul ul { + margin-top: 0; + margin-bottom: 0; +} + +div.sphinxsidebar form { + margin-top: 10px; +} + +div.sphinxsidebar input { + border: 1px solid #98dbcc; + font-family: sans-serif; + font-size: 1em; +} + +div.sphinxsidebar #searchbox form.search { + overflow: hidden; +} + +div.sphinxsidebar #searchbox input[type="text"] { + float: left; + width: 80%; + padding: 0.25em; + box-sizing: border-box; +} + +div.sphinxsidebar #searchbox input[type="submit"] { + float: left; + width: 20%; + border-left: none; + padding: 0.25em; + box-sizing: border-box; +} + + +img { + border: 0; + max-width: 100%; +} + +/* -- search page ----------------------------------------------------------- */ + +ul.search { + margin: 10px 0 0 20px; + padding: 0; +} + +ul.search li { + padding: 5px 0 5px 20px; + background-image: url(file.png); + background-repeat: no-repeat; + background-position: 0 7px; +} + +ul.search li a { + font-weight: bold; +} + +ul.search li div.context { + color: #888; + margin: 2px 0 0 30px; + text-align: left; +} + +ul.keywordmatches li.goodmatch a { + font-weight: bold; +} + +/* -- index page ------------------------------------------------------------ */ + +table.contentstable { + width: 90%; + margin-left: auto; + margin-right: auto; +} + +table.contentstable p.biglink { + line-height: 150%; +} + +a.biglink { + font-size: 1.3em; +} + +span.linkdescr { + font-style: italic; + padding-top: 5px; + font-size: 90%; +} + +/* -- general index --------------------------------------------------------- */ + +table.indextable { + width: 100%; +} + +table.indextable td { + text-align: left; + vertical-align: top; +} + +table.indextable ul { + margin-top: 0; + margin-bottom: 0; + list-style-type: none; +} + +table.indextable > tbody > tr > td > ul { + padding-left: 0em; +} + +table.indextable tr.pcap { + height: 10px; +} + +table.indextable tr.cap { + margin-top: 10px; + background-color: #f2f2f2; +} + +img.toggler { + margin-right: 3px; + margin-top: 3px; + cursor: pointer; +} + +div.modindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +div.genindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +/* -- domain module index --------------------------------------------------- */ + +table.modindextable td { + padding: 2px; + border-collapse: collapse; +} + +/* -- general body styles --------------------------------------------------- */ + +div.body { + min-width: 450px; + max-width: 800px; +} + +div.body p, div.body dd, div.body li, div.body blockquote { + -moz-hyphens: auto; + -ms-hyphens: auto; + -webkit-hyphens: auto; + hyphens: auto; +} + +a.headerlink { + visibility: hidden; +} + +a.brackets:before, +span.brackets > a:before{ + content: "["; +} + +a.brackets:after, +span.brackets > a:after { + content: "]"; +} + +h1:hover > a.headerlink, +h2:hover > a.headerlink, +h3:hover > a.headerlink, +h4:hover > a.headerlink, +h5:hover > a.headerlink, +h6:hover > a.headerlink, +dt:hover > a.headerlink, +caption:hover > a.headerlink, +p.caption:hover > a.headerlink, +div.code-block-caption:hover > a.headerlink { + visibility: visible; +} + +div.body p.caption { + text-align: inherit; +} + +div.body td { + text-align: left; +} + +.first { + margin-top: 0 !important; +} + +p.rubric { + margin-top: 30px; + font-weight: bold; +} + +img.align-left, .figure.align-left, object.align-left { + clear: left; + float: left; + margin-right: 1em; +} + +img.align-right, .figure.align-right, object.align-right { + clear: right; + float: right; + margin-left: 1em; +} + +img.align-center, .figure.align-center, object.align-center { + display: block; + margin-left: auto; + margin-right: auto; +} + +img.align-default, .figure.align-default { + display: block; + margin-left: auto; + margin-right: auto; +} + +.align-left { + text-align: left; +} + +.align-center { + text-align: center; +} + +.align-default { + text-align: center; +} + +.align-right { + text-align: right; +} + +/* -- sidebars -------------------------------------------------------------- */ + +div.sidebar { + margin: 0 0 0.5em 1em; + border: 1px solid #ddb; + padding: 7px; + background-color: #ffe; + width: 40%; + float: right; + clear: right; + overflow-x: auto; +} + +p.sidebar-title { + font-weight: bold; +} + +div.admonition, div.topic, blockquote { + clear: left; +} + +/* -- topics ---------------------------------------------------------------- */ + +div.topic { + border: 1px solid #ccc; + padding: 7px; + margin: 10px 0 10px 0; +} + +p.topic-title { + font-size: 1.1em; + font-weight: bold; + margin-top: 10px; +} + +/* -- admonitions ----------------------------------------------------------- */ + +div.admonition { + margin-top: 10px; + margin-bottom: 10px; + padding: 7px; +} + +div.admonition dt { + font-weight: bold; +} + +p.admonition-title { + margin: 0px 10px 5px 0px; + font-weight: bold; +} + +div.body p.centered { + text-align: center; + margin-top: 25px; +} + +/* -- content of sidebars/topics/admonitions -------------------------------- */ + +div.sidebar > :last-child, +div.topic > :last-child, +div.admonition > :last-child { + margin-bottom: 0; +} + +div.sidebar::after, +div.topic::after, +div.admonition::after, +blockquote::after { + display: block; + content: ''; + clear: both; +} + +/* -- tables ---------------------------------------------------------------- */ + +table.docutils { + margin-top: 10px; + margin-bottom: 10px; + border: 0; + border-collapse: collapse; +} + +table.align-center { + margin-left: auto; + margin-right: auto; +} + +table.align-default { + margin-left: auto; + margin-right: auto; +} + +table caption span.caption-number { + font-style: italic; +} + +table caption span.caption-text { +} + +table.docutils td, table.docutils th { + padding: 1px 8px 1px 5px; + border-top: 0; + border-left: 0; + border-right: 0; + border-bottom: 1px solid #aaa; +} + +table.footnote td, table.footnote th { + border: 0 !important; +} + +th { + text-align: left; + padding-right: 5px; +} + +table.citation { + border-left: solid 1px gray; + margin-left: 1px; +} + +table.citation td { + border-bottom: none; +} + +th > :first-child, +td > :first-child { + margin-top: 0px; +} + +th > :last-child, +td > :last-child { + margin-bottom: 0px; +} + +/* -- figures --------------------------------------------------------------- */ + +div.figure { + margin: 0.5em; + padding: 0.5em; +} + +div.figure p.caption { + padding: 0.3em; +} + +div.figure p.caption span.caption-number { + font-style: italic; +} + +div.figure p.caption span.caption-text { +} + +/* -- field list styles ----------------------------------------------------- */ + +table.field-list td, table.field-list th { + border: 0 !important; +} + +.field-list ul { + margin: 0; + padding-left: 1em; +} + +.field-list p { + margin: 0; +} + +.field-name { + -moz-hyphens: manual; + -ms-hyphens: manual; + -webkit-hyphens: manual; + hyphens: manual; +} + +/* -- hlist styles ---------------------------------------------------------- */ + +table.hlist { + margin: 1em 0; +} + +table.hlist td { + vertical-align: top; +} + + +/* -- other body styles ----------------------------------------------------- */ + +ol.arabic { + list-style: decimal; +} + +ol.loweralpha { + list-style: lower-alpha; +} + +ol.upperalpha { + list-style: upper-alpha; +} + +ol.lowerroman { + list-style: lower-roman; +} + +ol.upperroman { + list-style: upper-roman; +} + +:not(li) > ol > li:first-child > :first-child, +:not(li) > ul > li:first-child > :first-child { + margin-top: 0px; +} + +:not(li) > ol > li:last-child > :last-child, +:not(li) > ul > li:last-child > :last-child { + margin-bottom: 0px; +} + +ol.simple ol p, +ol.simple ul p, +ul.simple ol p, +ul.simple ul p { + margin-top: 0; +} + +ol.simple > li:not(:first-child) > p, +ul.simple > li:not(:first-child) > p { + margin-top: 0; +} + +ol.simple p, +ul.simple p { + margin-bottom: 0; +} + +dl.footnote > dt, +dl.citation > dt { + float: left; + margin-right: 0.5em; +} + +dl.footnote > dd, +dl.citation > dd { + margin-bottom: 0em; +} + +dl.footnote > dd:after, +dl.citation > dd:after { + content: ""; + clear: both; +} + +dl.field-list { + display: grid; + grid-template-columns: fit-content(30%) auto; +} + +dl.field-list > dt { + font-weight: bold; + word-break: break-word; + padding-left: 0.5em; + padding-right: 5px; +} + +dl.field-list > dt:after { + content: ":"; +} + +dl.field-list > dd { + padding-left: 0.5em; + margin-top: 0em; + margin-left: 0em; + margin-bottom: 0em; +} + +dl { + margin-bottom: 15px; +} + +dd > :first-child { + margin-top: 0px; +} + +dd ul, dd table { + margin-bottom: 10px; +} + +dd { + margin-top: 3px; + margin-bottom: 10px; + margin-left: 30px; +} + +dl > dd:last-child, +dl > dd:last-child > :last-child { + margin-bottom: 0; +} + +dt:target, span.highlighted { + background-color: #fbe54e; +} + +rect.highlighted { + fill: #fbe54e; +} + +dl.glossary dt { + font-weight: bold; + font-size: 1.1em; +} + +.optional { + font-size: 1.3em; +} + +.sig-paren { + font-size: larger; +} + +.versionmodified { + font-style: italic; +} + +.system-message { + background-color: #fda; + padding: 5px; + border: 3px solid red; +} + +.footnote:target { + background-color: #ffa; +} + +.line-block { + display: block; + margin-top: 1em; + margin-bottom: 1em; +} + +.line-block .line-block { + margin-top: 0; + margin-bottom: 0; + margin-left: 1.5em; +} + +.guilabel, .menuselection { + font-family: sans-serif; +} + +.accelerator { + text-decoration: underline; +} + +.classifier { + font-style: oblique; +} + +.classifier:before { + font-style: normal; + margin: 0.5em; + content: ":"; +} + +abbr, acronym { + border-bottom: dotted 1px; + cursor: help; +} + +/* -- code displays --------------------------------------------------------- */ + +pre { + overflow: auto; + overflow-y: hidden; /* fixes display issues on Chrome browsers */ +} + +pre, div[class*="highlight-"] { + clear: both; +} + +span.pre { + -moz-hyphens: none; + -ms-hyphens: none; + -webkit-hyphens: none; + hyphens: none; +} + +div[class*="highlight-"] { + margin: 1em 0; +} + +td.linenos pre { + border: 0; + background-color: transparent; + color: #aaa; +} + +table.highlighttable { + display: block; +} + +table.highlighttable tbody { + display: block; +} + +table.highlighttable tr { + display: flex; +} + +table.highlighttable td { + margin: 0; + padding: 0; +} + +table.highlighttable td.linenos { + padding-right: 0.5em; +} + +table.highlighttable td.code { + flex: 1; + overflow: hidden; +} + +.highlight .hll { + display: block; +} + +div.highlight pre, +table.highlighttable pre { + margin: 0; +} + +div.code-block-caption + div { + margin-top: 0; +} + +div.code-block-caption { + margin-top: 1em; + padding: 2px 5px; + font-size: small; +} + +div.code-block-caption code { + background-color: transparent; +} + +table.highlighttable td.linenos, +span.linenos, +div.doctest > div.highlight span.gp { /* gp: Generic.Prompt */ + user-select: none; +} + +div.code-block-caption span.caption-number { + padding: 0.1em 0.3em; + font-style: italic; +} + +div.code-block-caption span.caption-text { +} + +div.literal-block-wrapper { + margin: 1em 0; +} + +code.descname { + background-color: transparent; + font-weight: bold; + font-size: 1.2em; +} + +code.descclassname { + background-color: transparent; +} + +code.xref, a code { + background-color: transparent; + font-weight: bold; +} + +h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { + background-color: transparent; +} + +.viewcode-link { + float: right; +} + +.viewcode-back { + float: right; + font-family: sans-serif; +} + +div.viewcode-block:target { + margin: -1px -10px; + padding: 0 10px; +} + +/* -- math display ---------------------------------------------------------- */ + +img.math { + vertical-align: middle; +} + +div.body div.math p { + text-align: center; +} + +span.eqno { + float: right; +} + +span.eqno a.headerlink { + position: absolute; + z-index: 1; +} + +div.math:hover a.headerlink { + visibility: visible; +} + +/* -- printout stylesheet --------------------------------------------------- */ + +@media print { + div.document, + div.documentwrapper, + div.bodywrapper { + margin: 0 !important; + width: 100%; + } + + div.sphinxsidebar, + div.related, + div.footer, + #top-link { + display: none; + } +} \ No newline at end of file diff --git a/cleverhans_v3.1.0/docs/_static/custom.css b/cleverhans_v3.1.0/docs/_static/custom.css new file mode 100644 index 000000000..2a924f1d6 --- /dev/null +++ b/cleverhans_v3.1.0/docs/_static/custom.css @@ -0,0 +1 @@ +/* This file intentionally left blank. */ diff --git a/cleverhans_v3.1.0/docs/_static/doctools.js b/cleverhans_v3.1.0/docs/_static/doctools.js new file mode 100644 index 000000000..144884ea6 --- /dev/null +++ b/cleverhans_v3.1.0/docs/_static/doctools.js @@ -0,0 +1,316 @@ +/* + * doctools.js + * ~~~~~~~~~~~ + * + * Sphinx JavaScript utilities for all documentation. + * + * :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/** + * select a different prefix for underscore + */ +$u = _.noConflict(); + +/** + * make the code below compatible with browsers without + * an installed firebug like debugger +if (!window.console || !console.firebug) { + var names = ["log", "debug", "info", "warn", "error", "assert", "dir", + "dirxml", "group", "groupEnd", "time", "timeEnd", "count", "trace", + "profile", "profileEnd"]; + window.console = {}; + for (var i = 0; i < names.length; ++i) + window.console[names[i]] = function() {}; +} + */ + +/** + * small helper function to urldecode strings + */ +jQuery.urldecode = function(x) { + return decodeURIComponent(x).replace(/\+/g, ' '); +}; + +/** + * small helper function to urlencode strings + */ +jQuery.urlencode = encodeURIComponent; + +/** + * This function returns the parsed url parameters of the + * current request. Multiple values per key are supported, + * it will always return arrays of strings for the value parts. + */ +jQuery.getQueryParameters = function(s) { + if (typeof s === 'undefined') + s = document.location.search; + var parts = s.substr(s.indexOf('?') + 1).split('&'); + var result = {}; + for (var i = 0; i < parts.length; i++) { + var tmp = parts[i].split('=', 2); + var key = jQuery.urldecode(tmp[0]); + var value = jQuery.urldecode(tmp[1]); + if (key in result) + result[key].push(value); + else + result[key] = [value]; + } + return result; +}; + +/** + * highlight a given string on a jquery object by wrapping it in + * span elements with the given class name. + */ +jQuery.fn.highlightText = function(text, className) { + function highlight(node, addItems) { + if (node.nodeType === 3) { + var val = node.nodeValue; + var pos = val.toLowerCase().indexOf(text); + if (pos >= 0 && + !jQuery(node.parentNode).hasClass(className) && + !jQuery(node.parentNode).hasClass("nohighlight")) { + var span; + var isInSVG = jQuery(node).closest("body, svg, foreignObject").is("svg"); + if (isInSVG) { + span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); + } else { + span = document.createElement("span"); + span.className = className; + } + span.appendChild(document.createTextNode(val.substr(pos, text.length))); + node.parentNode.insertBefore(span, node.parentNode.insertBefore( + document.createTextNode(val.substr(pos + text.length)), + node.nextSibling)); + node.nodeValue = val.substr(0, pos); + if (isInSVG) { + var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect"); + var bbox = node.parentElement.getBBox(); + rect.x.baseVal.value = bbox.x; + rect.y.baseVal.value = bbox.y; + rect.width.baseVal.value = bbox.width; + rect.height.baseVal.value = bbox.height; + rect.setAttribute('class', className); + addItems.push({ + "parent": node.parentNode, + "target": rect}); + } + } + } + else if (!jQuery(node).is("button, select, textarea")) { + jQuery.each(node.childNodes, function() { + highlight(this, addItems); + }); + } + } + var addItems = []; + var result = this.each(function() { + highlight(this, addItems); + }); + for (var i = 0; i < addItems.length; ++i) { + jQuery(addItems[i].parent).before(addItems[i].target); + } + return result; +}; + +/* + * backward compatibility for jQuery.browser + * This will be supported until firefox bug is fixed. + */ +if (!jQuery.browser) { + jQuery.uaMatch = function(ua) { + ua = ua.toLowerCase(); + + var match = /(chrome)[ \/]([\w.]+)/.exec(ua) || + /(webkit)[ \/]([\w.]+)/.exec(ua) || + /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) || + /(msie) ([\w.]+)/.exec(ua) || + ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || + []; + + return { + browser: match[ 1 ] || "", + version: match[ 2 ] || "0" + }; + }; + jQuery.browser = {}; + jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true; +} + +/** + * Small JavaScript module for the documentation. + */ +var Documentation = { + + init : function() { + this.fixFirefoxAnchorBug(); + this.highlightSearchWords(); + this.initIndexTable(); + if (DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) { + this.initOnKeyListeners(); + } + }, + + /** + * i18n support + */ + TRANSLATIONS : {}, + PLURAL_EXPR : function(n) { return n === 1 ? 0 : 1; }, + LOCALE : 'unknown', + + // gettext and ngettext don't access this so that the functions + // can safely bound to a different name (_ = Documentation.gettext) + gettext : function(string) { + var translated = Documentation.TRANSLATIONS[string]; + if (typeof translated === 'undefined') + return string; + return (typeof translated === 'string') ? translated : translated[0]; + }, + + ngettext : function(singular, plural, n) { + var translated = Documentation.TRANSLATIONS[singular]; + if (typeof translated === 'undefined') + return (n == 1) ? singular : plural; + return translated[Documentation.PLURALEXPR(n)]; + }, + + addTranslations : function(catalog) { + for (var key in catalog.messages) + this.TRANSLATIONS[key] = catalog.messages[key]; + this.PLURAL_EXPR = new Function('n', 'return +(' + catalog.plural_expr + ')'); + this.LOCALE = catalog.locale; + }, + + /** + * add context elements like header anchor links + */ + addContextElements : function() { + $('div[id] > :header:first').each(function() { + $('\u00B6'). + attr('href', '#' + this.id). + attr('title', _('Permalink to this headline')). + appendTo(this); + }); + $('dt[id]').each(function() { + $('\u00B6'). + attr('href', '#' + this.id). + attr('title', _('Permalink to this definition')). + appendTo(this); + }); + }, + + /** + * workaround a firefox stupidity + * see: https://bugzilla.mozilla.org/show_bug.cgi?id=645075 + */ + fixFirefoxAnchorBug : function() { + if (document.location.hash && $.browser.mozilla) + window.setTimeout(function() { + document.location.href += ''; + }, 10); + }, + + /** + * highlight the search words provided in the url in the text + */ + highlightSearchWords : function() { + var params = $.getQueryParameters(); + var terms = (params.highlight) ? params.highlight[0].split(/\s+/) : []; + if (terms.length) { + var body = $('div.body'); + if (!body.length) { + body = $('body'); + } + window.setTimeout(function() { + $.each(terms, function() { + body.highlightText(this.toLowerCase(), 'highlighted'); + }); + }, 10); + $('') + .appendTo($('#searchbox')); + } + }, + + /** + * init the domain index toggle buttons + */ + initIndexTable : function() { + var togglers = $('img.toggler').click(function() { + var src = $(this).attr('src'); + var idnum = $(this).attr('id').substr(7); + $('tr.cg-' + idnum).toggle(); + if (src.substr(-9) === 'minus.png') + $(this).attr('src', src.substr(0, src.length-9) + 'plus.png'); + else + $(this).attr('src', src.substr(0, src.length-8) + 'minus.png'); + }).css('display', ''); + if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) { + togglers.click(); + } + }, + + /** + * helper function to hide the search marks again + */ + hideSearchWords : function() { + $('#searchbox .highlight-link').fadeOut(300); + $('span.highlighted').removeClass('highlighted'); + }, + + /** + * make the url absolute + */ + makeURL : function(relativeURL) { + return DOCUMENTATION_OPTIONS.URL_ROOT + '/' + relativeURL; + }, + + /** + * get the current relative url + */ + getCurrentURL : function() { + var path = document.location.pathname; + var parts = path.split(/\//); + $.each(DOCUMENTATION_OPTIONS.URL_ROOT.split(/\//), function() { + if (this === '..') + parts.pop(); + }); + var url = parts.join('/'); + return path.substring(url.lastIndexOf('/') + 1, path.length - 1); + }, + + initOnKeyListeners: function() { + $(document).keydown(function(event) { + var activeElementType = document.activeElement.tagName; + // don't navigate when in search box, textarea, dropdown or button + if (activeElementType !== 'TEXTAREA' && activeElementType !== 'INPUT' && activeElementType !== 'SELECT' + && activeElementType !== 'BUTTON' && !event.altKey && !event.ctrlKey && !event.metaKey + && !event.shiftKey) { + switch (event.keyCode) { + case 37: // left + var prevHref = $('link[rel="prev"]').prop('href'); + if (prevHref) { + window.location.href = prevHref; + return false; + } + case 39: // right + var nextHref = $('link[rel="next"]').prop('href'); + if (nextHref) { + window.location.href = nextHref; + return false; + } + } + } + }); + } +}; + +// quick alias for translations +_ = Documentation.gettext; + +$(document).ready(function() { + Documentation.init(); +}); diff --git a/cleverhans_v3.1.0/docs/_static/documentation_options.js b/cleverhans_v3.1.0/docs/_static/documentation_options.js new file mode 100644 index 000000000..2fa8c97fe --- /dev/null +++ b/cleverhans_v3.1.0/docs/_static/documentation_options.js @@ -0,0 +1,12 @@ +var DOCUMENTATION_OPTIONS = { + URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'), + VERSION: '', + LANGUAGE: 'None', + COLLAPSE_INDEX: false, + BUILDER: 'html', + FILE_SUFFIX: '.html', + LINK_SUFFIX: '.html', + HAS_SOURCE: true, + SOURCELINK_SUFFIX: '.txt', + NAVIGATION_WITH_KEYS: false +}; \ No newline at end of file diff --git a/cleverhans_v3.1.0/docs/_static/file.png b/cleverhans_v3.1.0/docs/_static/file.png new file mode 100644 index 000000000..a858a410e Binary files /dev/null and b/cleverhans_v3.1.0/docs/_static/file.png differ diff --git a/cleverhans_v3.1.0/docs/_static/jquery-3.5.1.js b/cleverhans_v3.1.0/docs/_static/jquery-3.5.1.js new file mode 100644 index 000000000..50937333b --- /dev/null +++ b/cleverhans_v3.1.0/docs/_static/jquery-3.5.1.js @@ -0,0 +1,10872 @@ +/*! + * jQuery JavaScript Library v3.5.1 + * https://jquery.com/ + * + * Includes Sizzle.js + * https://sizzlejs.com/ + * + * Copyright JS Foundation and other contributors + * Released under the MIT license + * https://jquery.org/license + * + * Date: 2020-05-04T22:49Z + */ +( function( global, factory ) { + + "use strict"; + + if ( typeof module === "object" && typeof module.exports === "object" ) { + + // For CommonJS and CommonJS-like environments where a proper `window` + // is present, execute the factory and get jQuery. + // For environments that do not have a `window` with a `document` + // (such as Node.js), expose a factory as module.exports. + // This accentuates the need for the creation of a real `window`. + // e.g. var jQuery = require("jquery")(window); + // See ticket #14549 for more info. + module.exports = global.document ? + factory( global, true ) : + function( w ) { + if ( !w.document ) { + throw new Error( "jQuery requires a window with a document" ); + } + return factory( w ); + }; + } else { + factory( global ); + } + +// Pass this if window is not defined yet +} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) { + +// Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1 +// throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode +// arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common +// enough that all such attempts are guarded in a try block. +"use strict"; + +var arr = []; + +var getProto = Object.getPrototypeOf; + +var slice = arr.slice; + +var flat = arr.flat ? function( array ) { + return arr.flat.call( array ); +} : function( array ) { + return arr.concat.apply( [], array ); +}; + + +var push = arr.push; + +var indexOf = arr.indexOf; + +var class2type = {}; + +var toString = class2type.toString; + +var hasOwn = class2type.hasOwnProperty; + +var fnToString = hasOwn.toString; + +var ObjectFunctionString = fnToString.call( Object ); + +var support = {}; + +var isFunction = function isFunction( obj ) { + + // Support: Chrome <=57, Firefox <=52 + // In some browsers, typeof returns "function" for HTML elements + // (i.e., `typeof document.createElement( "object" ) === "function"`). + // We don't want to classify *any* DOM node as a function. + return typeof obj === "function" && typeof obj.nodeType !== "number"; + }; + + +var isWindow = function isWindow( obj ) { + return obj != null && obj === obj.window; + }; + + +var document = window.document; + + + + var preservedScriptAttributes = { + type: true, + src: true, + nonce: true, + noModule: true + }; + + function DOMEval( code, node, doc ) { + doc = doc || document; + + var i, val, + script = doc.createElement( "script" ); + + script.text = code; + if ( node ) { + for ( i in preservedScriptAttributes ) { + + // Support: Firefox 64+, Edge 18+ + // Some browsers don't support the "nonce" property on scripts. + // On the other hand, just using `getAttribute` is not enough as + // the `nonce` attribute is reset to an empty string whenever it + // becomes browsing-context connected. + // See https://github.com/whatwg/html/issues/2369 + // See https://html.spec.whatwg.org/#nonce-attributes + // The `node.getAttribute` check was added for the sake of + // `jQuery.globalEval` so that it can fake a nonce-containing node + // via an object. + val = node[ i ] || node.getAttribute && node.getAttribute( i ); + if ( val ) { + script.setAttribute( i, val ); + } + } + } + doc.head.appendChild( script ).parentNode.removeChild( script ); + } + + +function toType( obj ) { + if ( obj == null ) { + return obj + ""; + } + + // Support: Android <=2.3 only (functionish RegExp) + return typeof obj === "object" || typeof obj === "function" ? + class2type[ toString.call( obj ) ] || "object" : + typeof obj; +} +/* global Symbol */ +// Defining this global in .eslintrc.json would create a danger of using the global +// unguarded in another place, it seems safer to define global only for this module + + + +var + version = "3.5.1", + + // Define a local copy of jQuery + jQuery = function( selector, context ) { + + // The jQuery object is actually just the init constructor 'enhanced' + // Need init if jQuery is called (just allow error to be thrown if not included) + return new jQuery.fn.init( selector, context ); + }; + +jQuery.fn = jQuery.prototype = { + + // The current version of jQuery being used + jquery: version, + + constructor: jQuery, + + // The default length of a jQuery object is 0 + length: 0, + + toArray: function() { + return slice.call( this ); + }, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function( num ) { + + // Return all the elements in a clean array + if ( num == null ) { + return slice.call( this ); + } + + // Return just the one element from the set + return num < 0 ? this[ num + this.length ] : this[ num ]; + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems ) { + + // Build a new jQuery matched element set + var ret = jQuery.merge( this.constructor(), elems ); + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + + // Return the newly-formed element set + return ret; + }, + + // Execute a callback for every element in the matched set. + each: function( callback ) { + return jQuery.each( this, callback ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map( this, function( elem, i ) { + return callback.call( elem, i, elem ); + } ) ); + }, + + slice: function() { + return this.pushStack( slice.apply( this, arguments ) ); + }, + + first: function() { + return this.eq( 0 ); + }, + + last: function() { + return this.eq( -1 ); + }, + + even: function() { + return this.pushStack( jQuery.grep( this, function( _elem, i ) { + return ( i + 1 ) % 2; + } ) ); + }, + + odd: function() { + return this.pushStack( jQuery.grep( this, function( _elem, i ) { + return i % 2; + } ) ); + }, + + eq: function( i ) { + var len = this.length, + j = +i + ( i < 0 ? len : 0 ); + return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] ); + }, + + end: function() { + return this.prevObject || this.constructor(); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push: push, + sort: arr.sort, + splice: arr.splice +}; + +jQuery.extend = jQuery.fn.extend = function() { + var options, name, src, copy, copyIsArray, clone, + target = arguments[ 0 ] || {}, + i = 1, + length = arguments.length, + deep = false; + + // Handle a deep copy situation + if ( typeof target === "boolean" ) { + deep = target; + + // Skip the boolean and the target + target = arguments[ i ] || {}; + i++; + } + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target !== "object" && !isFunction( target ) ) { + target = {}; + } + + // Extend jQuery itself if only one argument is passed + if ( i === length ) { + target = this; + i--; + } + + for ( ; i < length; i++ ) { + + // Only deal with non-null/undefined values + if ( ( options = arguments[ i ] ) != null ) { + + // Extend the base object + for ( name in options ) { + copy = options[ name ]; + + // Prevent Object.prototype pollution + // Prevent never-ending loop + if ( name === "__proto__" || target === copy ) { + continue; + } + + // Recurse if we're merging plain objects or arrays + if ( deep && copy && ( jQuery.isPlainObject( copy ) || + ( copyIsArray = Array.isArray( copy ) ) ) ) { + src = target[ name ]; + + // Ensure proper type for the source value + if ( copyIsArray && !Array.isArray( src ) ) { + clone = []; + } else if ( !copyIsArray && !jQuery.isPlainObject( src ) ) { + clone = {}; + } else { + clone = src; + } + copyIsArray = false; + + // Never move original objects, clone them + target[ name ] = jQuery.extend( deep, clone, copy ); + + // Don't bring in undefined values + } else if ( copy !== undefined ) { + target[ name ] = copy; + } + } + } + } + + // Return the modified object + return target; +}; + +jQuery.extend( { + + // Unique for each copy of jQuery on the page + expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ), + + // Assume jQuery is ready without the ready module + isReady: true, + + error: function( msg ) { + throw new Error( msg ); + }, + + noop: function() {}, + + isPlainObject: function( obj ) { + var proto, Ctor; + + // Detect obvious negatives + // Use toString instead of jQuery.type to catch host objects + if ( !obj || toString.call( obj ) !== "[object Object]" ) { + return false; + } + + proto = getProto( obj ); + + // Objects with no prototype (e.g., `Object.create( null )`) are plain + if ( !proto ) { + return true; + } + + // Objects with prototype are plain iff they were constructed by a global Object function + Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor; + return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString; + }, + + isEmptyObject: function( obj ) { + var name; + + for ( name in obj ) { + return false; + } + return true; + }, + + // Evaluates a script in a provided context; falls back to the global one + // if not specified. + globalEval: function( code, options, doc ) { + DOMEval( code, { nonce: options && options.nonce }, doc ); + }, + + each: function( obj, callback ) { + var length, i = 0; + + if ( isArrayLike( obj ) ) { + length = obj.length; + for ( ; i < length; i++ ) { + if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { + break; + } + } + } else { + for ( i in obj ) { + if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { + break; + } + } + } + + return obj; + }, + + // results is for internal usage only + makeArray: function( arr, results ) { + var ret = results || []; + + if ( arr != null ) { + if ( isArrayLike( Object( arr ) ) ) { + jQuery.merge( ret, + typeof arr === "string" ? + [ arr ] : arr + ); + } else { + push.call( ret, arr ); + } + } + + return ret; + }, + + inArray: function( elem, arr, i ) { + return arr == null ? -1 : indexOf.call( arr, elem, i ); + }, + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + merge: function( first, second ) { + var len = +second.length, + j = 0, + i = first.length; + + for ( ; j < len; j++ ) { + first[ i++ ] = second[ j ]; + } + + first.length = i; + + return first; + }, + + grep: function( elems, callback, invert ) { + var callbackInverse, + matches = [], + i = 0, + length = elems.length, + callbackExpect = !invert; + + // Go through the array, only saving the items + // that pass the validator function + for ( ; i < length; i++ ) { + callbackInverse = !callback( elems[ i ], i ); + if ( callbackInverse !== callbackExpect ) { + matches.push( elems[ i ] ); + } + } + + return matches; + }, + + // arg is for internal usage only + map: function( elems, callback, arg ) { + var length, value, + i = 0, + ret = []; + + // Go through the array, translating each of the items to their new values + if ( isArrayLike( elems ) ) { + length = elems.length; + for ( ; i < length; i++ ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + + // Go through every key on the object, + } else { + for ( i in elems ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + } + + // Flatten any nested arrays + return flat( ret ); + }, + + // A global GUID counter for objects + guid: 1, + + // jQuery.support is not used in Core but other projects attach their + // properties to it so it needs to exist. + support: support +} ); + +if ( typeof Symbol === "function" ) { + jQuery.fn[ Symbol.iterator ] = arr[ Symbol.iterator ]; +} + +// Populate the class2type map +jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ), +function( _i, name ) { + class2type[ "[object " + name + "]" ] = name.toLowerCase(); +} ); + +function isArrayLike( obj ) { + + // Support: real iOS 8.2 only (not reproducible in simulator) + // `in` check used to prevent JIT error (gh-2145) + // hasOwn isn't used here due to false negatives + // regarding Nodelist length in IE + var length = !!obj && "length" in obj && obj.length, + type = toType( obj ); + + if ( isFunction( obj ) || isWindow( obj ) ) { + return false; + } + + return type === "array" || length === 0 || + typeof length === "number" && length > 0 && ( length - 1 ) in obj; +} +var Sizzle = +/*! + * Sizzle CSS Selector Engine v2.3.5 + * https://sizzlejs.com/ + * + * Copyright JS Foundation and other contributors + * Released under the MIT license + * https://js.foundation/ + * + * Date: 2020-03-14 + */ +( function( window ) { +var i, + support, + Expr, + getText, + isXML, + tokenize, + compile, + select, + outermostContext, + sortInput, + hasDuplicate, + + // Local document vars + setDocument, + document, + docElem, + documentIsHTML, + rbuggyQSA, + rbuggyMatches, + matches, + contains, + + // Instance-specific data + expando = "sizzle" + 1 * new Date(), + preferredDoc = window.document, + dirruns = 0, + done = 0, + classCache = createCache(), + tokenCache = createCache(), + compilerCache = createCache(), + nonnativeSelectorCache = createCache(), + sortOrder = function( a, b ) { + if ( a === b ) { + hasDuplicate = true; + } + return 0; + }, + + // Instance methods + hasOwn = ( {} ).hasOwnProperty, + arr = [], + pop = arr.pop, + pushNative = arr.push, + push = arr.push, + slice = arr.slice, + + // Use a stripped-down indexOf as it's faster than native + // https://jsperf.com/thor-indexof-vs-for/5 + indexOf = function( list, elem ) { + var i = 0, + len = list.length; + for ( ; i < len; i++ ) { + if ( list[ i ] === elem ) { + return i; + } + } + return -1; + }, + + booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|" + + "ismap|loop|multiple|open|readonly|required|scoped", + + // Regular expressions + + // http://www.w3.org/TR/css3-selectors/#whitespace + whitespace = "[\\x20\\t\\r\\n\\f]", + + // https://www.w3.org/TR/css-syntax-3/#ident-token-diagram + identifier = "(?:\\\\[\\da-fA-F]{1,6}" + whitespace + + "?|\\\\[^\\r\\n\\f]|[\\w-]|[^\0-\\x7f])+", + + // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors + attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace + + + // Operator (capture 2) + "*([*^$|!~]?=)" + whitespace + + + // "Attribute values must be CSS identifiers [capture 5] + // or strings [capture 3 or capture 4]" + "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + + whitespace + "*\\]", + + pseudos = ":(" + identifier + ")(?:\\((" + + + // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: + // 1. quoted (capture 3; capture 4 or capture 5) + "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + + + // 2. simple (capture 6) + "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + + + // 3. anything else (capture 2) + ".*" + + ")\\)|)", + + // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter + rwhitespace = new RegExp( whitespace + "+", "g" ), + rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + + whitespace + "+$", "g" ), + + rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), + rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + + "*" ), + rdescend = new RegExp( whitespace + "|>" ), + + rpseudo = new RegExp( pseudos ), + ridentifier = new RegExp( "^" + identifier + "$" ), + + matchExpr = { + "ID": new RegExp( "^#(" + identifier + ")" ), + "CLASS": new RegExp( "^\\.(" + identifier + ")" ), + "TAG": new RegExp( "^(" + identifier + "|[*])" ), + "ATTR": new RegExp( "^" + attributes ), + "PSEUDO": new RegExp( "^" + pseudos ), + "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + + whitespace + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + + whitespace + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), + "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), + + // For use in libraries implementing .is() + // We use this for POS matching in `select` + "needsContext": new RegExp( "^" + whitespace + + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + whitespace + + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) + }, + + rhtml = /HTML$/i, + rinputs = /^(?:input|select|textarea|button)$/i, + rheader = /^h\d$/i, + + rnative = /^[^{]+\{\s*\[native \w/, + + // Easily-parseable/retrievable ID or TAG or CLASS selectors + rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, + + rsibling = /[+~]/, + + // CSS escapes + // http://www.w3.org/TR/CSS21/syndata.html#escaped-characters + runescape = new RegExp( "\\\\[\\da-fA-F]{1,6}" + whitespace + "?|\\\\([^\\r\\n\\f])", "g" ), + funescape = function( escape, nonHex ) { + var high = "0x" + escape.slice( 1 ) - 0x10000; + + return nonHex ? + + // Strip the backslash prefix from a non-hex escape sequence + nonHex : + + // Replace a hexadecimal escape sequence with the encoded Unicode code point + // Support: IE <=11+ + // For values outside the Basic Multilingual Plane (BMP), manually construct a + // surrogate pair + high < 0 ? + String.fromCharCode( high + 0x10000 ) : + String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); + }, + + // CSS string/identifier serialization + // https://drafts.csswg.org/cssom/#common-serializing-idioms + rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g, + fcssescape = function( ch, asCodePoint ) { + if ( asCodePoint ) { + + // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER + if ( ch === "\0" ) { + return "\uFFFD"; + } + + // Control characters and (dependent upon position) numbers get escaped as code points + return ch.slice( 0, -1 ) + "\\" + + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " "; + } + + // Other potentially-special ASCII characters get backslash-escaped + return "\\" + ch; + }, + + // Used for iframes + // See setDocument() + // Removing the function wrapper causes a "Permission Denied" + // error in IE + unloadHandler = function() { + setDocument(); + }, + + inDisabledFieldset = addCombinator( + function( elem ) { + return elem.disabled === true && elem.nodeName.toLowerCase() === "fieldset"; + }, + { dir: "parentNode", next: "legend" } + ); + +// Optimize for push.apply( _, NodeList ) +try { + push.apply( + ( arr = slice.call( preferredDoc.childNodes ) ), + preferredDoc.childNodes + ); + + // Support: Android<4.0 + // Detect silently failing push.apply + // eslint-disable-next-line no-unused-expressions + arr[ preferredDoc.childNodes.length ].nodeType; +} catch ( e ) { + push = { apply: arr.length ? + + // Leverage slice if possible + function( target, els ) { + pushNative.apply( target, slice.call( els ) ); + } : + + // Support: IE<9 + // Otherwise append directly + function( target, els ) { + var j = target.length, + i = 0; + + // Can't trust NodeList.length + while ( ( target[ j++ ] = els[ i++ ] ) ) {} + target.length = j - 1; + } + }; +} + +function Sizzle( selector, context, results, seed ) { + var m, i, elem, nid, match, groups, newSelector, + newContext = context && context.ownerDocument, + + // nodeType defaults to 9, since context defaults to document + nodeType = context ? context.nodeType : 9; + + results = results || []; + + // Return early from calls with invalid selector or context + if ( typeof selector !== "string" || !selector || + nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) { + + return results; + } + + // Try to shortcut find operations (as opposed to filters) in HTML documents + if ( !seed ) { + setDocument( context ); + context = context || document; + + if ( documentIsHTML ) { + + // If the selector is sufficiently simple, try using a "get*By*" DOM method + // (excepting DocumentFragment context, where the methods don't exist) + if ( nodeType !== 11 && ( match = rquickExpr.exec( selector ) ) ) { + + // ID selector + if ( ( m = match[ 1 ] ) ) { + + // Document context + if ( nodeType === 9 ) { + if ( ( elem = context.getElementById( m ) ) ) { + + // Support: IE, Opera, Webkit + // TODO: identify versions + // getElementById can match elements by name instead of ID + if ( elem.id === m ) { + results.push( elem ); + return results; + } + } else { + return results; + } + + // Element context + } else { + + // Support: IE, Opera, Webkit + // TODO: identify versions + // getElementById can match elements by name instead of ID + if ( newContext && ( elem = newContext.getElementById( m ) ) && + contains( context, elem ) && + elem.id === m ) { + + results.push( elem ); + return results; + } + } + + // Type selector + } else if ( match[ 2 ] ) { + push.apply( results, context.getElementsByTagName( selector ) ); + return results; + + // Class selector + } else if ( ( m = match[ 3 ] ) && support.getElementsByClassName && + context.getElementsByClassName ) { + + push.apply( results, context.getElementsByClassName( m ) ); + return results; + } + } + + // Take advantage of querySelectorAll + if ( support.qsa && + !nonnativeSelectorCache[ selector + " " ] && + ( !rbuggyQSA || !rbuggyQSA.test( selector ) ) && + + // Support: IE 8 only + // Exclude object elements + ( nodeType !== 1 || context.nodeName.toLowerCase() !== "object" ) ) { + + newSelector = selector; + newContext = context; + + // qSA considers elements outside a scoping root when evaluating child or + // descendant combinators, which is not what we want. + // In such cases, we work around the behavior by prefixing every selector in the + // list with an ID selector referencing the scope context. + // The technique has to be used as well when a leading combinator is used + // as such selectors are not recognized by querySelectorAll. + // Thanks to Andrew Dupont for this technique. + if ( nodeType === 1 && + ( rdescend.test( selector ) || rcombinators.test( selector ) ) ) { + + // Expand context for sibling selectors + newContext = rsibling.test( selector ) && testContext( context.parentNode ) || + context; + + // We can use :scope instead of the ID hack if the browser + // supports it & if we're not changing the context. + if ( newContext !== context || !support.scope ) { + + // Capture the context ID, setting it first if necessary + if ( ( nid = context.getAttribute( "id" ) ) ) { + nid = nid.replace( rcssescape, fcssescape ); + } else { + context.setAttribute( "id", ( nid = expando ) ); + } + } + + // Prefix every selector in the list + groups = tokenize( selector ); + i = groups.length; + while ( i-- ) { + groups[ i ] = ( nid ? "#" + nid : ":scope" ) + " " + + toSelector( groups[ i ] ); + } + newSelector = groups.join( "," ); + } + + try { + push.apply( results, + newContext.querySelectorAll( newSelector ) + ); + return results; + } catch ( qsaError ) { + nonnativeSelectorCache( selector, true ); + } finally { + if ( nid === expando ) { + context.removeAttribute( "id" ); + } + } + } + } + } + + // All others + return select( selector.replace( rtrim, "$1" ), context, results, seed ); +} + +/** + * Create key-value caches of limited size + * @returns {function(string, object)} Returns the Object data after storing it on itself with + * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) + * deleting the oldest entry + */ +function createCache() { + var keys = []; + + function cache( key, value ) { + + // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) + if ( keys.push( key + " " ) > Expr.cacheLength ) { + + // Only keep the most recent entries + delete cache[ keys.shift() ]; + } + return ( cache[ key + " " ] = value ); + } + return cache; +} + +/** + * Mark a function for special use by Sizzle + * @param {Function} fn The function to mark + */ +function markFunction( fn ) { + fn[ expando ] = true; + return fn; +} + +/** + * Support testing using an element + * @param {Function} fn Passed the created element and returns a boolean result + */ +function assert( fn ) { + var el = document.createElement( "fieldset" ); + + try { + return !!fn( el ); + } catch ( e ) { + return false; + } finally { + + // Remove from its parent by default + if ( el.parentNode ) { + el.parentNode.removeChild( el ); + } + + // release memory in IE + el = null; + } +} + +/** + * Adds the same handler for all of the specified attrs + * @param {String} attrs Pipe-separated list of attributes + * @param {Function} handler The method that will be applied + */ +function addHandle( attrs, handler ) { + var arr = attrs.split( "|" ), + i = arr.length; + + while ( i-- ) { + Expr.attrHandle[ arr[ i ] ] = handler; + } +} + +/** + * Checks document order of two siblings + * @param {Element} a + * @param {Element} b + * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b + */ +function siblingCheck( a, b ) { + var cur = b && a, + diff = cur && a.nodeType === 1 && b.nodeType === 1 && + a.sourceIndex - b.sourceIndex; + + // Use IE sourceIndex if available on both nodes + if ( diff ) { + return diff; + } + + // Check if b follows a + if ( cur ) { + while ( ( cur = cur.nextSibling ) ) { + if ( cur === b ) { + return -1; + } + } + } + + return a ? 1 : -1; +} + +/** + * Returns a function to use in pseudos for input types + * @param {String} type + */ +function createInputPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for buttons + * @param {String} type + */ +function createButtonPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return ( name === "input" || name === "button" ) && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for :enabled/:disabled + * @param {Boolean} disabled true for :disabled; false for :enabled + */ +function createDisabledPseudo( disabled ) { + + // Known :disabled false positives: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable + return function( elem ) { + + // Only certain elements can match :enabled or :disabled + // https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled + // https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled + if ( "form" in elem ) { + + // Check for inherited disabledness on relevant non-disabled elements: + // * listed form-associated elements in a disabled fieldset + // https://html.spec.whatwg.org/multipage/forms.html#category-listed + // https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled + // * option elements in a disabled optgroup + // https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled + // All such elements have a "form" property. + if ( elem.parentNode && elem.disabled === false ) { + + // Option elements defer to a parent optgroup if present + if ( "label" in elem ) { + if ( "label" in elem.parentNode ) { + return elem.parentNode.disabled === disabled; + } else { + return elem.disabled === disabled; + } + } + + // Support: IE 6 - 11 + // Use the isDisabled shortcut property to check for disabled fieldset ancestors + return elem.isDisabled === disabled || + + // Where there is no isDisabled, check manually + /* jshint -W018 */ + elem.isDisabled !== !disabled && + inDisabledFieldset( elem ) === disabled; + } + + return elem.disabled === disabled; + + // Try to winnow out elements that can't be disabled before trusting the disabled property. + // Some victims get caught in our net (label, legend, menu, track), but it shouldn't + // even exist on them, let alone have a boolean value. + } else if ( "label" in elem ) { + return elem.disabled === disabled; + } + + // Remaining elements are neither :enabled nor :disabled + return false; + }; +} + +/** + * Returns a function to use in pseudos for positionals + * @param {Function} fn + */ +function createPositionalPseudo( fn ) { + return markFunction( function( argument ) { + argument = +argument; + return markFunction( function( seed, matches ) { + var j, + matchIndexes = fn( [], seed.length, argument ), + i = matchIndexes.length; + + // Match elements found at the specified indexes + while ( i-- ) { + if ( seed[ ( j = matchIndexes[ i ] ) ] ) { + seed[ j ] = !( matches[ j ] = seed[ j ] ); + } + } + } ); + } ); +} + +/** + * Checks a node for validity as a Sizzle context + * @param {Element|Object=} context + * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value + */ +function testContext( context ) { + return context && typeof context.getElementsByTagName !== "undefined" && context; +} + +// Expose support vars for convenience +support = Sizzle.support = {}; + +/** + * Detects XML nodes + * @param {Element|Object} elem An element or a document + * @returns {Boolean} True iff elem is a non-HTML XML node + */ +isXML = Sizzle.isXML = function( elem ) { + var namespace = elem.namespaceURI, + docElem = ( elem.ownerDocument || elem ).documentElement; + + // Support: IE <=8 + // Assume HTML when documentElement doesn't yet exist, such as inside loading iframes + // https://bugs.jquery.com/ticket/4833 + return !rhtml.test( namespace || docElem && docElem.nodeName || "HTML" ); +}; + +/** + * Sets document-related variables once based on the current document + * @param {Element|Object} [doc] An element or document object to use to set the document + * @returns {Object} Returns the current document + */ +setDocument = Sizzle.setDocument = function( node ) { + var hasCompare, subWindow, + doc = node ? node.ownerDocument || node : preferredDoc; + + // Return early if doc is invalid or already selected + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( doc == document || doc.nodeType !== 9 || !doc.documentElement ) { + return document; + } + + // Update global variables + document = doc; + docElem = document.documentElement; + documentIsHTML = !isXML( document ); + + // Support: IE 9 - 11+, Edge 12 - 18+ + // Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936) + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( preferredDoc != document && + ( subWindow = document.defaultView ) && subWindow.top !== subWindow ) { + + // Support: IE 11, Edge + if ( subWindow.addEventListener ) { + subWindow.addEventListener( "unload", unloadHandler, false ); + + // Support: IE 9 - 10 only + } else if ( subWindow.attachEvent ) { + subWindow.attachEvent( "onunload", unloadHandler ); + } + } + + // Support: IE 8 - 11+, Edge 12 - 18+, Chrome <=16 - 25 only, Firefox <=3.6 - 31 only, + // Safari 4 - 5 only, Opera <=11.6 - 12.x only + // IE/Edge & older browsers don't support the :scope pseudo-class. + // Support: Safari 6.0 only + // Safari 6.0 supports :scope but it's an alias of :root there. + support.scope = assert( function( el ) { + docElem.appendChild( el ).appendChild( document.createElement( "div" ) ); + return typeof el.querySelectorAll !== "undefined" && + !el.querySelectorAll( ":scope fieldset div" ).length; + } ); + + /* Attributes + ---------------------------------------------------------------------- */ + + // Support: IE<8 + // Verify that getAttribute really returns attributes and not properties + // (excepting IE8 booleans) + support.attributes = assert( function( el ) { + el.className = "i"; + return !el.getAttribute( "className" ); + } ); + + /* getElement(s)By* + ---------------------------------------------------------------------- */ + + // Check if getElementsByTagName("*") returns only elements + support.getElementsByTagName = assert( function( el ) { + el.appendChild( document.createComment( "" ) ); + return !el.getElementsByTagName( "*" ).length; + } ); + + // Support: IE<9 + support.getElementsByClassName = rnative.test( document.getElementsByClassName ); + + // Support: IE<10 + // Check if getElementById returns elements by name + // The broken getElementById methods don't pick up programmatically-set names, + // so use a roundabout getElementsByName test + support.getById = assert( function( el ) { + docElem.appendChild( el ).id = expando; + return !document.getElementsByName || !document.getElementsByName( expando ).length; + } ); + + // ID filter and find + if ( support.getById ) { + Expr.filter[ "ID" ] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + return elem.getAttribute( "id" ) === attrId; + }; + }; + Expr.find[ "ID" ] = function( id, context ) { + if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { + var elem = context.getElementById( id ); + return elem ? [ elem ] : []; + } + }; + } else { + Expr.filter[ "ID" ] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + var node = typeof elem.getAttributeNode !== "undefined" && + elem.getAttributeNode( "id" ); + return node && node.value === attrId; + }; + }; + + // Support: IE 6 - 7 only + // getElementById is not reliable as a find shortcut + Expr.find[ "ID" ] = function( id, context ) { + if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { + var node, i, elems, + elem = context.getElementById( id ); + + if ( elem ) { + + // Verify the id attribute + node = elem.getAttributeNode( "id" ); + if ( node && node.value === id ) { + return [ elem ]; + } + + // Fall back on getElementsByName + elems = context.getElementsByName( id ); + i = 0; + while ( ( elem = elems[ i++ ] ) ) { + node = elem.getAttributeNode( "id" ); + if ( node && node.value === id ) { + return [ elem ]; + } + } + } + + return []; + } + }; + } + + // Tag + Expr.find[ "TAG" ] = support.getElementsByTagName ? + function( tag, context ) { + if ( typeof context.getElementsByTagName !== "undefined" ) { + return context.getElementsByTagName( tag ); + + // DocumentFragment nodes don't have gEBTN + } else if ( support.qsa ) { + return context.querySelectorAll( tag ); + } + } : + + function( tag, context ) { + var elem, + tmp = [], + i = 0, + + // By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too + results = context.getElementsByTagName( tag ); + + // Filter out possible comments + if ( tag === "*" ) { + while ( ( elem = results[ i++ ] ) ) { + if ( elem.nodeType === 1 ) { + tmp.push( elem ); + } + } + + return tmp; + } + return results; + }; + + // Class + Expr.find[ "CLASS" ] = support.getElementsByClassName && function( className, context ) { + if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) { + return context.getElementsByClassName( className ); + } + }; + + /* QSA/matchesSelector + ---------------------------------------------------------------------- */ + + // QSA and matchesSelector support + + // matchesSelector(:active) reports false when true (IE9/Opera 11.5) + rbuggyMatches = []; + + // qSa(:focus) reports false when true (Chrome 21) + // We allow this because of a bug in IE8/9 that throws an error + // whenever `document.activeElement` is accessed on an iframe + // So, we allow :focus to pass through QSA all the time to avoid the IE error + // See https://bugs.jquery.com/ticket/13378 + rbuggyQSA = []; + + if ( ( support.qsa = rnative.test( document.querySelectorAll ) ) ) { + + // Build QSA regex + // Regex strategy adopted from Diego Perini + assert( function( el ) { + + var input; + + // Select is set to empty string on purpose + // This is to test IE's treatment of not explicitly + // setting a boolean content attribute, + // since its presence should be enough + // https://bugs.jquery.com/ticket/12359 + docElem.appendChild( el ).innerHTML = "" + + ""; + + // Support: IE8, Opera 11-12.16 + // Nothing should be selected when empty strings follow ^= or $= or *= + // The test attribute must be unknown in Opera but "safe" for WinRT + // https://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section + if ( el.querySelectorAll( "[msallowcapture^='']" ).length ) { + rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); + } + + // Support: IE8 + // Boolean attributes and "value" are not treated correctly + if ( !el.querySelectorAll( "[selected]" ).length ) { + rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); + } + + // Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+ + if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) { + rbuggyQSA.push( "~=" ); + } + + // Support: IE 11+, Edge 15 - 18+ + // IE 11/Edge don't find elements on a `[name='']` query in some cases. + // Adding a temporary attribute to the document before the selection works + // around the issue. + // Interestingly, IE 10 & older don't seem to have the issue. + input = document.createElement( "input" ); + input.setAttribute( "name", "" ); + el.appendChild( input ); + if ( !el.querySelectorAll( "[name='']" ).length ) { + rbuggyQSA.push( "\\[" + whitespace + "*name" + whitespace + "*=" + + whitespace + "*(?:''|\"\")" ); + } + + // Webkit/Opera - :checked should return selected option elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + // IE8 throws error here and will not see later tests + if ( !el.querySelectorAll( ":checked" ).length ) { + rbuggyQSA.push( ":checked" ); + } + + // Support: Safari 8+, iOS 8+ + // https://bugs.webkit.org/show_bug.cgi?id=136851 + // In-page `selector#id sibling-combinator selector` fails + if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) { + rbuggyQSA.push( ".#.+[+~]" ); + } + + // Support: Firefox <=3.6 - 5 only + // Old Firefox doesn't throw on a badly-escaped identifier. + el.querySelectorAll( "\\\f" ); + rbuggyQSA.push( "[\\r\\n\\f]" ); + } ); + + assert( function( el ) { + el.innerHTML = "" + + ""; + + // Support: Windows 8 Native Apps + // The type and name attributes are restricted during .innerHTML assignment + var input = document.createElement( "input" ); + input.setAttribute( "type", "hidden" ); + el.appendChild( input ).setAttribute( "name", "D" ); + + // Support: IE8 + // Enforce case-sensitivity of name attribute + if ( el.querySelectorAll( "[name=d]" ).length ) { + rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" ); + } + + // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) + // IE8 throws error here and will not see later tests + if ( el.querySelectorAll( ":enabled" ).length !== 2 ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Support: IE9-11+ + // IE's :disabled selector does not pick up the children of disabled fieldsets + docElem.appendChild( el ).disabled = true; + if ( el.querySelectorAll( ":disabled" ).length !== 2 ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Support: Opera 10 - 11 only + // Opera 10-11 does not throw on post-comma invalid pseudos + el.querySelectorAll( "*,:x" ); + rbuggyQSA.push( ",.*:" ); + } ); + } + + if ( ( support.matchesSelector = rnative.test( ( matches = docElem.matches || + docElem.webkitMatchesSelector || + docElem.mozMatchesSelector || + docElem.oMatchesSelector || + docElem.msMatchesSelector ) ) ) ) { + + assert( function( el ) { + + // Check to see if it's possible to do matchesSelector + // on a disconnected node (IE 9) + support.disconnectedMatch = matches.call( el, "*" ); + + // This should fail with an exception + // Gecko does not error, returns false instead + matches.call( el, "[s!='']:x" ); + rbuggyMatches.push( "!=", pseudos ); + } ); + } + + rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join( "|" ) ); + rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join( "|" ) ); + + /* Contains + ---------------------------------------------------------------------- */ + hasCompare = rnative.test( docElem.compareDocumentPosition ); + + // Element contains another + // Purposefully self-exclusive + // As in, an element does not contain itself + contains = hasCompare || rnative.test( docElem.contains ) ? + function( a, b ) { + var adown = a.nodeType === 9 ? a.documentElement : a, + bup = b && b.parentNode; + return a === bup || !!( bup && bup.nodeType === 1 && ( + adown.contains ? + adown.contains( bup ) : + a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 + ) ); + } : + function( a, b ) { + if ( b ) { + while ( ( b = b.parentNode ) ) { + if ( b === a ) { + return true; + } + } + } + return false; + }; + + /* Sorting + ---------------------------------------------------------------------- */ + + // Document order sorting + sortOrder = hasCompare ? + function( a, b ) { + + // Flag for duplicate removal + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + // Sort on method existence if only one input has compareDocumentPosition + var compare = !a.compareDocumentPosition - !b.compareDocumentPosition; + if ( compare ) { + return compare; + } + + // Calculate position if both inputs belong to the same document + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + compare = ( a.ownerDocument || a ) == ( b.ownerDocument || b ) ? + a.compareDocumentPosition( b ) : + + // Otherwise we know they are disconnected + 1; + + // Disconnected nodes + if ( compare & 1 || + ( !support.sortDetached && b.compareDocumentPosition( a ) === compare ) ) { + + // Choose the first element that is related to our preferred document + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( a == document || a.ownerDocument == preferredDoc && + contains( preferredDoc, a ) ) { + return -1; + } + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( b == document || b.ownerDocument == preferredDoc && + contains( preferredDoc, b ) ) { + return 1; + } + + // Maintain original order + return sortInput ? + ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : + 0; + } + + return compare & 4 ? -1 : 1; + } : + function( a, b ) { + + // Exit early if the nodes are identical + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + var cur, + i = 0, + aup = a.parentNode, + bup = b.parentNode, + ap = [ a ], + bp = [ b ]; + + // Parentless nodes are either documents or disconnected + if ( !aup || !bup ) { + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + /* eslint-disable eqeqeq */ + return a == document ? -1 : + b == document ? 1 : + /* eslint-enable eqeqeq */ + aup ? -1 : + bup ? 1 : + sortInput ? + ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : + 0; + + // If the nodes are siblings, we can do a quick check + } else if ( aup === bup ) { + return siblingCheck( a, b ); + } + + // Otherwise we need full lists of their ancestors for comparison + cur = a; + while ( ( cur = cur.parentNode ) ) { + ap.unshift( cur ); + } + cur = b; + while ( ( cur = cur.parentNode ) ) { + bp.unshift( cur ); + } + + // Walk down the tree looking for a discrepancy + while ( ap[ i ] === bp[ i ] ) { + i++; + } + + return i ? + + // Do a sibling check if the nodes have a common ancestor + siblingCheck( ap[ i ], bp[ i ] ) : + + // Otherwise nodes in our document sort first + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + /* eslint-disable eqeqeq */ + ap[ i ] == preferredDoc ? -1 : + bp[ i ] == preferredDoc ? 1 : + /* eslint-enable eqeqeq */ + 0; + }; + + return document; +}; + +Sizzle.matches = function( expr, elements ) { + return Sizzle( expr, null, null, elements ); +}; + +Sizzle.matchesSelector = function( elem, expr ) { + setDocument( elem ); + + if ( support.matchesSelector && documentIsHTML && + !nonnativeSelectorCache[ expr + " " ] && + ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) && + ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { + + try { + var ret = matches.call( elem, expr ); + + // IE 9's matchesSelector returns false on disconnected nodes + if ( ret || support.disconnectedMatch || + + // As well, disconnected nodes are said to be in a document + // fragment in IE 9 + elem.document && elem.document.nodeType !== 11 ) { + return ret; + } + } catch ( e ) { + nonnativeSelectorCache( expr, true ); + } + } + + return Sizzle( expr, document, null, [ elem ] ).length > 0; +}; + +Sizzle.contains = function( context, elem ) { + + // Set document vars if needed + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( ( context.ownerDocument || context ) != document ) { + setDocument( context ); + } + return contains( context, elem ); +}; + +Sizzle.attr = function( elem, name ) { + + // Set document vars if needed + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( ( elem.ownerDocument || elem ) != document ) { + setDocument( elem ); + } + + var fn = Expr.attrHandle[ name.toLowerCase() ], + + // Don't get fooled by Object.prototype properties (jQuery #13807) + val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? + fn( elem, name, !documentIsHTML ) : + undefined; + + return val !== undefined ? + val : + support.attributes || !documentIsHTML ? + elem.getAttribute( name ) : + ( val = elem.getAttributeNode( name ) ) && val.specified ? + val.value : + null; +}; + +Sizzle.escape = function( sel ) { + return ( sel + "" ).replace( rcssescape, fcssescape ); +}; + +Sizzle.error = function( msg ) { + throw new Error( "Syntax error, unrecognized expression: " + msg ); +}; + +/** + * Document sorting and removing duplicates + * @param {ArrayLike} results + */ +Sizzle.uniqueSort = function( results ) { + var elem, + duplicates = [], + j = 0, + i = 0; + + // Unless we *know* we can detect duplicates, assume their presence + hasDuplicate = !support.detectDuplicates; + sortInput = !support.sortStable && results.slice( 0 ); + results.sort( sortOrder ); + + if ( hasDuplicate ) { + while ( ( elem = results[ i++ ] ) ) { + if ( elem === results[ i ] ) { + j = duplicates.push( i ); + } + } + while ( j-- ) { + results.splice( duplicates[ j ], 1 ); + } + } + + // Clear input after sorting to release objects + // See https://github.com/jquery/sizzle/pull/225 + sortInput = null; + + return results; +}; + +/** + * Utility function for retrieving the text value of an array of DOM nodes + * @param {Array|Element} elem + */ +getText = Sizzle.getText = function( elem ) { + var node, + ret = "", + i = 0, + nodeType = elem.nodeType; + + if ( !nodeType ) { + + // If no nodeType, this is expected to be an array + while ( ( node = elem[ i++ ] ) ) { + + // Do not traverse comment nodes + ret += getText( node ); + } + } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { + + // Use textContent for elements + // innerText usage removed for consistency of new lines (jQuery #11153) + if ( typeof elem.textContent === "string" ) { + return elem.textContent; + } else { + + // Traverse its children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + ret += getText( elem ); + } + } + } else if ( nodeType === 3 || nodeType === 4 ) { + return elem.nodeValue; + } + + // Do not include comment or processing instruction nodes + + return ret; +}; + +Expr = Sizzle.selectors = { + + // Can be adjusted by the user + cacheLength: 50, + + createPseudo: markFunction, + + match: matchExpr, + + attrHandle: {}, + + find: {}, + + relative: { + ">": { dir: "parentNode", first: true }, + " ": { dir: "parentNode" }, + "+": { dir: "previousSibling", first: true }, + "~": { dir: "previousSibling" } + }, + + preFilter: { + "ATTR": function( match ) { + match[ 1 ] = match[ 1 ].replace( runescape, funescape ); + + // Move the given value to match[3] whether quoted or unquoted + match[ 3 ] = ( match[ 3 ] || match[ 4 ] || + match[ 5 ] || "" ).replace( runescape, funescape ); + + if ( match[ 2 ] === "~=" ) { + match[ 3 ] = " " + match[ 3 ] + " "; + } + + return match.slice( 0, 4 ); + }, + + "CHILD": function( match ) { + + /* matches from matchExpr["CHILD"] + 1 type (only|nth|...) + 2 what (child|of-type) + 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) + 4 xn-component of xn+y argument ([+-]?\d*n|) + 5 sign of xn-component + 6 x of xn-component + 7 sign of y-component + 8 y of y-component + */ + match[ 1 ] = match[ 1 ].toLowerCase(); + + if ( match[ 1 ].slice( 0, 3 ) === "nth" ) { + + // nth-* requires argument + if ( !match[ 3 ] ) { + Sizzle.error( match[ 0 ] ); + } + + // numeric x and y parameters for Expr.filter.CHILD + // remember that false/true cast respectively to 0/1 + match[ 4 ] = +( match[ 4 ] ? + match[ 5 ] + ( match[ 6 ] || 1 ) : + 2 * ( match[ 3 ] === "even" || match[ 3 ] === "odd" ) ); + match[ 5 ] = +( ( match[ 7 ] + match[ 8 ] ) || match[ 3 ] === "odd" ); + + // other types prohibit arguments + } else if ( match[ 3 ] ) { + Sizzle.error( match[ 0 ] ); + } + + return match; + }, + + "PSEUDO": function( match ) { + var excess, + unquoted = !match[ 6 ] && match[ 2 ]; + + if ( matchExpr[ "CHILD" ].test( match[ 0 ] ) ) { + return null; + } + + // Accept quoted arguments as-is + if ( match[ 3 ] ) { + match[ 2 ] = match[ 4 ] || match[ 5 ] || ""; + + // Strip excess characters from unquoted arguments + } else if ( unquoted && rpseudo.test( unquoted ) && + + // Get excess from tokenize (recursively) + ( excess = tokenize( unquoted, true ) ) && + + // advance to the next closing parenthesis + ( excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length ) ) { + + // excess is a negative index + match[ 0 ] = match[ 0 ].slice( 0, excess ); + match[ 2 ] = unquoted.slice( 0, excess ); + } + + // Return only captures needed by the pseudo filter method (type and argument) + return match.slice( 0, 3 ); + } + }, + + filter: { + + "TAG": function( nodeNameSelector ) { + var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); + return nodeNameSelector === "*" ? + function() { + return true; + } : + function( elem ) { + return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; + }; + }, + + "CLASS": function( className ) { + var pattern = classCache[ className + " " ]; + + return pattern || + ( pattern = new RegExp( "(^|" + whitespace + + ")" + className + "(" + whitespace + "|$)" ) ) && classCache( + className, function( elem ) { + return pattern.test( + typeof elem.className === "string" && elem.className || + typeof elem.getAttribute !== "undefined" && + elem.getAttribute( "class" ) || + "" + ); + } ); + }, + + "ATTR": function( name, operator, check ) { + return function( elem ) { + var result = Sizzle.attr( elem, name ); + + if ( result == null ) { + return operator === "!="; + } + if ( !operator ) { + return true; + } + + result += ""; + + /* eslint-disable max-len */ + + return operator === "=" ? result === check : + operator === "!=" ? result !== check : + operator === "^=" ? check && result.indexOf( check ) === 0 : + operator === "*=" ? check && result.indexOf( check ) > -1 : + operator === "$=" ? check && result.slice( -check.length ) === check : + operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 : + operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : + false; + /* eslint-enable max-len */ + + }; + }, + + "CHILD": function( type, what, _argument, first, last ) { + var simple = type.slice( 0, 3 ) !== "nth", + forward = type.slice( -4 ) !== "last", + ofType = what === "of-type"; + + return first === 1 && last === 0 ? + + // Shortcut for :nth-*(n) + function( elem ) { + return !!elem.parentNode; + } : + + function( elem, _context, xml ) { + var cache, uniqueCache, outerCache, node, nodeIndex, start, + dir = simple !== forward ? "nextSibling" : "previousSibling", + parent = elem.parentNode, + name = ofType && elem.nodeName.toLowerCase(), + useCache = !xml && !ofType, + diff = false; + + if ( parent ) { + + // :(first|last|only)-(child|of-type) + if ( simple ) { + while ( dir ) { + node = elem; + while ( ( node = node[ dir ] ) ) { + if ( ofType ? + node.nodeName.toLowerCase() === name : + node.nodeType === 1 ) { + + return false; + } + } + + // Reverse direction for :only-* (if we haven't yet done so) + start = dir = type === "only" && !start && "nextSibling"; + } + return true; + } + + start = [ forward ? parent.firstChild : parent.lastChild ]; + + // non-xml :nth-child(...) stores cache data on `parent` + if ( forward && useCache ) { + + // Seek `elem` from a previously-cached index + + // ...in a gzip-friendly way + node = parent; + outerCache = node[ expando ] || ( node[ expando ] = {} ); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + ( outerCache[ node.uniqueID ] = {} ); + + cache = uniqueCache[ type ] || []; + nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; + diff = nodeIndex && cache[ 2 ]; + node = nodeIndex && parent.childNodes[ nodeIndex ]; + + while ( ( node = ++nodeIndex && node && node[ dir ] || + + // Fallback to seeking `elem` from the start + ( diff = nodeIndex = 0 ) || start.pop() ) ) { + + // When found, cache indexes on `parent` and break + if ( node.nodeType === 1 && ++diff && node === elem ) { + uniqueCache[ type ] = [ dirruns, nodeIndex, diff ]; + break; + } + } + + } else { + + // Use previously-cached element index if available + if ( useCache ) { + + // ...in a gzip-friendly way + node = elem; + outerCache = node[ expando ] || ( node[ expando ] = {} ); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + ( outerCache[ node.uniqueID ] = {} ); + + cache = uniqueCache[ type ] || []; + nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; + diff = nodeIndex; + } + + // xml :nth-child(...) + // or :nth-last-child(...) or :nth(-last)?-of-type(...) + if ( diff === false ) { + + // Use the same loop as above to seek `elem` from the start + while ( ( node = ++nodeIndex && node && node[ dir ] || + ( diff = nodeIndex = 0 ) || start.pop() ) ) { + + if ( ( ofType ? + node.nodeName.toLowerCase() === name : + node.nodeType === 1 ) && + ++diff ) { + + // Cache the index of each encountered element + if ( useCache ) { + outerCache = node[ expando ] || + ( node[ expando ] = {} ); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + ( outerCache[ node.uniqueID ] = {} ); + + uniqueCache[ type ] = [ dirruns, diff ]; + } + + if ( node === elem ) { + break; + } + } + } + } + } + + // Incorporate the offset, then check against cycle size + diff -= last; + return diff === first || ( diff % first === 0 && diff / first >= 0 ); + } + }; + }, + + "PSEUDO": function( pseudo, argument ) { + + // pseudo-class names are case-insensitive + // http://www.w3.org/TR/selectors/#pseudo-classes + // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters + // Remember that setFilters inherits from pseudos + var args, + fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || + Sizzle.error( "unsupported pseudo: " + pseudo ); + + // The user may use createPseudo to indicate that + // arguments are needed to create the filter function + // just as Sizzle does + if ( fn[ expando ] ) { + return fn( argument ); + } + + // But maintain support for old signatures + if ( fn.length > 1 ) { + args = [ pseudo, pseudo, "", argument ]; + return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? + markFunction( function( seed, matches ) { + var idx, + matched = fn( seed, argument ), + i = matched.length; + while ( i-- ) { + idx = indexOf( seed, matched[ i ] ); + seed[ idx ] = !( matches[ idx ] = matched[ i ] ); + } + } ) : + function( elem ) { + return fn( elem, 0, args ); + }; + } + + return fn; + } + }, + + pseudos: { + + // Potentially complex pseudos + "not": markFunction( function( selector ) { + + // Trim the selector passed to compile + // to avoid treating leading and trailing + // spaces as combinators + var input = [], + results = [], + matcher = compile( selector.replace( rtrim, "$1" ) ); + + return matcher[ expando ] ? + markFunction( function( seed, matches, _context, xml ) { + var elem, + unmatched = matcher( seed, null, xml, [] ), + i = seed.length; + + // Match elements unmatched by `matcher` + while ( i-- ) { + if ( ( elem = unmatched[ i ] ) ) { + seed[ i ] = !( matches[ i ] = elem ); + } + } + } ) : + function( elem, _context, xml ) { + input[ 0 ] = elem; + matcher( input, null, xml, results ); + + // Don't keep the element (issue #299) + input[ 0 ] = null; + return !results.pop(); + }; + } ), + + "has": markFunction( function( selector ) { + return function( elem ) { + return Sizzle( selector, elem ).length > 0; + }; + } ), + + "contains": markFunction( function( text ) { + text = text.replace( runescape, funescape ); + return function( elem ) { + return ( elem.textContent || getText( elem ) ).indexOf( text ) > -1; + }; + } ), + + // "Whether an element is represented by a :lang() selector + // is based solely on the element's language value + // being equal to the identifier C, + // or beginning with the identifier C immediately followed by "-". + // The matching of C against the element's language value is performed case-insensitively. + // The identifier C does not have to be a valid language name." + // http://www.w3.org/TR/selectors/#lang-pseudo + "lang": markFunction( function( lang ) { + + // lang value must be a valid identifier + if ( !ridentifier.test( lang || "" ) ) { + Sizzle.error( "unsupported lang: " + lang ); + } + lang = lang.replace( runescape, funescape ).toLowerCase(); + return function( elem ) { + var elemLang; + do { + if ( ( elemLang = documentIsHTML ? + elem.lang : + elem.getAttribute( "xml:lang" ) || elem.getAttribute( "lang" ) ) ) { + + elemLang = elemLang.toLowerCase(); + return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; + } + } while ( ( elem = elem.parentNode ) && elem.nodeType === 1 ); + return false; + }; + } ), + + // Miscellaneous + "target": function( elem ) { + var hash = window.location && window.location.hash; + return hash && hash.slice( 1 ) === elem.id; + }, + + "root": function( elem ) { + return elem === docElem; + }, + + "focus": function( elem ) { + return elem === document.activeElement && + ( !document.hasFocus || document.hasFocus() ) && + !!( elem.type || elem.href || ~elem.tabIndex ); + }, + + // Boolean properties + "enabled": createDisabledPseudo( false ), + "disabled": createDisabledPseudo( true ), + + "checked": function( elem ) { + + // In CSS3, :checked should return both checked and selected elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + var nodeName = elem.nodeName.toLowerCase(); + return ( nodeName === "input" && !!elem.checked ) || + ( nodeName === "option" && !!elem.selected ); + }, + + "selected": function( elem ) { + + // Accessing this property makes selected-by-default + // options in Safari work properly + if ( elem.parentNode ) { + // eslint-disable-next-line no-unused-expressions + elem.parentNode.selectedIndex; + } + + return elem.selected === true; + }, + + // Contents + "empty": function( elem ) { + + // http://www.w3.org/TR/selectors/#empty-pseudo + // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), + // but not by others (comment: 8; processing instruction: 7; etc.) + // nodeType < 6 works because attributes (2) do not appear as children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + if ( elem.nodeType < 6 ) { + return false; + } + } + return true; + }, + + "parent": function( elem ) { + return !Expr.pseudos[ "empty" ]( elem ); + }, + + // Element/input types + "header": function( elem ) { + return rheader.test( elem.nodeName ); + }, + + "input": function( elem ) { + return rinputs.test( elem.nodeName ); + }, + + "button": function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === "button" || name === "button"; + }, + + "text": function( elem ) { + var attr; + return elem.nodeName.toLowerCase() === "input" && + elem.type === "text" && + + // Support: IE<8 + // New HTML5 attribute values (e.g., "search") appear with elem.type === "text" + ( ( attr = elem.getAttribute( "type" ) ) == null || + attr.toLowerCase() === "text" ); + }, + + // Position-in-collection + "first": createPositionalPseudo( function() { + return [ 0 ]; + } ), + + "last": createPositionalPseudo( function( _matchIndexes, length ) { + return [ length - 1 ]; + } ), + + "eq": createPositionalPseudo( function( _matchIndexes, length, argument ) { + return [ argument < 0 ? argument + length : argument ]; + } ), + + "even": createPositionalPseudo( function( matchIndexes, length ) { + var i = 0; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ), + + "odd": createPositionalPseudo( function( matchIndexes, length ) { + var i = 1; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ), + + "lt": createPositionalPseudo( function( matchIndexes, length, argument ) { + var i = argument < 0 ? + argument + length : + argument > length ? + length : + argument; + for ( ; --i >= 0; ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ), + + "gt": createPositionalPseudo( function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; ++i < length; ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ) + } +}; + +Expr.pseudos[ "nth" ] = Expr.pseudos[ "eq" ]; + +// Add button/input type pseudos +for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { + Expr.pseudos[ i ] = createInputPseudo( i ); +} +for ( i in { submit: true, reset: true } ) { + Expr.pseudos[ i ] = createButtonPseudo( i ); +} + +// Easy API for creating new setFilters +function setFilters() {} +setFilters.prototype = Expr.filters = Expr.pseudos; +Expr.setFilters = new setFilters(); + +tokenize = Sizzle.tokenize = function( selector, parseOnly ) { + var matched, match, tokens, type, + soFar, groups, preFilters, + cached = tokenCache[ selector + " " ]; + + if ( cached ) { + return parseOnly ? 0 : cached.slice( 0 ); + } + + soFar = selector; + groups = []; + preFilters = Expr.preFilter; + + while ( soFar ) { + + // Comma and first run + if ( !matched || ( match = rcomma.exec( soFar ) ) ) { + if ( match ) { + + // Don't consume trailing commas as valid + soFar = soFar.slice( match[ 0 ].length ) || soFar; + } + groups.push( ( tokens = [] ) ); + } + + matched = false; + + // Combinators + if ( ( match = rcombinators.exec( soFar ) ) ) { + matched = match.shift(); + tokens.push( { + value: matched, + + // Cast descendant combinators to space + type: match[ 0 ].replace( rtrim, " " ) + } ); + soFar = soFar.slice( matched.length ); + } + + // Filters + for ( type in Expr.filter ) { + if ( ( match = matchExpr[ type ].exec( soFar ) ) && ( !preFilters[ type ] || + ( match = preFilters[ type ]( match ) ) ) ) { + matched = match.shift(); + tokens.push( { + value: matched, + type: type, + matches: match + } ); + soFar = soFar.slice( matched.length ); + } + } + + if ( !matched ) { + break; + } + } + + // Return the length of the invalid excess + // if we're just parsing + // Otherwise, throw an error or return tokens + return parseOnly ? + soFar.length : + soFar ? + Sizzle.error( selector ) : + + // Cache the tokens + tokenCache( selector, groups ).slice( 0 ); +}; + +function toSelector( tokens ) { + var i = 0, + len = tokens.length, + selector = ""; + for ( ; i < len; i++ ) { + selector += tokens[ i ].value; + } + return selector; +} + +function addCombinator( matcher, combinator, base ) { + var dir = combinator.dir, + skip = combinator.next, + key = skip || dir, + checkNonElements = base && key === "parentNode", + doneName = done++; + + return combinator.first ? + + // Check against closest ancestor/preceding element + function( elem, context, xml ) { + while ( ( elem = elem[ dir ] ) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + return matcher( elem, context, xml ); + } + } + return false; + } : + + // Check against all ancestor/preceding elements + function( elem, context, xml ) { + var oldCache, uniqueCache, outerCache, + newCache = [ dirruns, doneName ]; + + // We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching + if ( xml ) { + while ( ( elem = elem[ dir ] ) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + if ( matcher( elem, context, xml ) ) { + return true; + } + } + } + } else { + while ( ( elem = elem[ dir ] ) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + outerCache = elem[ expando ] || ( elem[ expando ] = {} ); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ elem.uniqueID ] || + ( outerCache[ elem.uniqueID ] = {} ); + + if ( skip && skip === elem.nodeName.toLowerCase() ) { + elem = elem[ dir ] || elem; + } else if ( ( oldCache = uniqueCache[ key ] ) && + oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { + + // Assign to newCache so results back-propagate to previous elements + return ( newCache[ 2 ] = oldCache[ 2 ] ); + } else { + + // Reuse newcache so results back-propagate to previous elements + uniqueCache[ key ] = newCache; + + // A match means we're done; a fail means we have to keep checking + if ( ( newCache[ 2 ] = matcher( elem, context, xml ) ) ) { + return true; + } + } + } + } + } + return false; + }; +} + +function elementMatcher( matchers ) { + return matchers.length > 1 ? + function( elem, context, xml ) { + var i = matchers.length; + while ( i-- ) { + if ( !matchers[ i ]( elem, context, xml ) ) { + return false; + } + } + return true; + } : + matchers[ 0 ]; +} + +function multipleContexts( selector, contexts, results ) { + var i = 0, + len = contexts.length; + for ( ; i < len; i++ ) { + Sizzle( selector, contexts[ i ], results ); + } + return results; +} + +function condense( unmatched, map, filter, context, xml ) { + var elem, + newUnmatched = [], + i = 0, + len = unmatched.length, + mapped = map != null; + + for ( ; i < len; i++ ) { + if ( ( elem = unmatched[ i ] ) ) { + if ( !filter || filter( elem, context, xml ) ) { + newUnmatched.push( elem ); + if ( mapped ) { + map.push( i ); + } + } + } + } + + return newUnmatched; +} + +function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { + if ( postFilter && !postFilter[ expando ] ) { + postFilter = setMatcher( postFilter ); + } + if ( postFinder && !postFinder[ expando ] ) { + postFinder = setMatcher( postFinder, postSelector ); + } + return markFunction( function( seed, results, context, xml ) { + var temp, i, elem, + preMap = [], + postMap = [], + preexisting = results.length, + + // Get initial elements from seed or context + elems = seed || multipleContexts( + selector || "*", + context.nodeType ? [ context ] : context, + [] + ), + + // Prefilter to get matcher input, preserving a map for seed-results synchronization + matcherIn = preFilter && ( seed || !selector ) ? + condense( elems, preMap, preFilter, context, xml ) : + elems, + + matcherOut = matcher ? + + // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, + postFinder || ( seed ? preFilter : preexisting || postFilter ) ? + + // ...intermediate processing is necessary + [] : + + // ...otherwise use results directly + results : + matcherIn; + + // Find primary matches + if ( matcher ) { + matcher( matcherIn, matcherOut, context, xml ); + } + + // Apply postFilter + if ( postFilter ) { + temp = condense( matcherOut, postMap ); + postFilter( temp, [], context, xml ); + + // Un-match failing elements by moving them back to matcherIn + i = temp.length; + while ( i-- ) { + if ( ( elem = temp[ i ] ) ) { + matcherOut[ postMap[ i ] ] = !( matcherIn[ postMap[ i ] ] = elem ); + } + } + } + + if ( seed ) { + if ( postFinder || preFilter ) { + if ( postFinder ) { + + // Get the final matcherOut by condensing this intermediate into postFinder contexts + temp = []; + i = matcherOut.length; + while ( i-- ) { + if ( ( elem = matcherOut[ i ] ) ) { + + // Restore matcherIn since elem is not yet a final match + temp.push( ( matcherIn[ i ] = elem ) ); + } + } + postFinder( null, ( matcherOut = [] ), temp, xml ); + } + + // Move matched elements from seed to results to keep them synchronized + i = matcherOut.length; + while ( i-- ) { + if ( ( elem = matcherOut[ i ] ) && + ( temp = postFinder ? indexOf( seed, elem ) : preMap[ i ] ) > -1 ) { + + seed[ temp ] = !( results[ temp ] = elem ); + } + } + } + + // Add elements to results, through postFinder if defined + } else { + matcherOut = condense( + matcherOut === results ? + matcherOut.splice( preexisting, matcherOut.length ) : + matcherOut + ); + if ( postFinder ) { + postFinder( null, results, matcherOut, xml ); + } else { + push.apply( results, matcherOut ); + } + } + } ); +} + +function matcherFromTokens( tokens ) { + var checkContext, matcher, j, + len = tokens.length, + leadingRelative = Expr.relative[ tokens[ 0 ].type ], + implicitRelative = leadingRelative || Expr.relative[ " " ], + i = leadingRelative ? 1 : 0, + + // The foundational matcher ensures that elements are reachable from top-level context(s) + matchContext = addCombinator( function( elem ) { + return elem === checkContext; + }, implicitRelative, true ), + matchAnyContext = addCombinator( function( elem ) { + return indexOf( checkContext, elem ) > -1; + }, implicitRelative, true ), + matchers = [ function( elem, context, xml ) { + var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( + ( checkContext = context ).nodeType ? + matchContext( elem, context, xml ) : + matchAnyContext( elem, context, xml ) ); + + // Avoid hanging onto element (issue #299) + checkContext = null; + return ret; + } ]; + + for ( ; i < len; i++ ) { + if ( ( matcher = Expr.relative[ tokens[ i ].type ] ) ) { + matchers = [ addCombinator( elementMatcher( matchers ), matcher ) ]; + } else { + matcher = Expr.filter[ tokens[ i ].type ].apply( null, tokens[ i ].matches ); + + // Return special upon seeing a positional matcher + if ( matcher[ expando ] ) { + + // Find the next relative operator (if any) for proper handling + j = ++i; + for ( ; j < len; j++ ) { + if ( Expr.relative[ tokens[ j ].type ] ) { + break; + } + } + return setMatcher( + i > 1 && elementMatcher( matchers ), + i > 1 && toSelector( + + // If the preceding token was a descendant combinator, insert an implicit any-element `*` + tokens + .slice( 0, i - 1 ) + .concat( { value: tokens[ i - 2 ].type === " " ? "*" : "" } ) + ).replace( rtrim, "$1" ), + matcher, + i < j && matcherFromTokens( tokens.slice( i, j ) ), + j < len && matcherFromTokens( ( tokens = tokens.slice( j ) ) ), + j < len && toSelector( tokens ) + ); + } + matchers.push( matcher ); + } + } + + return elementMatcher( matchers ); +} + +function matcherFromGroupMatchers( elementMatchers, setMatchers ) { + var bySet = setMatchers.length > 0, + byElement = elementMatchers.length > 0, + superMatcher = function( seed, context, xml, results, outermost ) { + var elem, j, matcher, + matchedCount = 0, + i = "0", + unmatched = seed && [], + setMatched = [], + contextBackup = outermostContext, + + // We must always have either seed elements or outermost context + elems = seed || byElement && Expr.find[ "TAG" ]( "*", outermost ), + + // Use integer dirruns iff this is the outermost matcher + dirrunsUnique = ( dirruns += contextBackup == null ? 1 : Math.random() || 0.1 ), + len = elems.length; + + if ( outermost ) { + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + outermostContext = context == document || context || outermost; + } + + // Add elements passing elementMatchers directly to results + // Support: IE<9, Safari + // Tolerate NodeList properties (IE: "length"; Safari: ) matching elements by id + for ( ; i !== len && ( elem = elems[ i ] ) != null; i++ ) { + if ( byElement && elem ) { + j = 0; + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( !context && elem.ownerDocument != document ) { + setDocument( elem ); + xml = !documentIsHTML; + } + while ( ( matcher = elementMatchers[ j++ ] ) ) { + if ( matcher( elem, context || document, xml ) ) { + results.push( elem ); + break; + } + } + if ( outermost ) { + dirruns = dirrunsUnique; + } + } + + // Track unmatched elements for set filters + if ( bySet ) { + + // They will have gone through all possible matchers + if ( ( elem = !matcher && elem ) ) { + matchedCount--; + } + + // Lengthen the array for every element, matched or not + if ( seed ) { + unmatched.push( elem ); + } + } + } + + // `i` is now the count of elements visited above, and adding it to `matchedCount` + // makes the latter nonnegative. + matchedCount += i; + + // Apply set filters to unmatched elements + // NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount` + // equals `i`), unless we didn't visit _any_ elements in the above loop because we have + // no element matchers and no seed. + // Incrementing an initially-string "0" `i` allows `i` to remain a string only in that + // case, which will result in a "00" `matchedCount` that differs from `i` but is also + // numerically zero. + if ( bySet && i !== matchedCount ) { + j = 0; + while ( ( matcher = setMatchers[ j++ ] ) ) { + matcher( unmatched, setMatched, context, xml ); + } + + if ( seed ) { + + // Reintegrate element matches to eliminate the need for sorting + if ( matchedCount > 0 ) { + while ( i-- ) { + if ( !( unmatched[ i ] || setMatched[ i ] ) ) { + setMatched[ i ] = pop.call( results ); + } + } + } + + // Discard index placeholder values to get only actual matches + setMatched = condense( setMatched ); + } + + // Add matches to results + push.apply( results, setMatched ); + + // Seedless set matches succeeding multiple successful matchers stipulate sorting + if ( outermost && !seed && setMatched.length > 0 && + ( matchedCount + setMatchers.length ) > 1 ) { + + Sizzle.uniqueSort( results ); + } + } + + // Override manipulation of globals by nested matchers + if ( outermost ) { + dirruns = dirrunsUnique; + outermostContext = contextBackup; + } + + return unmatched; + }; + + return bySet ? + markFunction( superMatcher ) : + superMatcher; +} + +compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) { + var i, + setMatchers = [], + elementMatchers = [], + cached = compilerCache[ selector + " " ]; + + if ( !cached ) { + + // Generate a function of recursive functions that can be used to check each element + if ( !match ) { + match = tokenize( selector ); + } + i = match.length; + while ( i-- ) { + cached = matcherFromTokens( match[ i ] ); + if ( cached[ expando ] ) { + setMatchers.push( cached ); + } else { + elementMatchers.push( cached ); + } + } + + // Cache the compiled function + cached = compilerCache( + selector, + matcherFromGroupMatchers( elementMatchers, setMatchers ) + ); + + // Save selector and tokenization + cached.selector = selector; + } + return cached; +}; + +/** + * A low-level selection function that works with Sizzle's compiled + * selector functions + * @param {String|Function} selector A selector or a pre-compiled + * selector function built with Sizzle.compile + * @param {Element} context + * @param {Array} [results] + * @param {Array} [seed] A set of elements to match against + */ +select = Sizzle.select = function( selector, context, results, seed ) { + var i, tokens, token, type, find, + compiled = typeof selector === "function" && selector, + match = !seed && tokenize( ( selector = compiled.selector || selector ) ); + + results = results || []; + + // Try to minimize operations if there is only one selector in the list and no seed + // (the latter of which guarantees us context) + if ( match.length === 1 ) { + + // Reduce context if the leading compound selector is an ID + tokens = match[ 0 ] = match[ 0 ].slice( 0 ); + if ( tokens.length > 2 && ( token = tokens[ 0 ] ).type === "ID" && + context.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[ 1 ].type ] ) { + + context = ( Expr.find[ "ID" ]( token.matches[ 0 ] + .replace( runescape, funescape ), context ) || [] )[ 0 ]; + if ( !context ) { + return results; + + // Precompiled matchers will still verify ancestry, so step up a level + } else if ( compiled ) { + context = context.parentNode; + } + + selector = selector.slice( tokens.shift().value.length ); + } + + // Fetch a seed set for right-to-left matching + i = matchExpr[ "needsContext" ].test( selector ) ? 0 : tokens.length; + while ( i-- ) { + token = tokens[ i ]; + + // Abort if we hit a combinator + if ( Expr.relative[ ( type = token.type ) ] ) { + break; + } + if ( ( find = Expr.find[ type ] ) ) { + + // Search, expanding context for leading sibling combinators + if ( ( seed = find( + token.matches[ 0 ].replace( runescape, funescape ), + rsibling.test( tokens[ 0 ].type ) && testContext( context.parentNode ) || + context + ) ) ) { + + // If seed is empty or no tokens remain, we can return early + tokens.splice( i, 1 ); + selector = seed.length && toSelector( tokens ); + if ( !selector ) { + push.apply( results, seed ); + return results; + } + + break; + } + } + } + } + + // Compile and execute a filtering function if one is not provided + // Provide `match` to avoid retokenization if we modified the selector above + ( compiled || compile( selector, match ) )( + seed, + context, + !documentIsHTML, + results, + !context || rsibling.test( selector ) && testContext( context.parentNode ) || context + ); + return results; +}; + +// One-time assignments + +// Sort stability +support.sortStable = expando.split( "" ).sort( sortOrder ).join( "" ) === expando; + +// Support: Chrome 14-35+ +// Always assume duplicates if they aren't passed to the comparison function +support.detectDuplicates = !!hasDuplicate; + +// Initialize against the default document +setDocument(); + +// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27) +// Detached nodes confoundingly follow *each other* +support.sortDetached = assert( function( el ) { + + // Should return 1, but returns 4 (following) + return el.compareDocumentPosition( document.createElement( "fieldset" ) ) & 1; +} ); + +// Support: IE<8 +// Prevent attribute/property "interpolation" +// https://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx +if ( !assert( function( el ) { + el.innerHTML = ""; + return el.firstChild.getAttribute( "href" ) === "#"; +} ) ) { + addHandle( "type|href|height|width", function( elem, name, isXML ) { + if ( !isXML ) { + return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 ); + } + } ); +} + +// Support: IE<9 +// Use defaultValue in place of getAttribute("value") +if ( !support.attributes || !assert( function( el ) { + el.innerHTML = ""; + el.firstChild.setAttribute( "value", "" ); + return el.firstChild.getAttribute( "value" ) === ""; +} ) ) { + addHandle( "value", function( elem, _name, isXML ) { + if ( !isXML && elem.nodeName.toLowerCase() === "input" ) { + return elem.defaultValue; + } + } ); +} + +// Support: IE<9 +// Use getAttributeNode to fetch booleans when getAttribute lies +if ( !assert( function( el ) { + return el.getAttribute( "disabled" ) == null; +} ) ) { + addHandle( booleans, function( elem, name, isXML ) { + var val; + if ( !isXML ) { + return elem[ name ] === true ? name.toLowerCase() : + ( val = elem.getAttributeNode( name ) ) && val.specified ? + val.value : + null; + } + } ); +} + +return Sizzle; + +} )( window ); + + + +jQuery.find = Sizzle; +jQuery.expr = Sizzle.selectors; + +// Deprecated +jQuery.expr[ ":" ] = jQuery.expr.pseudos; +jQuery.uniqueSort = jQuery.unique = Sizzle.uniqueSort; +jQuery.text = Sizzle.getText; +jQuery.isXMLDoc = Sizzle.isXML; +jQuery.contains = Sizzle.contains; +jQuery.escapeSelector = Sizzle.escape; + + + + +var dir = function( elem, dir, until ) { + var matched = [], + truncate = until !== undefined; + + while ( ( elem = elem[ dir ] ) && elem.nodeType !== 9 ) { + if ( elem.nodeType === 1 ) { + if ( truncate && jQuery( elem ).is( until ) ) { + break; + } + matched.push( elem ); + } + } + return matched; +}; + + +var siblings = function( n, elem ) { + var matched = []; + + for ( ; n; n = n.nextSibling ) { + if ( n.nodeType === 1 && n !== elem ) { + matched.push( n ); + } + } + + return matched; +}; + + +var rneedsContext = jQuery.expr.match.needsContext; + + + +function nodeName( elem, name ) { + + return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); + +}; +var rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i ); + + + +// Implement the identical functionality for filter and not +function winnow( elements, qualifier, not ) { + if ( isFunction( qualifier ) ) { + return jQuery.grep( elements, function( elem, i ) { + return !!qualifier.call( elem, i, elem ) !== not; + } ); + } + + // Single element + if ( qualifier.nodeType ) { + return jQuery.grep( elements, function( elem ) { + return ( elem === qualifier ) !== not; + } ); + } + + // Arraylike of elements (jQuery, arguments, Array) + if ( typeof qualifier !== "string" ) { + return jQuery.grep( elements, function( elem ) { + return ( indexOf.call( qualifier, elem ) > -1 ) !== not; + } ); + } + + // Filtered directly for both simple and complex selectors + return jQuery.filter( qualifier, elements, not ); +} + +jQuery.filter = function( expr, elems, not ) { + var elem = elems[ 0 ]; + + if ( not ) { + expr = ":not(" + expr + ")"; + } + + if ( elems.length === 1 && elem.nodeType === 1 ) { + return jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : []; + } + + return jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) { + return elem.nodeType === 1; + } ) ); +}; + +jQuery.fn.extend( { + find: function( selector ) { + var i, ret, + len = this.length, + self = this; + + if ( typeof selector !== "string" ) { + return this.pushStack( jQuery( selector ).filter( function() { + for ( i = 0; i < len; i++ ) { + if ( jQuery.contains( self[ i ], this ) ) { + return true; + } + } + } ) ); + } + + ret = this.pushStack( [] ); + + for ( i = 0; i < len; i++ ) { + jQuery.find( selector, self[ i ], ret ); + } + + return len > 1 ? jQuery.uniqueSort( ret ) : ret; + }, + filter: function( selector ) { + return this.pushStack( winnow( this, selector || [], false ) ); + }, + not: function( selector ) { + return this.pushStack( winnow( this, selector || [], true ) ); + }, + is: function( selector ) { + return !!winnow( + this, + + // If this is a positional/relative selector, check membership in the returned set + // so $("p:first").is("p:last") won't return true for a doc with two "p". + typeof selector === "string" && rneedsContext.test( selector ) ? + jQuery( selector ) : + selector || [], + false + ).length; + } +} ); + + +// Initialize a jQuery object + + +// A central reference to the root jQuery(document) +var rootjQuery, + + // A simple way to check for HTML strings + // Prioritize #id over to avoid XSS via location.hash (#9521) + // Strict HTML recognition (#11290: must start with <) + // Shortcut simple #id case for speed + rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/, + + init = jQuery.fn.init = function( selector, context, root ) { + var match, elem; + + // HANDLE: $(""), $(null), $(undefined), $(false) + if ( !selector ) { + return this; + } + + // Method init() accepts an alternate rootjQuery + // so migrate can support jQuery.sub (gh-2101) + root = root || rootjQuery; + + // Handle HTML strings + if ( typeof selector === "string" ) { + if ( selector[ 0 ] === "<" && + selector[ selector.length - 1 ] === ">" && + selector.length >= 3 ) { + + // Assume that strings that start and end with <> are HTML and skip the regex check + match = [ null, selector, null ]; + + } else { + match = rquickExpr.exec( selector ); + } + + // Match html or make sure no context is specified for #id + if ( match && ( match[ 1 ] || !context ) ) { + + // HANDLE: $(html) -> $(array) + if ( match[ 1 ] ) { + context = context instanceof jQuery ? context[ 0 ] : context; + + // Option to run scripts is true for back-compat + // Intentionally let the error be thrown if parseHTML is not present + jQuery.merge( this, jQuery.parseHTML( + match[ 1 ], + context && context.nodeType ? context.ownerDocument || context : document, + true + ) ); + + // HANDLE: $(html, props) + if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) { + for ( match in context ) { + + // Properties of context are called as methods if possible + if ( isFunction( this[ match ] ) ) { + this[ match ]( context[ match ] ); + + // ...and otherwise set as attributes + } else { + this.attr( match, context[ match ] ); + } + } + } + + return this; + + // HANDLE: $(#id) + } else { + elem = document.getElementById( match[ 2 ] ); + + if ( elem ) { + + // Inject the element directly into the jQuery object + this[ 0 ] = elem; + this.length = 1; + } + return this; + } + + // HANDLE: $(expr, $(...)) + } else if ( !context || context.jquery ) { + return ( context || root ).find( selector ); + + // HANDLE: $(expr, context) + // (which is just equivalent to: $(context).find(expr) + } else { + return this.constructor( context ).find( selector ); + } + + // HANDLE: $(DOMElement) + } else if ( selector.nodeType ) { + this[ 0 ] = selector; + this.length = 1; + return this; + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( isFunction( selector ) ) { + return root.ready !== undefined ? + root.ready( selector ) : + + // Execute immediately if ready is not present + selector( jQuery ); + } + + return jQuery.makeArray( selector, this ); + }; + +// Give the init function the jQuery prototype for later instantiation +init.prototype = jQuery.fn; + +// Initialize central reference +rootjQuery = jQuery( document ); + + +var rparentsprev = /^(?:parents|prev(?:Until|All))/, + + // Methods guaranteed to produce a unique set when starting from a unique set + guaranteedUnique = { + children: true, + contents: true, + next: true, + prev: true + }; + +jQuery.fn.extend( { + has: function( target ) { + var targets = jQuery( target, this ), + l = targets.length; + + return this.filter( function() { + var i = 0; + for ( ; i < l; i++ ) { + if ( jQuery.contains( this, targets[ i ] ) ) { + return true; + } + } + } ); + }, + + closest: function( selectors, context ) { + var cur, + i = 0, + l = this.length, + matched = [], + targets = typeof selectors !== "string" && jQuery( selectors ); + + // Positional selectors never match, since there's no _selection_ context + if ( !rneedsContext.test( selectors ) ) { + for ( ; i < l; i++ ) { + for ( cur = this[ i ]; cur && cur !== context; cur = cur.parentNode ) { + + // Always skip document fragments + if ( cur.nodeType < 11 && ( targets ? + targets.index( cur ) > -1 : + + // Don't pass non-elements to Sizzle + cur.nodeType === 1 && + jQuery.find.matchesSelector( cur, selectors ) ) ) { + + matched.push( cur ); + break; + } + } + } + } + + return this.pushStack( matched.length > 1 ? jQuery.uniqueSort( matched ) : matched ); + }, + + // Determine the position of an element within the set + index: function( elem ) { + + // No argument, return index in parent + if ( !elem ) { + return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1; + } + + // Index in selector + if ( typeof elem === "string" ) { + return indexOf.call( jQuery( elem ), this[ 0 ] ); + } + + // Locate the position of the desired element + return indexOf.call( this, + + // If it receives a jQuery object, the first element is used + elem.jquery ? elem[ 0 ] : elem + ); + }, + + add: function( selector, context ) { + return this.pushStack( + jQuery.uniqueSort( + jQuery.merge( this.get(), jQuery( selector, context ) ) + ) + ); + }, + + addBack: function( selector ) { + return this.add( selector == null ? + this.prevObject : this.prevObject.filter( selector ) + ); + } +} ); + +function sibling( cur, dir ) { + while ( ( cur = cur[ dir ] ) && cur.nodeType !== 1 ) {} + return cur; +} + +jQuery.each( { + parent: function( elem ) { + var parent = elem.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + parents: function( elem ) { + return dir( elem, "parentNode" ); + }, + parentsUntil: function( elem, _i, until ) { + return dir( elem, "parentNode", until ); + }, + next: function( elem ) { + return sibling( elem, "nextSibling" ); + }, + prev: function( elem ) { + return sibling( elem, "previousSibling" ); + }, + nextAll: function( elem ) { + return dir( elem, "nextSibling" ); + }, + prevAll: function( elem ) { + return dir( elem, "previousSibling" ); + }, + nextUntil: function( elem, _i, until ) { + return dir( elem, "nextSibling", until ); + }, + prevUntil: function( elem, _i, until ) { + return dir( elem, "previousSibling", until ); + }, + siblings: function( elem ) { + return siblings( ( elem.parentNode || {} ).firstChild, elem ); + }, + children: function( elem ) { + return siblings( elem.firstChild ); + }, + contents: function( elem ) { + if ( elem.contentDocument != null && + + // Support: IE 11+ + // elements with no `data` attribute has an object + // `contentDocument` with a `null` prototype. + getProto( elem.contentDocument ) ) { + + return elem.contentDocument; + } + + // Support: IE 9 - 11 only, iOS 7 only, Android Browser <=4.3 only + // Treat the template element as a regular one in browsers that + // don't support it. + if ( nodeName( elem, "template" ) ) { + elem = elem.content || elem; + } + + return jQuery.merge( [], elem.childNodes ); + } +}, function( name, fn ) { + jQuery.fn[ name ] = function( until, selector ) { + var matched = jQuery.map( this, fn, until ); + + if ( name.slice( -5 ) !== "Until" ) { + selector = until; + } + + if ( selector && typeof selector === "string" ) { + matched = jQuery.filter( selector, matched ); + } + + if ( this.length > 1 ) { + + // Remove duplicates + if ( !guaranteedUnique[ name ] ) { + jQuery.uniqueSort( matched ); + } + + // Reverse order for parents* and prev-derivatives + if ( rparentsprev.test( name ) ) { + matched.reverse(); + } + } + + return this.pushStack( matched ); + }; +} ); +var rnothtmlwhite = ( /[^\x20\t\r\n\f]+/g ); + + + +// Convert String-formatted options into Object-formatted ones +function createOptions( options ) { + var object = {}; + jQuery.each( options.match( rnothtmlwhite ) || [], function( _, flag ) { + object[ flag ] = true; + } ); + return object; +} + +/* + * Create a callback list using the following parameters: + * + * options: an optional list of space-separated options that will change how + * the callback list behaves or a more traditional option object + * + * By default a callback list will act like an event callback list and can be + * "fired" multiple times. + * + * Possible options: + * + * once: will ensure the callback list can only be fired once (like a Deferred) + * + * memory: will keep track of previous values and will call any callback added + * after the list has been fired right away with the latest "memorized" + * values (like a Deferred) + * + * unique: will ensure a callback can only be added once (no duplicate in the list) + * + * stopOnFalse: interrupt callings when a callback returns false + * + */ +jQuery.Callbacks = function( options ) { + + // Convert options from String-formatted to Object-formatted if needed + // (we check in cache first) + options = typeof options === "string" ? + createOptions( options ) : + jQuery.extend( {}, options ); + + var // Flag to know if list is currently firing + firing, + + // Last fire value for non-forgettable lists + memory, + + // Flag to know if list was already fired + fired, + + // Flag to prevent firing + locked, + + // Actual callback list + list = [], + + // Queue of execution data for repeatable lists + queue = [], + + // Index of currently firing callback (modified by add/remove as needed) + firingIndex = -1, + + // Fire callbacks + fire = function() { + + // Enforce single-firing + locked = locked || options.once; + + // Execute callbacks for all pending executions, + // respecting firingIndex overrides and runtime changes + fired = firing = true; + for ( ; queue.length; firingIndex = -1 ) { + memory = queue.shift(); + while ( ++firingIndex < list.length ) { + + // Run callback and check for early termination + if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false && + options.stopOnFalse ) { + + // Jump to end and forget the data so .add doesn't re-fire + firingIndex = list.length; + memory = false; + } + } + } + + // Forget the data if we're done with it + if ( !options.memory ) { + memory = false; + } + + firing = false; + + // Clean up if we're done firing for good + if ( locked ) { + + // Keep an empty list if we have data for future add calls + if ( memory ) { + list = []; + + // Otherwise, this object is spent + } else { + list = ""; + } + } + }, + + // Actual Callbacks object + self = { + + // Add a callback or a collection of callbacks to the list + add: function() { + if ( list ) { + + // If we have memory from a past run, we should fire after adding + if ( memory && !firing ) { + firingIndex = list.length - 1; + queue.push( memory ); + } + + ( function add( args ) { + jQuery.each( args, function( _, arg ) { + if ( isFunction( arg ) ) { + if ( !options.unique || !self.has( arg ) ) { + list.push( arg ); + } + } else if ( arg && arg.length && toType( arg ) !== "string" ) { + + // Inspect recursively + add( arg ); + } + } ); + } )( arguments ); + + if ( memory && !firing ) { + fire(); + } + } + return this; + }, + + // Remove a callback from the list + remove: function() { + jQuery.each( arguments, function( _, arg ) { + var index; + while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { + list.splice( index, 1 ); + + // Handle firing indexes + if ( index <= firingIndex ) { + firingIndex--; + } + } + } ); + return this; + }, + + // Check if a given callback is in the list. + // If no argument is given, return whether or not list has callbacks attached. + has: function( fn ) { + return fn ? + jQuery.inArray( fn, list ) > -1 : + list.length > 0; + }, + + // Remove all callbacks from the list + empty: function() { + if ( list ) { + list = []; + } + return this; + }, + + // Disable .fire and .add + // Abort any current/pending executions + // Clear all callbacks and values + disable: function() { + locked = queue = []; + list = memory = ""; + return this; + }, + disabled: function() { + return !list; + }, + + // Disable .fire + // Also disable .add unless we have memory (since it would have no effect) + // Abort any pending executions + lock: function() { + locked = queue = []; + if ( !memory && !firing ) { + list = memory = ""; + } + return this; + }, + locked: function() { + return !!locked; + }, + + // Call all callbacks with the given context and arguments + fireWith: function( context, args ) { + if ( !locked ) { + args = args || []; + args = [ context, args.slice ? args.slice() : args ]; + queue.push( args ); + if ( !firing ) { + fire(); + } + } + return this; + }, + + // Call all the callbacks with the given arguments + fire: function() { + self.fireWith( this, arguments ); + return this; + }, + + // To know if the callbacks have already been called at least once + fired: function() { + return !!fired; + } + }; + + return self; +}; + + +function Identity( v ) { + return v; +} +function Thrower( ex ) { + throw ex; +} + +function adoptValue( value, resolve, reject, noValue ) { + var method; + + try { + + // Check for promise aspect first to privilege synchronous behavior + if ( value && isFunction( ( method = value.promise ) ) ) { + method.call( value ).done( resolve ).fail( reject ); + + // Other thenables + } else if ( value && isFunction( ( method = value.then ) ) ) { + method.call( value, resolve, reject ); + + // Other non-thenables + } else { + + // Control `resolve` arguments by letting Array#slice cast boolean `noValue` to integer: + // * false: [ value ].slice( 0 ) => resolve( value ) + // * true: [ value ].slice( 1 ) => resolve() + resolve.apply( undefined, [ value ].slice( noValue ) ); + } + + // For Promises/A+, convert exceptions into rejections + // Since jQuery.when doesn't unwrap thenables, we can skip the extra checks appearing in + // Deferred#then to conditionally suppress rejection. + } catch ( value ) { + + // Support: Android 4.0 only + // Strict mode functions invoked without .call/.apply get global-object context + reject.apply( undefined, [ value ] ); + } +} + +jQuery.extend( { + + Deferred: function( func ) { + var tuples = [ + + // action, add listener, callbacks, + // ... .then handlers, argument index, [final state] + [ "notify", "progress", jQuery.Callbacks( "memory" ), + jQuery.Callbacks( "memory" ), 2 ], + [ "resolve", "done", jQuery.Callbacks( "once memory" ), + jQuery.Callbacks( "once memory" ), 0, "resolved" ], + [ "reject", "fail", jQuery.Callbacks( "once memory" ), + jQuery.Callbacks( "once memory" ), 1, "rejected" ] + ], + state = "pending", + promise = { + state: function() { + return state; + }, + always: function() { + deferred.done( arguments ).fail( arguments ); + return this; + }, + "catch": function( fn ) { + return promise.then( null, fn ); + }, + + // Keep pipe for back-compat + pipe: function( /* fnDone, fnFail, fnProgress */ ) { + var fns = arguments; + + return jQuery.Deferred( function( newDefer ) { + jQuery.each( tuples, function( _i, tuple ) { + + // Map tuples (progress, done, fail) to arguments (done, fail, progress) + var fn = isFunction( fns[ tuple[ 4 ] ] ) && fns[ tuple[ 4 ] ]; + + // deferred.progress(function() { bind to newDefer or newDefer.notify }) + // deferred.done(function() { bind to newDefer or newDefer.resolve }) + // deferred.fail(function() { bind to newDefer or newDefer.reject }) + deferred[ tuple[ 1 ] ]( function() { + var returned = fn && fn.apply( this, arguments ); + if ( returned && isFunction( returned.promise ) ) { + returned.promise() + .progress( newDefer.notify ) + .done( newDefer.resolve ) + .fail( newDefer.reject ); + } else { + newDefer[ tuple[ 0 ] + "With" ]( + this, + fn ? [ returned ] : arguments + ); + } + } ); + } ); + fns = null; + } ).promise(); + }, + then: function( onFulfilled, onRejected, onProgress ) { + var maxDepth = 0; + function resolve( depth, deferred, handler, special ) { + return function() { + var that = this, + args = arguments, + mightThrow = function() { + var returned, then; + + // Support: Promises/A+ section 2.3.3.3.3 + // https://promisesaplus.com/#point-59 + // Ignore double-resolution attempts + if ( depth < maxDepth ) { + return; + } + + returned = handler.apply( that, args ); + + // Support: Promises/A+ section 2.3.1 + // https://promisesaplus.com/#point-48 + if ( returned === deferred.promise() ) { + throw new TypeError( "Thenable self-resolution" ); + } + + // Support: Promises/A+ sections 2.3.3.1, 3.5 + // https://promisesaplus.com/#point-54 + // https://promisesaplus.com/#point-75 + // Retrieve `then` only once + then = returned && + + // Support: Promises/A+ section 2.3.4 + // https://promisesaplus.com/#point-64 + // Only check objects and functions for thenability + ( typeof returned === "object" || + typeof returned === "function" ) && + returned.then; + + // Handle a returned thenable + if ( isFunction( then ) ) { + + // Special processors (notify) just wait for resolution + if ( special ) { + then.call( + returned, + resolve( maxDepth, deferred, Identity, special ), + resolve( maxDepth, deferred, Thrower, special ) + ); + + // Normal processors (resolve) also hook into progress + } else { + + // ...and disregard older resolution values + maxDepth++; + + then.call( + returned, + resolve( maxDepth, deferred, Identity, special ), + resolve( maxDepth, deferred, Thrower, special ), + resolve( maxDepth, deferred, Identity, + deferred.notifyWith ) + ); + } + + // Handle all other returned values + } else { + + // Only substitute handlers pass on context + // and multiple values (non-spec behavior) + if ( handler !== Identity ) { + that = undefined; + args = [ returned ]; + } + + // Process the value(s) + // Default process is resolve + ( special || deferred.resolveWith )( that, args ); + } + }, + + // Only normal processors (resolve) catch and reject exceptions + process = special ? + mightThrow : + function() { + try { + mightThrow(); + } catch ( e ) { + + if ( jQuery.Deferred.exceptionHook ) { + jQuery.Deferred.exceptionHook( e, + process.stackTrace ); + } + + // Support: Promises/A+ section 2.3.3.3.4.1 + // https://promisesaplus.com/#point-61 + // Ignore post-resolution exceptions + if ( depth + 1 >= maxDepth ) { + + // Only substitute handlers pass on context + // and multiple values (non-spec behavior) + if ( handler !== Thrower ) { + that = undefined; + args = [ e ]; + } + + deferred.rejectWith( that, args ); + } + } + }; + + // Support: Promises/A+ section 2.3.3.3.1 + // https://promisesaplus.com/#point-57 + // Re-resolve promises immediately to dodge false rejection from + // subsequent errors + if ( depth ) { + process(); + } else { + + // Call an optional hook to record the stack, in case of exception + // since it's otherwise lost when execution goes async + if ( jQuery.Deferred.getStackHook ) { + process.stackTrace = jQuery.Deferred.getStackHook(); + } + window.setTimeout( process ); + } + }; + } + + return jQuery.Deferred( function( newDefer ) { + + // progress_handlers.add( ... ) + tuples[ 0 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onProgress ) ? + onProgress : + Identity, + newDefer.notifyWith + ) + ); + + // fulfilled_handlers.add( ... ) + tuples[ 1 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onFulfilled ) ? + onFulfilled : + Identity + ) + ); + + // rejected_handlers.add( ... ) + tuples[ 2 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onRejected ) ? + onRejected : + Thrower + ) + ); + } ).promise(); + }, + + // Get a promise for this deferred + // If obj is provided, the promise aspect is added to the object + promise: function( obj ) { + return obj != null ? jQuery.extend( obj, promise ) : promise; + } + }, + deferred = {}; + + // Add list-specific methods + jQuery.each( tuples, function( i, tuple ) { + var list = tuple[ 2 ], + stateString = tuple[ 5 ]; + + // promise.progress = list.add + // promise.done = list.add + // promise.fail = list.add + promise[ tuple[ 1 ] ] = list.add; + + // Handle state + if ( stateString ) { + list.add( + function() { + + // state = "resolved" (i.e., fulfilled) + // state = "rejected" + state = stateString; + }, + + // rejected_callbacks.disable + // fulfilled_callbacks.disable + tuples[ 3 - i ][ 2 ].disable, + + // rejected_handlers.disable + // fulfilled_handlers.disable + tuples[ 3 - i ][ 3 ].disable, + + // progress_callbacks.lock + tuples[ 0 ][ 2 ].lock, + + // progress_handlers.lock + tuples[ 0 ][ 3 ].lock + ); + } + + // progress_handlers.fire + // fulfilled_handlers.fire + // rejected_handlers.fire + list.add( tuple[ 3 ].fire ); + + // deferred.notify = function() { deferred.notifyWith(...) } + // deferred.resolve = function() { deferred.resolveWith(...) } + // deferred.reject = function() { deferred.rejectWith(...) } + deferred[ tuple[ 0 ] ] = function() { + deferred[ tuple[ 0 ] + "With" ]( this === deferred ? undefined : this, arguments ); + return this; + }; + + // deferred.notifyWith = list.fireWith + // deferred.resolveWith = list.fireWith + // deferred.rejectWith = list.fireWith + deferred[ tuple[ 0 ] + "With" ] = list.fireWith; + } ); + + // Make the deferred a promise + promise.promise( deferred ); + + // Call given func if any + if ( func ) { + func.call( deferred, deferred ); + } + + // All done! + return deferred; + }, + + // Deferred helper + when: function( singleValue ) { + var + + // count of uncompleted subordinates + remaining = arguments.length, + + // count of unprocessed arguments + i = remaining, + + // subordinate fulfillment data + resolveContexts = Array( i ), + resolveValues = slice.call( arguments ), + + // the master Deferred + master = jQuery.Deferred(), + + // subordinate callback factory + updateFunc = function( i ) { + return function( value ) { + resolveContexts[ i ] = this; + resolveValues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value; + if ( !( --remaining ) ) { + master.resolveWith( resolveContexts, resolveValues ); + } + }; + }; + + // Single- and empty arguments are adopted like Promise.resolve + if ( remaining <= 1 ) { + adoptValue( singleValue, master.done( updateFunc( i ) ).resolve, master.reject, + !remaining ); + + // Use .then() to unwrap secondary thenables (cf. gh-3000) + if ( master.state() === "pending" || + isFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) { + + return master.then(); + } + } + + // Multiple arguments are aggregated like Promise.all array elements + while ( i-- ) { + adoptValue( resolveValues[ i ], updateFunc( i ), master.reject ); + } + + return master.promise(); + } +} ); + + +// These usually indicate a programmer mistake during development, +// warn about them ASAP rather than swallowing them by default. +var rerrorNames = /^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/; + +jQuery.Deferred.exceptionHook = function( error, stack ) { + + // Support: IE 8 - 9 only + // Console exists when dev tools are open, which can happen at any time + if ( window.console && window.console.warn && error && rerrorNames.test( error.name ) ) { + window.console.warn( "jQuery.Deferred exception: " + error.message, error.stack, stack ); + } +}; + + + + +jQuery.readyException = function( error ) { + window.setTimeout( function() { + throw error; + } ); +}; + + + + +// The deferred used on DOM ready +var readyList = jQuery.Deferred(); + +jQuery.fn.ready = function( fn ) { + + readyList + .then( fn ) + + // Wrap jQuery.readyException in a function so that the lookup + // happens at the time of error handling instead of callback + // registration. + .catch( function( error ) { + jQuery.readyException( error ); + } ); + + return this; +}; + +jQuery.extend( { + + // Is the DOM ready to be used? Set to true once it occurs. + isReady: false, + + // A counter to track how many items to wait for before + // the ready event fires. See #6781 + readyWait: 1, + + // Handle when the DOM is ready + ready: function( wait ) { + + // Abort if there are pending holds or we're already ready + if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { + return; + } + + // Remember that the DOM is ready + jQuery.isReady = true; + + // If a normal DOM Ready event fired, decrement, and wait if need be + if ( wait !== true && --jQuery.readyWait > 0 ) { + return; + } + + // If there are functions bound, to execute + readyList.resolveWith( document, [ jQuery ] ); + } +} ); + +jQuery.ready.then = readyList.then; + +// The ready event handler and self cleanup method +function completed() { + document.removeEventListener( "DOMContentLoaded", completed ); + window.removeEventListener( "load", completed ); + jQuery.ready(); +} + +// Catch cases where $(document).ready() is called +// after the browser event has already occurred. +// Support: IE <=9 - 10 only +// Older IE sometimes signals "interactive" too soon +if ( document.readyState === "complete" || + ( document.readyState !== "loading" && !document.documentElement.doScroll ) ) { + + // Handle it asynchronously to allow scripts the opportunity to delay ready + window.setTimeout( jQuery.ready ); + +} else { + + // Use the handy event callback + document.addEventListener( "DOMContentLoaded", completed ); + + // A fallback to window.onload, that will always work + window.addEventListener( "load", completed ); +} + + + + +// Multifunctional method to get and set values of a collection +// The value/s can optionally be executed if it's a function +var access = function( elems, fn, key, value, chainable, emptyGet, raw ) { + var i = 0, + len = elems.length, + bulk = key == null; + + // Sets many values + if ( toType( key ) === "object" ) { + chainable = true; + for ( i in key ) { + access( elems, fn, i, key[ i ], true, emptyGet, raw ); + } + + // Sets one value + } else if ( value !== undefined ) { + chainable = true; + + if ( !isFunction( value ) ) { + raw = true; + } + + if ( bulk ) { + + // Bulk operations run against the entire set + if ( raw ) { + fn.call( elems, value ); + fn = null; + + // ...except when executing function values + } else { + bulk = fn; + fn = function( elem, _key, value ) { + return bulk.call( jQuery( elem ), value ); + }; + } + } + + if ( fn ) { + for ( ; i < len; i++ ) { + fn( + elems[ i ], key, raw ? + value : + value.call( elems[ i ], i, fn( elems[ i ], key ) ) + ); + } + } + } + + if ( chainable ) { + return elems; + } + + // Gets + if ( bulk ) { + return fn.call( elems ); + } + + return len ? fn( elems[ 0 ], key ) : emptyGet; +}; + + +// Matches dashed string for camelizing +var rmsPrefix = /^-ms-/, + rdashAlpha = /-([a-z])/g; + +// Used by camelCase as callback to replace() +function fcamelCase( _all, letter ) { + return letter.toUpperCase(); +} + +// Convert dashed to camelCase; used by the css and data modules +// Support: IE <=9 - 11, Edge 12 - 15 +// Microsoft forgot to hump their vendor prefix (#9572) +function camelCase( string ) { + return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); +} +var acceptData = function( owner ) { + + // Accepts only: + // - Node + // - Node.ELEMENT_NODE + // - Node.DOCUMENT_NODE + // - Object + // - Any + return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType ); +}; + + + + +function Data() { + this.expando = jQuery.expando + Data.uid++; +} + +Data.uid = 1; + +Data.prototype = { + + cache: function( owner ) { + + // Check if the owner object already has a cache + var value = owner[ this.expando ]; + + // If not, create one + if ( !value ) { + value = {}; + + // We can accept data for non-element nodes in modern browsers, + // but we should not, see #8335. + // Always return an empty object. + if ( acceptData( owner ) ) { + + // If it is a node unlikely to be stringify-ed or looped over + // use plain assignment + if ( owner.nodeType ) { + owner[ this.expando ] = value; + + // Otherwise secure it in a non-enumerable property + // configurable must be true to allow the property to be + // deleted when data is removed + } else { + Object.defineProperty( owner, this.expando, { + value: value, + configurable: true + } ); + } + } + } + + return value; + }, + set: function( owner, data, value ) { + var prop, + cache = this.cache( owner ); + + // Handle: [ owner, key, value ] args + // Always use camelCase key (gh-2257) + if ( typeof data === "string" ) { + cache[ camelCase( data ) ] = value; + + // Handle: [ owner, { properties } ] args + } else { + + // Copy the properties one-by-one to the cache object + for ( prop in data ) { + cache[ camelCase( prop ) ] = data[ prop ]; + } + } + return cache; + }, + get: function( owner, key ) { + return key === undefined ? + this.cache( owner ) : + + // Always use camelCase key (gh-2257) + owner[ this.expando ] && owner[ this.expando ][ camelCase( key ) ]; + }, + access: function( owner, key, value ) { + + // In cases where either: + // + // 1. No key was specified + // 2. A string key was specified, but no value provided + // + // Take the "read" path and allow the get method to determine + // which value to return, respectively either: + // + // 1. The entire cache object + // 2. The data stored at the key + // + if ( key === undefined || + ( ( key && typeof key === "string" ) && value === undefined ) ) { + + return this.get( owner, key ); + } + + // When the key is not a string, or both a key and value + // are specified, set or extend (existing objects) with either: + // + // 1. An object of properties + // 2. A key and value + // + this.set( owner, key, value ); + + // Since the "set" path can have two possible entry points + // return the expected data based on which path was taken[*] + return value !== undefined ? value : key; + }, + remove: function( owner, key ) { + var i, + cache = owner[ this.expando ]; + + if ( cache === undefined ) { + return; + } + + if ( key !== undefined ) { + + // Support array or space separated string of keys + if ( Array.isArray( key ) ) { + + // If key is an array of keys... + // We always set camelCase keys, so remove that. + key = key.map( camelCase ); + } else { + key = camelCase( key ); + + // If a key with the spaces exists, use it. + // Otherwise, create an array by matching non-whitespace + key = key in cache ? + [ key ] : + ( key.match( rnothtmlwhite ) || [] ); + } + + i = key.length; + + while ( i-- ) { + delete cache[ key[ i ] ]; + } + } + + // Remove the expando if there's no more data + if ( key === undefined || jQuery.isEmptyObject( cache ) ) { + + // Support: Chrome <=35 - 45 + // Webkit & Blink performance suffers when deleting properties + // from DOM nodes, so set to undefined instead + // https://bugs.chromium.org/p/chromium/issues/detail?id=378607 (bug restricted) + if ( owner.nodeType ) { + owner[ this.expando ] = undefined; + } else { + delete owner[ this.expando ]; + } + } + }, + hasData: function( owner ) { + var cache = owner[ this.expando ]; + return cache !== undefined && !jQuery.isEmptyObject( cache ); + } +}; +var dataPriv = new Data(); + +var dataUser = new Data(); + + + +// Implementation Summary +// +// 1. Enforce API surface and semantic compatibility with 1.9.x branch +// 2. Improve the module's maintainability by reducing the storage +// paths to a single mechanism. +// 3. Use the same single mechanism to support "private" and "user" data. +// 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData) +// 5. Avoid exposing implementation details on user objects (eg. expando properties) +// 6. Provide a clear path for implementation upgrade to WeakMap in 2014 + +var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/, + rmultiDash = /[A-Z]/g; + +function getData( data ) { + if ( data === "true" ) { + return true; + } + + if ( data === "false" ) { + return false; + } + + if ( data === "null" ) { + return null; + } + + // Only convert to a number if it doesn't change the string + if ( data === +data + "" ) { + return +data; + } + + if ( rbrace.test( data ) ) { + return JSON.parse( data ); + } + + return data; +} + +function dataAttr( elem, key, data ) { + var name; + + // If nothing was found internally, try to fetch any + // data from the HTML5 data-* attribute + if ( data === undefined && elem.nodeType === 1 ) { + name = "data-" + key.replace( rmultiDash, "-$&" ).toLowerCase(); + data = elem.getAttribute( name ); + + if ( typeof data === "string" ) { + try { + data = getData( data ); + } catch ( e ) {} + + // Make sure we set the data so it isn't changed later + dataUser.set( elem, key, data ); + } else { + data = undefined; + } + } + return data; +} + +jQuery.extend( { + hasData: function( elem ) { + return dataUser.hasData( elem ) || dataPriv.hasData( elem ); + }, + + data: function( elem, name, data ) { + return dataUser.access( elem, name, data ); + }, + + removeData: function( elem, name ) { + dataUser.remove( elem, name ); + }, + + // TODO: Now that all calls to _data and _removeData have been replaced + // with direct calls to dataPriv methods, these can be deprecated. + _data: function( elem, name, data ) { + return dataPriv.access( elem, name, data ); + }, + + _removeData: function( elem, name ) { + dataPriv.remove( elem, name ); + } +} ); + +jQuery.fn.extend( { + data: function( key, value ) { + var i, name, data, + elem = this[ 0 ], + attrs = elem && elem.attributes; + + // Gets all values + if ( key === undefined ) { + if ( this.length ) { + data = dataUser.get( elem ); + + if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) { + i = attrs.length; + while ( i-- ) { + + // Support: IE 11 only + // The attrs elements can be null (#14894) + if ( attrs[ i ] ) { + name = attrs[ i ].name; + if ( name.indexOf( "data-" ) === 0 ) { + name = camelCase( name.slice( 5 ) ); + dataAttr( elem, name, data[ name ] ); + } + } + } + dataPriv.set( elem, "hasDataAttrs", true ); + } + } + + return data; + } + + // Sets multiple values + if ( typeof key === "object" ) { + return this.each( function() { + dataUser.set( this, key ); + } ); + } + + return access( this, function( value ) { + var data; + + // The calling jQuery object (element matches) is not empty + // (and therefore has an element appears at this[ 0 ]) and the + // `value` parameter was not undefined. An empty jQuery object + // will result in `undefined` for elem = this[ 0 ] which will + // throw an exception if an attempt to read a data cache is made. + if ( elem && value === undefined ) { + + // Attempt to get data from the cache + // The key will always be camelCased in Data + data = dataUser.get( elem, key ); + if ( data !== undefined ) { + return data; + } + + // Attempt to "discover" the data in + // HTML5 custom data-* attrs + data = dataAttr( elem, key ); + if ( data !== undefined ) { + return data; + } + + // We tried really hard, but the data doesn't exist. + return; + } + + // Set the data... + this.each( function() { + + // We always store the camelCased key + dataUser.set( this, key, value ); + } ); + }, null, value, arguments.length > 1, null, true ); + }, + + removeData: function( key ) { + return this.each( function() { + dataUser.remove( this, key ); + } ); + } +} ); + + +jQuery.extend( { + queue: function( elem, type, data ) { + var queue; + + if ( elem ) { + type = ( type || "fx" ) + "queue"; + queue = dataPriv.get( elem, type ); + + // Speed up dequeue by getting out quickly if this is just a lookup + if ( data ) { + if ( !queue || Array.isArray( data ) ) { + queue = dataPriv.access( elem, type, jQuery.makeArray( data ) ); + } else { + queue.push( data ); + } + } + return queue || []; + } + }, + + dequeue: function( elem, type ) { + type = type || "fx"; + + var queue = jQuery.queue( elem, type ), + startLength = queue.length, + fn = queue.shift(), + hooks = jQuery._queueHooks( elem, type ), + next = function() { + jQuery.dequeue( elem, type ); + }; + + // If the fx queue is dequeued, always remove the progress sentinel + if ( fn === "inprogress" ) { + fn = queue.shift(); + startLength--; + } + + if ( fn ) { + + // Add a progress sentinel to prevent the fx queue from being + // automatically dequeued + if ( type === "fx" ) { + queue.unshift( "inprogress" ); + } + + // Clear up the last queue stop function + delete hooks.stop; + fn.call( elem, next, hooks ); + } + + if ( !startLength && hooks ) { + hooks.empty.fire(); + } + }, + + // Not public - generate a queueHooks object, or return the current one + _queueHooks: function( elem, type ) { + var key = type + "queueHooks"; + return dataPriv.get( elem, key ) || dataPriv.access( elem, key, { + empty: jQuery.Callbacks( "once memory" ).add( function() { + dataPriv.remove( elem, [ type + "queue", key ] ); + } ) + } ); + } +} ); + +jQuery.fn.extend( { + queue: function( type, data ) { + var setter = 2; + + if ( typeof type !== "string" ) { + data = type; + type = "fx"; + setter--; + } + + if ( arguments.length < setter ) { + return jQuery.queue( this[ 0 ], type ); + } + + return data === undefined ? + this : + this.each( function() { + var queue = jQuery.queue( this, type, data ); + + // Ensure a hooks for this queue + jQuery._queueHooks( this, type ); + + if ( type === "fx" && queue[ 0 ] !== "inprogress" ) { + jQuery.dequeue( this, type ); + } + } ); + }, + dequeue: function( type ) { + return this.each( function() { + jQuery.dequeue( this, type ); + } ); + }, + clearQueue: function( type ) { + return this.queue( type || "fx", [] ); + }, + + // Get a promise resolved when queues of a certain type + // are emptied (fx is the type by default) + promise: function( type, obj ) { + var tmp, + count = 1, + defer = jQuery.Deferred(), + elements = this, + i = this.length, + resolve = function() { + if ( !( --count ) ) { + defer.resolveWith( elements, [ elements ] ); + } + }; + + if ( typeof type !== "string" ) { + obj = type; + type = undefined; + } + type = type || "fx"; + + while ( i-- ) { + tmp = dataPriv.get( elements[ i ], type + "queueHooks" ); + if ( tmp && tmp.empty ) { + count++; + tmp.empty.add( resolve ); + } + } + resolve(); + return defer.promise( obj ); + } +} ); +var pnum = ( /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/ ).source; + +var rcssNum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" ); + + +var cssExpand = [ "Top", "Right", "Bottom", "Left" ]; + +var documentElement = document.documentElement; + + + + var isAttached = function( elem ) { + return jQuery.contains( elem.ownerDocument, elem ); + }, + composed = { composed: true }; + + // Support: IE 9 - 11+, Edge 12 - 18+, iOS 10.0 - 10.2 only + // Check attachment across shadow DOM boundaries when possible (gh-3504) + // Support: iOS 10.0-10.2 only + // Early iOS 10 versions support `attachShadow` but not `getRootNode`, + // leading to errors. We need to check for `getRootNode`. + if ( documentElement.getRootNode ) { + isAttached = function( elem ) { + return jQuery.contains( elem.ownerDocument, elem ) || + elem.getRootNode( composed ) === elem.ownerDocument; + }; + } +var isHiddenWithinTree = function( elem, el ) { + + // isHiddenWithinTree might be called from jQuery#filter function; + // in that case, element will be second argument + elem = el || elem; + + // Inline style trumps all + return elem.style.display === "none" || + elem.style.display === "" && + + // Otherwise, check computed style + // Support: Firefox <=43 - 45 + // Disconnected elements can have computed display: none, so first confirm that elem is + // in the document. + isAttached( elem ) && + + jQuery.css( elem, "display" ) === "none"; + }; + + + +function adjustCSS( elem, prop, valueParts, tween ) { + var adjusted, scale, + maxIterations = 20, + currentValue = tween ? + function() { + return tween.cur(); + } : + function() { + return jQuery.css( elem, prop, "" ); + }, + initial = currentValue(), + unit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ), + + // Starting value computation is required for potential unit mismatches + initialInUnit = elem.nodeType && + ( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) && + rcssNum.exec( jQuery.css( elem, prop ) ); + + if ( initialInUnit && initialInUnit[ 3 ] !== unit ) { + + // Support: Firefox <=54 + // Halve the iteration target value to prevent interference from CSS upper bounds (gh-2144) + initial = initial / 2; + + // Trust units reported by jQuery.css + unit = unit || initialInUnit[ 3 ]; + + // Iteratively approximate from a nonzero starting point + initialInUnit = +initial || 1; + + while ( maxIterations-- ) { + + // Evaluate and update our best guess (doubling guesses that zero out). + // Finish if the scale equals or crosses 1 (making the old*new product non-positive). + jQuery.style( elem, prop, initialInUnit + unit ); + if ( ( 1 - scale ) * ( 1 - ( scale = currentValue() / initial || 0.5 ) ) <= 0 ) { + maxIterations = 0; + } + initialInUnit = initialInUnit / scale; + + } + + initialInUnit = initialInUnit * 2; + jQuery.style( elem, prop, initialInUnit + unit ); + + // Make sure we update the tween properties later on + valueParts = valueParts || []; + } + + if ( valueParts ) { + initialInUnit = +initialInUnit || +initial || 0; + + // Apply relative offset (+=/-=) if specified + adjusted = valueParts[ 1 ] ? + initialInUnit + ( valueParts[ 1 ] + 1 ) * valueParts[ 2 ] : + +valueParts[ 2 ]; + if ( tween ) { + tween.unit = unit; + tween.start = initialInUnit; + tween.end = adjusted; + } + } + return adjusted; +} + + +var defaultDisplayMap = {}; + +function getDefaultDisplay( elem ) { + var temp, + doc = elem.ownerDocument, + nodeName = elem.nodeName, + display = defaultDisplayMap[ nodeName ]; + + if ( display ) { + return display; + } + + temp = doc.body.appendChild( doc.createElement( nodeName ) ); + display = jQuery.css( temp, "display" ); + + temp.parentNode.removeChild( temp ); + + if ( display === "none" ) { + display = "block"; + } + defaultDisplayMap[ nodeName ] = display; + + return display; +} + +function showHide( elements, show ) { + var display, elem, + values = [], + index = 0, + length = elements.length; + + // Determine new display value for elements that need to change + for ( ; index < length; index++ ) { + elem = elements[ index ]; + if ( !elem.style ) { + continue; + } + + display = elem.style.display; + if ( show ) { + + // Since we force visibility upon cascade-hidden elements, an immediate (and slow) + // check is required in this first loop unless we have a nonempty display value (either + // inline or about-to-be-restored) + if ( display === "none" ) { + values[ index ] = dataPriv.get( elem, "display" ) || null; + if ( !values[ index ] ) { + elem.style.display = ""; + } + } + if ( elem.style.display === "" && isHiddenWithinTree( elem ) ) { + values[ index ] = getDefaultDisplay( elem ); + } + } else { + if ( display !== "none" ) { + values[ index ] = "none"; + + // Remember what we're overwriting + dataPriv.set( elem, "display", display ); + } + } + } + + // Set the display of the elements in a second loop to avoid constant reflow + for ( index = 0; index < length; index++ ) { + if ( values[ index ] != null ) { + elements[ index ].style.display = values[ index ]; + } + } + + return elements; +} + +jQuery.fn.extend( { + show: function() { + return showHide( this, true ); + }, + hide: function() { + return showHide( this ); + }, + toggle: function( state ) { + if ( typeof state === "boolean" ) { + return state ? this.show() : this.hide(); + } + + return this.each( function() { + if ( isHiddenWithinTree( this ) ) { + jQuery( this ).show(); + } else { + jQuery( this ).hide(); + } + } ); + } +} ); +var rcheckableType = ( /^(?:checkbox|radio)$/i ); + +var rtagName = ( /<([a-z][^\/\0>\x20\t\r\n\f]*)/i ); + +var rscriptType = ( /^$|^module$|\/(?:java|ecma)script/i ); + + + +( function() { + var fragment = document.createDocumentFragment(), + div = fragment.appendChild( document.createElement( "div" ) ), + input = document.createElement( "input" ); + + // Support: Android 4.0 - 4.3 only + // Check state lost if the name is set (#11217) + // Support: Windows Web Apps (WWA) + // `name` and `type` must use .setAttribute for WWA (#14901) + input.setAttribute( "type", "radio" ); + input.setAttribute( "checked", "checked" ); + input.setAttribute( "name", "t" ); + + div.appendChild( input ); + + // Support: Android <=4.1 only + // Older WebKit doesn't clone checked state correctly in fragments + support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked; + + // Support: IE <=11 only + // Make sure textarea (and checkbox) defaultValue is properly cloned + div.innerHTML = ""; + support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; + + // Support: IE <=9 only + // IE <=9 replaces "; + support.option = !!div.lastChild; +} )(); + + +// We have to close these tags to support XHTML (#13200) +var wrapMap = { + + // XHTML parsers do not magically insert elements in the + // same way that tag soup parsers do. So we cannot shorten + // this by omitting or other required elements. + thead: [ 1, "", "
" ], + col: [ 2, "", "
" ], + tr: [ 2, "", "
" ], + td: [ 3, "", "
" ], + + _default: [ 0, "", "" ] +}; + +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + +// Support: IE <=9 only +if ( !support.option ) { + wrapMap.optgroup = wrapMap.option = [ 1, "" ]; +} + + +function getAll( context, tag ) { + + // Support: IE <=9 - 11 only + // Use typeof to avoid zero-argument method invocation on host objects (#15151) + var ret; + + if ( typeof context.getElementsByTagName !== "undefined" ) { + ret = context.getElementsByTagName( tag || "*" ); + + } else if ( typeof context.querySelectorAll !== "undefined" ) { + ret = context.querySelectorAll( tag || "*" ); + + } else { + ret = []; + } + + if ( tag === undefined || tag && nodeName( context, tag ) ) { + return jQuery.merge( [ context ], ret ); + } + + return ret; +} + + +// Mark scripts as having already been evaluated +function setGlobalEval( elems, refElements ) { + var i = 0, + l = elems.length; + + for ( ; i < l; i++ ) { + dataPriv.set( + elems[ i ], + "globalEval", + !refElements || dataPriv.get( refElements[ i ], "globalEval" ) + ); + } +} + + +var rhtml = /<|&#?\w+;/; + +function buildFragment( elems, context, scripts, selection, ignored ) { + var elem, tmp, tag, wrap, attached, j, + fragment = context.createDocumentFragment(), + nodes = [], + i = 0, + l = elems.length; + + for ( ; i < l; i++ ) { + elem = elems[ i ]; + + if ( elem || elem === 0 ) { + + // Add nodes directly + if ( toType( elem ) === "object" ) { + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); + + // Convert non-html into a text node + } else if ( !rhtml.test( elem ) ) { + nodes.push( context.createTextNode( elem ) ); + + // Convert html into DOM nodes + } else { + tmp = tmp || fragment.appendChild( context.createElement( "div" ) ); + + // Deserialize a standard representation + tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase(); + wrap = wrapMap[ tag ] || wrapMap._default; + tmp.innerHTML = wrap[ 1 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ]; + + // Descend through wrappers to the right content + j = wrap[ 0 ]; + while ( j-- ) { + tmp = tmp.lastChild; + } + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( nodes, tmp.childNodes ); + + // Remember the top-level container + tmp = fragment.firstChild; + + // Ensure the created nodes are orphaned (#12392) + tmp.textContent = ""; + } + } + } + + // Remove wrapper from fragment + fragment.textContent = ""; + + i = 0; + while ( ( elem = nodes[ i++ ] ) ) { + + // Skip elements already in the context collection (trac-4087) + if ( selection && jQuery.inArray( elem, selection ) > -1 ) { + if ( ignored ) { + ignored.push( elem ); + } + continue; + } + + attached = isAttached( elem ); + + // Append to fragment + tmp = getAll( fragment.appendChild( elem ), "script" ); + + // Preserve script evaluation history + if ( attached ) { + setGlobalEval( tmp ); + } + + // Capture executables + if ( scripts ) { + j = 0; + while ( ( elem = tmp[ j++ ] ) ) { + if ( rscriptType.test( elem.type || "" ) ) { + scripts.push( elem ); + } + } + } + } + + return fragment; +} + + +var + rkeyEvent = /^key/, + rmouseEvent = /^(?:mouse|pointer|contextmenu|drag|drop)|click/, + rtypenamespace = /^([^.]*)(?:\.(.+)|)/; + +function returnTrue() { + return true; +} + +function returnFalse() { + return false; +} + +// Support: IE <=9 - 11+ +// focus() and blur() are asynchronous, except when they are no-op. +// So expect focus to be synchronous when the element is already active, +// and blur to be synchronous when the element is not already active. +// (focus and blur are always synchronous in other supported browsers, +// this just defines when we can count on it). +function expectSync( elem, type ) { + return ( elem === safeActiveElement() ) === ( type === "focus" ); +} + +// Support: IE <=9 only +// Accessing document.activeElement can throw unexpectedly +// https://bugs.jquery.com/ticket/13393 +function safeActiveElement() { + try { + return document.activeElement; + } catch ( err ) { } +} + +function on( elem, types, selector, data, fn, one ) { + var origFn, type; + + // Types can be a map of types/handlers + if ( typeof types === "object" ) { + + // ( types-Object, selector, data ) + if ( typeof selector !== "string" ) { + + // ( types-Object, data ) + data = data || selector; + selector = undefined; + } + for ( type in types ) { + on( elem, type, selector, data, types[ type ], one ); + } + return elem; + } + + if ( data == null && fn == null ) { + + // ( types, fn ) + fn = selector; + data = selector = undefined; + } else if ( fn == null ) { + if ( typeof selector === "string" ) { + + // ( types, selector, fn ) + fn = data; + data = undefined; + } else { + + // ( types, data, fn ) + fn = data; + data = selector; + selector = undefined; + } + } + if ( fn === false ) { + fn = returnFalse; + } else if ( !fn ) { + return elem; + } + + if ( one === 1 ) { + origFn = fn; + fn = function( event ) { + + // Can use an empty set, since event contains the info + jQuery().off( event ); + return origFn.apply( this, arguments ); + }; + + // Use same guid so caller can remove using origFn + fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); + } + return elem.each( function() { + jQuery.event.add( this, types, fn, data, selector ); + } ); +} + +/* + * Helper functions for managing events -- not part of the public interface. + * Props to Dean Edwards' addEvent library for many of the ideas. + */ +jQuery.event = { + + global: {}, + + add: function( elem, types, handler, data, selector ) { + + var handleObjIn, eventHandle, tmp, + events, t, handleObj, + special, handlers, type, namespaces, origType, + elemData = dataPriv.get( elem ); + + // Only attach events to objects that accept data + if ( !acceptData( elem ) ) { + return; + } + + // Caller can pass in an object of custom data in lieu of the handler + if ( handler.handler ) { + handleObjIn = handler; + handler = handleObjIn.handler; + selector = handleObjIn.selector; + } + + // Ensure that invalid selectors throw exceptions at attach time + // Evaluate against documentElement in case elem is a non-element node (e.g., document) + if ( selector ) { + jQuery.find.matchesSelector( documentElement, selector ); + } + + // Make sure that the handler has a unique ID, used to find/remove it later + if ( !handler.guid ) { + handler.guid = jQuery.guid++; + } + + // Init the element's event structure and main handler, if this is the first + if ( !( events = elemData.events ) ) { + events = elemData.events = Object.create( null ); + } + if ( !( eventHandle = elemData.handle ) ) { + eventHandle = elemData.handle = function( e ) { + + // Discard the second event of a jQuery.event.trigger() and + // when an event is called after a page has unloaded + return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ? + jQuery.event.dispatch.apply( elem, arguments ) : undefined; + }; + } + + // Handle multiple events separated by a space + types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[ t ] ) || []; + type = origType = tmp[ 1 ]; + namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); + + // There *must* be a type, no attaching namespace-only handlers + if ( !type ) { + continue; + } + + // If event changes its type, use the special event handlers for the changed type + special = jQuery.event.special[ type ] || {}; + + // If selector defined, determine special event api type, otherwise given type + type = ( selector ? special.delegateType : special.bindType ) || type; + + // Update special based on newly reset type + special = jQuery.event.special[ type ] || {}; + + // handleObj is passed to all event handlers + handleObj = jQuery.extend( { + type: type, + origType: origType, + data: data, + handler: handler, + guid: handler.guid, + selector: selector, + needsContext: selector && jQuery.expr.match.needsContext.test( selector ), + namespace: namespaces.join( "." ) + }, handleObjIn ); + + // Init the event handler queue if we're the first + if ( !( handlers = events[ type ] ) ) { + handlers = events[ type ] = []; + handlers.delegateCount = 0; + + // Only use addEventListener if the special events handler returns false + if ( !special.setup || + special.setup.call( elem, data, namespaces, eventHandle ) === false ) { + + if ( elem.addEventListener ) { + elem.addEventListener( type, eventHandle ); + } + } + } + + if ( special.add ) { + special.add.call( elem, handleObj ); + + if ( !handleObj.handler.guid ) { + handleObj.handler.guid = handler.guid; + } + } + + // Add to the element's handler list, delegates in front + if ( selector ) { + handlers.splice( handlers.delegateCount++, 0, handleObj ); + } else { + handlers.push( handleObj ); + } + + // Keep track of which events have ever been used, for event optimization + jQuery.event.global[ type ] = true; + } + + }, + + // Detach an event or set of events from an element + remove: function( elem, types, handler, selector, mappedTypes ) { + + var j, origCount, tmp, + events, t, handleObj, + special, handlers, type, namespaces, origType, + elemData = dataPriv.hasData( elem ) && dataPriv.get( elem ); + + if ( !elemData || !( events = elemData.events ) ) { + return; + } + + // Once for each type.namespace in types; type may be omitted + types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[ t ] ) || []; + type = origType = tmp[ 1 ]; + namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); + + // Unbind all events (on this namespace, if provided) for the element + if ( !type ) { + for ( type in events ) { + jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); + } + continue; + } + + special = jQuery.event.special[ type ] || {}; + type = ( selector ? special.delegateType : special.bindType ) || type; + handlers = events[ type ] || []; + tmp = tmp[ 2 ] && + new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ); + + // Remove matching events + origCount = j = handlers.length; + while ( j-- ) { + handleObj = handlers[ j ]; + + if ( ( mappedTypes || origType === handleObj.origType ) && + ( !handler || handler.guid === handleObj.guid ) && + ( !tmp || tmp.test( handleObj.namespace ) ) && + ( !selector || selector === handleObj.selector || + selector === "**" && handleObj.selector ) ) { + handlers.splice( j, 1 ); + + if ( handleObj.selector ) { + handlers.delegateCount--; + } + if ( special.remove ) { + special.remove.call( elem, handleObj ); + } + } + } + + // Remove generic event handler if we removed something and no more handlers exist + // (avoids potential for endless recursion during removal of special event handlers) + if ( origCount && !handlers.length ) { + if ( !special.teardown || + special.teardown.call( elem, namespaces, elemData.handle ) === false ) { + + jQuery.removeEvent( elem, type, elemData.handle ); + } + + delete events[ type ]; + } + } + + // Remove data and the expando if it's no longer used + if ( jQuery.isEmptyObject( events ) ) { + dataPriv.remove( elem, "handle events" ); + } + }, + + dispatch: function( nativeEvent ) { + + var i, j, ret, matched, handleObj, handlerQueue, + args = new Array( arguments.length ), + + // Make a writable jQuery.Event from the native event object + event = jQuery.event.fix( nativeEvent ), + + handlers = ( + dataPriv.get( this, "events" ) || Object.create( null ) + )[ event.type ] || [], + special = jQuery.event.special[ event.type ] || {}; + + // Use the fix-ed jQuery.Event rather than the (read-only) native event + args[ 0 ] = event; + + for ( i = 1; i < arguments.length; i++ ) { + args[ i ] = arguments[ i ]; + } + + event.delegateTarget = this; + + // Call the preDispatch hook for the mapped type, and let it bail if desired + if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { + return; + } + + // Determine handlers + handlerQueue = jQuery.event.handlers.call( this, event, handlers ); + + // Run delegates first; they may want to stop propagation beneath us + i = 0; + while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) { + event.currentTarget = matched.elem; + + j = 0; + while ( ( handleObj = matched.handlers[ j++ ] ) && + !event.isImmediatePropagationStopped() ) { + + // If the event is namespaced, then each handler is only invoked if it is + // specially universal or its namespaces are a superset of the event's. + if ( !event.rnamespace || handleObj.namespace === false || + event.rnamespace.test( handleObj.namespace ) ) { + + event.handleObj = handleObj; + event.data = handleObj.data; + + ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle || + handleObj.handler ).apply( matched.elem, args ); + + if ( ret !== undefined ) { + if ( ( event.result = ret ) === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } + } + } + } + + // Call the postDispatch hook for the mapped type + if ( special.postDispatch ) { + special.postDispatch.call( this, event ); + } + + return event.result; + }, + + handlers: function( event, handlers ) { + var i, handleObj, sel, matchedHandlers, matchedSelectors, + handlerQueue = [], + delegateCount = handlers.delegateCount, + cur = event.target; + + // Find delegate handlers + if ( delegateCount && + + // Support: IE <=9 + // Black-hole SVG instance trees (trac-13180) + cur.nodeType && + + // Support: Firefox <=42 + // Suppress spec-violating clicks indicating a non-primary pointer button (trac-3861) + // https://www.w3.org/TR/DOM-Level-3-Events/#event-type-click + // Support: IE 11 only + // ...but not arrow key "clicks" of radio inputs, which can have `button` -1 (gh-2343) + !( event.type === "click" && event.button >= 1 ) ) { + + for ( ; cur !== this; cur = cur.parentNode || this ) { + + // Don't check non-elements (#13208) + // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) + if ( cur.nodeType === 1 && !( event.type === "click" && cur.disabled === true ) ) { + matchedHandlers = []; + matchedSelectors = {}; + for ( i = 0; i < delegateCount; i++ ) { + handleObj = handlers[ i ]; + + // Don't conflict with Object.prototype properties (#13203) + sel = handleObj.selector + " "; + + if ( matchedSelectors[ sel ] === undefined ) { + matchedSelectors[ sel ] = handleObj.needsContext ? + jQuery( sel, this ).index( cur ) > -1 : + jQuery.find( sel, this, null, [ cur ] ).length; + } + if ( matchedSelectors[ sel ] ) { + matchedHandlers.push( handleObj ); + } + } + if ( matchedHandlers.length ) { + handlerQueue.push( { elem: cur, handlers: matchedHandlers } ); + } + } + } + } + + // Add the remaining (directly-bound) handlers + cur = this; + if ( delegateCount < handlers.length ) { + handlerQueue.push( { elem: cur, handlers: handlers.slice( delegateCount ) } ); + } + + return handlerQueue; + }, + + addProp: function( name, hook ) { + Object.defineProperty( jQuery.Event.prototype, name, { + enumerable: true, + configurable: true, + + get: isFunction( hook ) ? + function() { + if ( this.originalEvent ) { + return hook( this.originalEvent ); + } + } : + function() { + if ( this.originalEvent ) { + return this.originalEvent[ name ]; + } + }, + + set: function( value ) { + Object.defineProperty( this, name, { + enumerable: true, + configurable: true, + writable: true, + value: value + } ); + } + } ); + }, + + fix: function( originalEvent ) { + return originalEvent[ jQuery.expando ] ? + originalEvent : + new jQuery.Event( originalEvent ); + }, + + special: { + load: { + + // Prevent triggered image.load events from bubbling to window.load + noBubble: true + }, + click: { + + // Utilize native event to ensure correct state for checkable inputs + setup: function( data ) { + + // For mutual compressibility with _default, replace `this` access with a local var. + // `|| data` is dead code meant only to preserve the variable through minification. + var el = this || data; + + // Claim the first handler + if ( rcheckableType.test( el.type ) && + el.click && nodeName( el, "input" ) ) { + + // dataPriv.set( el, "click", ... ) + leverageNative( el, "click", returnTrue ); + } + + // Return false to allow normal processing in the caller + return false; + }, + trigger: function( data ) { + + // For mutual compressibility with _default, replace `this` access with a local var. + // `|| data` is dead code meant only to preserve the variable through minification. + var el = this || data; + + // Force setup before triggering a click + if ( rcheckableType.test( el.type ) && + el.click && nodeName( el, "input" ) ) { + + leverageNative( el, "click" ); + } + + // Return non-false to allow normal event-path propagation + return true; + }, + + // For cross-browser consistency, suppress native .click() on links + // Also prevent it if we're currently inside a leveraged native-event stack + _default: function( event ) { + var target = event.target; + return rcheckableType.test( target.type ) && + target.click && nodeName( target, "input" ) && + dataPriv.get( target, "click" ) || + nodeName( target, "a" ); + } + }, + + beforeunload: { + postDispatch: function( event ) { + + // Support: Firefox 20+ + // Firefox doesn't alert if the returnValue field is not set. + if ( event.result !== undefined && event.originalEvent ) { + event.originalEvent.returnValue = event.result; + } + } + } + } +}; + +// Ensure the presence of an event listener that handles manually-triggered +// synthetic events by interrupting progress until reinvoked in response to +// *native* events that it fires directly, ensuring that state changes have +// already occurred before other listeners are invoked. +function leverageNative( el, type, expectSync ) { + + // Missing expectSync indicates a trigger call, which must force setup through jQuery.event.add + if ( !expectSync ) { + if ( dataPriv.get( el, type ) === undefined ) { + jQuery.event.add( el, type, returnTrue ); + } + return; + } + + // Register the controller as a special universal handler for all event namespaces + dataPriv.set( el, type, false ); + jQuery.event.add( el, type, { + namespace: false, + handler: function( event ) { + var notAsync, result, + saved = dataPriv.get( this, type ); + + if ( ( event.isTrigger & 1 ) && this[ type ] ) { + + // Interrupt processing of the outer synthetic .trigger()ed event + // Saved data should be false in such cases, but might be a leftover capture object + // from an async native handler (gh-4350) + if ( !saved.length ) { + + // Store arguments for use when handling the inner native event + // There will always be at least one argument (an event object), so this array + // will not be confused with a leftover capture object. + saved = slice.call( arguments ); + dataPriv.set( this, type, saved ); + + // Trigger the native event and capture its result + // Support: IE <=9 - 11+ + // focus() and blur() are asynchronous + notAsync = expectSync( this, type ); + this[ type ](); + result = dataPriv.get( this, type ); + if ( saved !== result || notAsync ) { + dataPriv.set( this, type, false ); + } else { + result = {}; + } + if ( saved !== result ) { + + // Cancel the outer synthetic event + event.stopImmediatePropagation(); + event.preventDefault(); + return result.value; + } + + // If this is an inner synthetic event for an event with a bubbling surrogate + // (focus or blur), assume that the surrogate already propagated from triggering the + // native event and prevent that from happening again here. + // This technically gets the ordering wrong w.r.t. to `.trigger()` (in which the + // bubbling surrogate propagates *after* the non-bubbling base), but that seems + // less bad than duplication. + } else if ( ( jQuery.event.special[ type ] || {} ).delegateType ) { + event.stopPropagation(); + } + + // If this is a native event triggered above, everything is now in order + // Fire an inner synthetic event with the original arguments + } else if ( saved.length ) { + + // ...and capture the result + dataPriv.set( this, type, { + value: jQuery.event.trigger( + + // Support: IE <=9 - 11+ + // Extend with the prototype to reset the above stopImmediatePropagation() + jQuery.extend( saved[ 0 ], jQuery.Event.prototype ), + saved.slice( 1 ), + this + ) + } ); + + // Abort handling of the native event + event.stopImmediatePropagation(); + } + } + } ); +} + +jQuery.removeEvent = function( elem, type, handle ) { + + // This "if" is needed for plain objects + if ( elem.removeEventListener ) { + elem.removeEventListener( type, handle ); + } +}; + +jQuery.Event = function( src, props ) { + + // Allow instantiation without the 'new' keyword + if ( !( this instanceof jQuery.Event ) ) { + return new jQuery.Event( src, props ); + } + + // Event object + if ( src && src.type ) { + this.originalEvent = src; + this.type = src.type; + + // Events bubbling up the document may have been marked as prevented + // by a handler lower down the tree; reflect the correct value. + this.isDefaultPrevented = src.defaultPrevented || + src.defaultPrevented === undefined && + + // Support: Android <=2.3 only + src.returnValue === false ? + returnTrue : + returnFalse; + + // Create target properties + // Support: Safari <=6 - 7 only + // Target should not be a text node (#504, #13143) + this.target = ( src.target && src.target.nodeType === 3 ) ? + src.target.parentNode : + src.target; + + this.currentTarget = src.currentTarget; + this.relatedTarget = src.relatedTarget; + + // Event type + } else { + this.type = src; + } + + // Put explicitly provided properties onto the event object + if ( props ) { + jQuery.extend( this, props ); + } + + // Create a timestamp if incoming event doesn't have one + this.timeStamp = src && src.timeStamp || Date.now(); + + // Mark it as fixed + this[ jQuery.expando ] = true; +}; + +// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding +// https://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html +jQuery.Event.prototype = { + constructor: jQuery.Event, + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse, + isSimulated: false, + + preventDefault: function() { + var e = this.originalEvent; + + this.isDefaultPrevented = returnTrue; + + if ( e && !this.isSimulated ) { + e.preventDefault(); + } + }, + stopPropagation: function() { + var e = this.originalEvent; + + this.isPropagationStopped = returnTrue; + + if ( e && !this.isSimulated ) { + e.stopPropagation(); + } + }, + stopImmediatePropagation: function() { + var e = this.originalEvent; + + this.isImmediatePropagationStopped = returnTrue; + + if ( e && !this.isSimulated ) { + e.stopImmediatePropagation(); + } + + this.stopPropagation(); + } +}; + +// Includes all common event props including KeyEvent and MouseEvent specific props +jQuery.each( { + altKey: true, + bubbles: true, + cancelable: true, + changedTouches: true, + ctrlKey: true, + detail: true, + eventPhase: true, + metaKey: true, + pageX: true, + pageY: true, + shiftKey: true, + view: true, + "char": true, + code: true, + charCode: true, + key: true, + keyCode: true, + button: true, + buttons: true, + clientX: true, + clientY: true, + offsetX: true, + offsetY: true, + pointerId: true, + pointerType: true, + screenX: true, + screenY: true, + targetTouches: true, + toElement: true, + touches: true, + + which: function( event ) { + var button = event.button; + + // Add which for key events + if ( event.which == null && rkeyEvent.test( event.type ) ) { + return event.charCode != null ? event.charCode : event.keyCode; + } + + // Add which for click: 1 === left; 2 === middle; 3 === right + if ( !event.which && button !== undefined && rmouseEvent.test( event.type ) ) { + if ( button & 1 ) { + return 1; + } + + if ( button & 2 ) { + return 3; + } + + if ( button & 4 ) { + return 2; + } + + return 0; + } + + return event.which; + } +}, jQuery.event.addProp ); + +jQuery.each( { focus: "focusin", blur: "focusout" }, function( type, delegateType ) { + jQuery.event.special[ type ] = { + + // Utilize native event if possible so blur/focus sequence is correct + setup: function() { + + // Claim the first handler + // dataPriv.set( this, "focus", ... ) + // dataPriv.set( this, "blur", ... ) + leverageNative( this, type, expectSync ); + + // Return false to allow normal processing in the caller + return false; + }, + trigger: function() { + + // Force setup before trigger + leverageNative( this, type ); + + // Return non-false to allow normal event-path propagation + return true; + }, + + delegateType: delegateType + }; +} ); + +// Create mouseenter/leave events using mouseover/out and event-time checks +// so that event delegation works in jQuery. +// Do the same for pointerenter/pointerleave and pointerover/pointerout +// +// Support: Safari 7 only +// Safari sends mouseenter too often; see: +// https://bugs.chromium.org/p/chromium/issues/detail?id=470258 +// for the description of the bug (it existed in older Chrome versions as well). +jQuery.each( { + mouseenter: "mouseover", + mouseleave: "mouseout", + pointerenter: "pointerover", + pointerleave: "pointerout" +}, function( orig, fix ) { + jQuery.event.special[ orig ] = { + delegateType: fix, + bindType: fix, + + handle: function( event ) { + var ret, + target = this, + related = event.relatedTarget, + handleObj = event.handleObj; + + // For mouseenter/leave call the handler if related is outside the target. + // NB: No relatedTarget if the mouse left/entered the browser window + if ( !related || ( related !== target && !jQuery.contains( target, related ) ) ) { + event.type = handleObj.origType; + ret = handleObj.handler.apply( this, arguments ); + event.type = fix; + } + return ret; + } + }; +} ); + +jQuery.fn.extend( { + + on: function( types, selector, data, fn ) { + return on( this, types, selector, data, fn ); + }, + one: function( types, selector, data, fn ) { + return on( this, types, selector, data, fn, 1 ); + }, + off: function( types, selector, fn ) { + var handleObj, type; + if ( types && types.preventDefault && types.handleObj ) { + + // ( event ) dispatched jQuery.Event + handleObj = types.handleObj; + jQuery( types.delegateTarget ).off( + handleObj.namespace ? + handleObj.origType + "." + handleObj.namespace : + handleObj.origType, + handleObj.selector, + handleObj.handler + ); + return this; + } + if ( typeof types === "object" ) { + + // ( types-object [, selector] ) + for ( type in types ) { + this.off( type, selector, types[ type ] ); + } + return this; + } + if ( selector === false || typeof selector === "function" ) { + + // ( types [, fn] ) + fn = selector; + selector = undefined; + } + if ( fn === false ) { + fn = returnFalse; + } + return this.each( function() { + jQuery.event.remove( this, types, fn, selector ); + } ); + } +} ); + + +var + + // Support: IE <=10 - 11, Edge 12 - 13 only + // In IE/Edge using regex groups here causes severe slowdowns. + // See https://connect.microsoft.com/IE/feedback/details/1736512/ + rnoInnerhtml = /\s*$/g; + +// Prefer a tbody over its parent table for containing new rows +function manipulationTarget( elem, content ) { + if ( nodeName( elem, "table" ) && + nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ) { + + return jQuery( elem ).children( "tbody" )[ 0 ] || elem; + } + + return elem; +} + +// Replace/restore the type attribute of script elements for safe DOM manipulation +function disableScript( elem ) { + elem.type = ( elem.getAttribute( "type" ) !== null ) + "/" + elem.type; + return elem; +} +function restoreScript( elem ) { + if ( ( elem.type || "" ).slice( 0, 5 ) === "true/" ) { + elem.type = elem.type.slice( 5 ); + } else { + elem.removeAttribute( "type" ); + } + + return elem; +} + +function cloneCopyEvent( src, dest ) { + var i, l, type, pdataOld, udataOld, udataCur, events; + + if ( dest.nodeType !== 1 ) { + return; + } + + // 1. Copy private data: events, handlers, etc. + if ( dataPriv.hasData( src ) ) { + pdataOld = dataPriv.get( src ); + events = pdataOld.events; + + if ( events ) { + dataPriv.remove( dest, "handle events" ); + + for ( type in events ) { + for ( i = 0, l = events[ type ].length; i < l; i++ ) { + jQuery.event.add( dest, type, events[ type ][ i ] ); + } + } + } + } + + // 2. Copy user data + if ( dataUser.hasData( src ) ) { + udataOld = dataUser.access( src ); + udataCur = jQuery.extend( {}, udataOld ); + + dataUser.set( dest, udataCur ); + } +} + +// Fix IE bugs, see support tests +function fixInput( src, dest ) { + var nodeName = dest.nodeName.toLowerCase(); + + // Fails to persist the checked state of a cloned checkbox or radio button. + if ( nodeName === "input" && rcheckableType.test( src.type ) ) { + dest.checked = src.checked; + + // Fails to return the selected option to the default selected state when cloning options + } else if ( nodeName === "input" || nodeName === "textarea" ) { + dest.defaultValue = src.defaultValue; + } +} + +function domManip( collection, args, callback, ignored ) { + + // Flatten any nested arrays + args = flat( args ); + + var fragment, first, scripts, hasScripts, node, doc, + i = 0, + l = collection.length, + iNoClone = l - 1, + value = args[ 0 ], + valueIsFunction = isFunction( value ); + + // We can't cloneNode fragments that contain checked, in WebKit + if ( valueIsFunction || + ( l > 1 && typeof value === "string" && + !support.checkClone && rchecked.test( value ) ) ) { + return collection.each( function( index ) { + var self = collection.eq( index ); + if ( valueIsFunction ) { + args[ 0 ] = value.call( this, index, self.html() ); + } + domManip( self, args, callback, ignored ); + } ); + } + + if ( l ) { + fragment = buildFragment( args, collection[ 0 ].ownerDocument, false, collection, ignored ); + first = fragment.firstChild; + + if ( fragment.childNodes.length === 1 ) { + fragment = first; + } + + // Require either new content or an interest in ignored elements to invoke the callback + if ( first || ignored ) { + scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); + hasScripts = scripts.length; + + // Use the original fragment for the last item + // instead of the first because it can end up + // being emptied incorrectly in certain situations (#8070). + for ( ; i < l; i++ ) { + node = fragment; + + if ( i !== iNoClone ) { + node = jQuery.clone( node, true, true ); + + // Keep references to cloned scripts for later restoration + if ( hasScripts ) { + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( scripts, getAll( node, "script" ) ); + } + } + + callback.call( collection[ i ], node, i ); + } + + if ( hasScripts ) { + doc = scripts[ scripts.length - 1 ].ownerDocument; + + // Reenable scripts + jQuery.map( scripts, restoreScript ); + + // Evaluate executable scripts on first document insertion + for ( i = 0; i < hasScripts; i++ ) { + node = scripts[ i ]; + if ( rscriptType.test( node.type || "" ) && + !dataPriv.access( node, "globalEval" ) && + jQuery.contains( doc, node ) ) { + + if ( node.src && ( node.type || "" ).toLowerCase() !== "module" ) { + + // Optional AJAX dependency, but won't run scripts if not present + if ( jQuery._evalUrl && !node.noModule ) { + jQuery._evalUrl( node.src, { + nonce: node.nonce || node.getAttribute( "nonce" ) + }, doc ); + } + } else { + DOMEval( node.textContent.replace( rcleanScript, "" ), node, doc ); + } + } + } + } + } + } + + return collection; +} + +function remove( elem, selector, keepData ) { + var node, + nodes = selector ? jQuery.filter( selector, elem ) : elem, + i = 0; + + for ( ; ( node = nodes[ i ] ) != null; i++ ) { + if ( !keepData && node.nodeType === 1 ) { + jQuery.cleanData( getAll( node ) ); + } + + if ( node.parentNode ) { + if ( keepData && isAttached( node ) ) { + setGlobalEval( getAll( node, "script" ) ); + } + node.parentNode.removeChild( node ); + } + } + + return elem; +} + +jQuery.extend( { + htmlPrefilter: function( html ) { + return html; + }, + + clone: function( elem, dataAndEvents, deepDataAndEvents ) { + var i, l, srcElements, destElements, + clone = elem.cloneNode( true ), + inPage = isAttached( elem ); + + // Fix IE cloning issues + if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) && + !jQuery.isXMLDoc( elem ) ) { + + // We eschew Sizzle here for performance reasons: https://jsperf.com/getall-vs-sizzle/2 + destElements = getAll( clone ); + srcElements = getAll( elem ); + + for ( i = 0, l = srcElements.length; i < l; i++ ) { + fixInput( srcElements[ i ], destElements[ i ] ); + } + } + + // Copy the events from the original to the clone + if ( dataAndEvents ) { + if ( deepDataAndEvents ) { + srcElements = srcElements || getAll( elem ); + destElements = destElements || getAll( clone ); + + for ( i = 0, l = srcElements.length; i < l; i++ ) { + cloneCopyEvent( srcElements[ i ], destElements[ i ] ); + } + } else { + cloneCopyEvent( elem, clone ); + } + } + + // Preserve script evaluation history + destElements = getAll( clone, "script" ); + if ( destElements.length > 0 ) { + setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); + } + + // Return the cloned set + return clone; + }, + + cleanData: function( elems ) { + var data, elem, type, + special = jQuery.event.special, + i = 0; + + for ( ; ( elem = elems[ i ] ) !== undefined; i++ ) { + if ( acceptData( elem ) ) { + if ( ( data = elem[ dataPriv.expando ] ) ) { + if ( data.events ) { + for ( type in data.events ) { + if ( special[ type ] ) { + jQuery.event.remove( elem, type ); + + // This is a shortcut to avoid jQuery.event.remove's overhead + } else { + jQuery.removeEvent( elem, type, data.handle ); + } + } + } + + // Support: Chrome <=35 - 45+ + // Assign undefined instead of using delete, see Data#remove + elem[ dataPriv.expando ] = undefined; + } + if ( elem[ dataUser.expando ] ) { + + // Support: Chrome <=35 - 45+ + // Assign undefined instead of using delete, see Data#remove + elem[ dataUser.expando ] = undefined; + } + } + } + } +} ); + +jQuery.fn.extend( { + detach: function( selector ) { + return remove( this, selector, true ); + }, + + remove: function( selector ) { + return remove( this, selector ); + }, + + text: function( value ) { + return access( this, function( value ) { + return value === undefined ? + jQuery.text( this ) : + this.empty().each( function() { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + this.textContent = value; + } + } ); + }, null, value, arguments.length ); + }, + + append: function() { + return domManip( this, arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.appendChild( elem ); + } + } ); + }, + + prepend: function() { + return domManip( this, arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.insertBefore( elem, target.firstChild ); + } + } ); + }, + + before: function() { + return domManip( this, arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this ); + } + } ); + }, + + after: function() { + return domManip( this, arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this.nextSibling ); + } + } ); + }, + + empty: function() { + var elem, + i = 0; + + for ( ; ( elem = this[ i ] ) != null; i++ ) { + if ( elem.nodeType === 1 ) { + + // Prevent memory leaks + jQuery.cleanData( getAll( elem, false ) ); + + // Remove any remaining nodes + elem.textContent = ""; + } + } + + return this; + }, + + clone: function( dataAndEvents, deepDataAndEvents ) { + dataAndEvents = dataAndEvents == null ? false : dataAndEvents; + deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; + + return this.map( function() { + return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); + } ); + }, + + html: function( value ) { + return access( this, function( value ) { + var elem = this[ 0 ] || {}, + i = 0, + l = this.length; + + if ( value === undefined && elem.nodeType === 1 ) { + return elem.innerHTML; + } + + // See if we can take a shortcut and just use innerHTML + if ( typeof value === "string" && !rnoInnerhtml.test( value ) && + !wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) { + + value = jQuery.htmlPrefilter( value ); + + try { + for ( ; i < l; i++ ) { + elem = this[ i ] || {}; + + // Remove element nodes and prevent memory leaks + if ( elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem, false ) ); + elem.innerHTML = value; + } + } + + elem = 0; + + // If using innerHTML throws an exception, use the fallback method + } catch ( e ) {} + } + + if ( elem ) { + this.empty().append( value ); + } + }, null, value, arguments.length ); + }, + + replaceWith: function() { + var ignored = []; + + // Make the changes, replacing each non-ignored context element with the new content + return domManip( this, arguments, function( elem ) { + var parent = this.parentNode; + + if ( jQuery.inArray( this, ignored ) < 0 ) { + jQuery.cleanData( getAll( this ) ); + if ( parent ) { + parent.replaceChild( elem, this ); + } + } + + // Force callback invocation + }, ignored ); + } +} ); + +jQuery.each( { + appendTo: "append", + prependTo: "prepend", + insertBefore: "before", + insertAfter: "after", + replaceAll: "replaceWith" +}, function( name, original ) { + jQuery.fn[ name ] = function( selector ) { + var elems, + ret = [], + insert = jQuery( selector ), + last = insert.length - 1, + i = 0; + + for ( ; i <= last; i++ ) { + elems = i === last ? this : this.clone( true ); + jQuery( insert[ i ] )[ original ]( elems ); + + // Support: Android <=4.0 only, PhantomJS 1 only + // .get() because push.apply(_, arraylike) throws on ancient WebKit + push.apply( ret, elems.get() ); + } + + return this.pushStack( ret ); + }; +} ); +var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" ); + +var getStyles = function( elem ) { + + // Support: IE <=11 only, Firefox <=30 (#15098, #14150) + // IE throws on elements created in popups + // FF meanwhile throws on frame elements through "defaultView.getComputedStyle" + var view = elem.ownerDocument.defaultView; + + if ( !view || !view.opener ) { + view = window; + } + + return view.getComputedStyle( elem ); + }; + +var swap = function( elem, options, callback ) { + var ret, name, + old = {}; + + // Remember the old values, and insert the new ones + for ( name in options ) { + old[ name ] = elem.style[ name ]; + elem.style[ name ] = options[ name ]; + } + + ret = callback.call( elem ); + + // Revert the old values + for ( name in options ) { + elem.style[ name ] = old[ name ]; + } + + return ret; +}; + + +var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" ); + + + +( function() { + + // Executing both pixelPosition & boxSizingReliable tests require only one layout + // so they're executed at the same time to save the second computation. + function computeStyleTests() { + + // This is a singleton, we need to execute it only once + if ( !div ) { + return; + } + + container.style.cssText = "position:absolute;left:-11111px;width:60px;" + + "margin-top:1px;padding:0;border:0"; + div.style.cssText = + "position:relative;display:block;box-sizing:border-box;overflow:scroll;" + + "margin:auto;border:1px;padding:1px;" + + "width:60%;top:1%"; + documentElement.appendChild( container ).appendChild( div ); + + var divStyle = window.getComputedStyle( div ); + pixelPositionVal = divStyle.top !== "1%"; + + // Support: Android 4.0 - 4.3 only, Firefox <=3 - 44 + reliableMarginLeftVal = roundPixelMeasures( divStyle.marginLeft ) === 12; + + // Support: Android 4.0 - 4.3 only, Safari <=9.1 - 10.1, iOS <=7.0 - 9.3 + // Some styles come back with percentage values, even though they shouldn't + div.style.right = "60%"; + pixelBoxStylesVal = roundPixelMeasures( divStyle.right ) === 36; + + // Support: IE 9 - 11 only + // Detect misreporting of content dimensions for box-sizing:border-box elements + boxSizingReliableVal = roundPixelMeasures( divStyle.width ) === 36; + + // Support: IE 9 only + // Detect overflow:scroll screwiness (gh-3699) + // Support: Chrome <=64 + // Don't get tricked when zoom affects offsetWidth (gh-4029) + div.style.position = "absolute"; + scrollboxSizeVal = roundPixelMeasures( div.offsetWidth / 3 ) === 12; + + documentElement.removeChild( container ); + + // Nullify the div so it wouldn't be stored in the memory and + // it will also be a sign that checks already performed + div = null; + } + + function roundPixelMeasures( measure ) { + return Math.round( parseFloat( measure ) ); + } + + var pixelPositionVal, boxSizingReliableVal, scrollboxSizeVal, pixelBoxStylesVal, + reliableTrDimensionsVal, reliableMarginLeftVal, + container = document.createElement( "div" ), + div = document.createElement( "div" ); + + // Finish early in limited (non-browser) environments + if ( !div.style ) { + return; + } + + // Support: IE <=9 - 11 only + // Style of cloned element affects source element cloned (#8908) + div.style.backgroundClip = "content-box"; + div.cloneNode( true ).style.backgroundClip = ""; + support.clearCloneStyle = div.style.backgroundClip === "content-box"; + + jQuery.extend( support, { + boxSizingReliable: function() { + computeStyleTests(); + return boxSizingReliableVal; + }, + pixelBoxStyles: function() { + computeStyleTests(); + return pixelBoxStylesVal; + }, + pixelPosition: function() { + computeStyleTests(); + return pixelPositionVal; + }, + reliableMarginLeft: function() { + computeStyleTests(); + return reliableMarginLeftVal; + }, + scrollboxSize: function() { + computeStyleTests(); + return scrollboxSizeVal; + }, + + // Support: IE 9 - 11+, Edge 15 - 18+ + // IE/Edge misreport `getComputedStyle` of table rows with width/height + // set in CSS while `offset*` properties report correct values. + // Behavior in IE 9 is more subtle than in newer versions & it passes + // some versions of this test; make sure not to make it pass there! + reliableTrDimensions: function() { + var table, tr, trChild, trStyle; + if ( reliableTrDimensionsVal == null ) { + table = document.createElement( "table" ); + tr = document.createElement( "tr" ); + trChild = document.createElement( "div" ); + + table.style.cssText = "position:absolute;left:-11111px"; + tr.style.height = "1px"; + trChild.style.height = "9px"; + + documentElement + .appendChild( table ) + .appendChild( tr ) + .appendChild( trChild ); + + trStyle = window.getComputedStyle( tr ); + reliableTrDimensionsVal = parseInt( trStyle.height ) > 3; + + documentElement.removeChild( table ); + } + return reliableTrDimensionsVal; + } + } ); +} )(); + + +function curCSS( elem, name, computed ) { + var width, minWidth, maxWidth, ret, + + // Support: Firefox 51+ + // Retrieving style before computed somehow + // fixes an issue with getting wrong values + // on detached elements + style = elem.style; + + computed = computed || getStyles( elem ); + + // getPropertyValue is needed for: + // .css('filter') (IE 9 only, #12537) + // .css('--customProperty) (#3144) + if ( computed ) { + ret = computed.getPropertyValue( name ) || computed[ name ]; + + if ( ret === "" && !isAttached( elem ) ) { + ret = jQuery.style( elem, name ); + } + + // A tribute to the "awesome hack by Dean Edwards" + // Android Browser returns percentage for some values, + // but width seems to be reliably pixels. + // This is against the CSSOM draft spec: + // https://drafts.csswg.org/cssom/#resolved-values + if ( !support.pixelBoxStyles() && rnumnonpx.test( ret ) && rboxStyle.test( name ) ) { + + // Remember the original values + width = style.width; + minWidth = style.minWidth; + maxWidth = style.maxWidth; + + // Put in the new values to get a computed value out + style.minWidth = style.maxWidth = style.width = ret; + ret = computed.width; + + // Revert the changed values + style.width = width; + style.minWidth = minWidth; + style.maxWidth = maxWidth; + } + } + + return ret !== undefined ? + + // Support: IE <=9 - 11 only + // IE returns zIndex value as an integer. + ret + "" : + ret; +} + + +function addGetHookIf( conditionFn, hookFn ) { + + // Define the hook, we'll check on the first run if it's really needed. + return { + get: function() { + if ( conditionFn() ) { + + // Hook not needed (or it's not possible to use it due + // to missing dependency), remove it. + delete this.get; + return; + } + + // Hook needed; redefine it so that the support test is not executed again. + return ( this.get = hookFn ).apply( this, arguments ); + } + }; +} + + +var cssPrefixes = [ "Webkit", "Moz", "ms" ], + emptyStyle = document.createElement( "div" ).style, + vendorProps = {}; + +// Return a vendor-prefixed property or undefined +function vendorPropName( name ) { + + // Check for vendor prefixed names + var capName = name[ 0 ].toUpperCase() + name.slice( 1 ), + i = cssPrefixes.length; + + while ( i-- ) { + name = cssPrefixes[ i ] + capName; + if ( name in emptyStyle ) { + return name; + } + } +} + +// Return a potentially-mapped jQuery.cssProps or vendor prefixed property +function finalPropName( name ) { + var final = jQuery.cssProps[ name ] || vendorProps[ name ]; + + if ( final ) { + return final; + } + if ( name in emptyStyle ) { + return name; + } + return vendorProps[ name ] = vendorPropName( name ) || name; +} + + +var + + // Swappable if display is none or starts with table + // except "table", "table-cell", or "table-caption" + // See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display + rdisplayswap = /^(none|table(?!-c[ea]).+)/, + rcustomProp = /^--/, + cssShow = { position: "absolute", visibility: "hidden", display: "block" }, + cssNormalTransform = { + letterSpacing: "0", + fontWeight: "400" + }; + +function setPositiveNumber( _elem, value, subtract ) { + + // Any relative (+/-) values have already been + // normalized at this point + var matches = rcssNum.exec( value ); + return matches ? + + // Guard against undefined "subtract", e.g., when used as in cssHooks + Math.max( 0, matches[ 2 ] - ( subtract || 0 ) ) + ( matches[ 3 ] || "px" ) : + value; +} + +function boxModelAdjustment( elem, dimension, box, isBorderBox, styles, computedVal ) { + var i = dimension === "width" ? 1 : 0, + extra = 0, + delta = 0; + + // Adjustment may not be necessary + if ( box === ( isBorderBox ? "border" : "content" ) ) { + return 0; + } + + for ( ; i < 4; i += 2 ) { + + // Both box models exclude margin + if ( box === "margin" ) { + delta += jQuery.css( elem, box + cssExpand[ i ], true, styles ); + } + + // If we get here with a content-box, we're seeking "padding" or "border" or "margin" + if ( !isBorderBox ) { + + // Add padding + delta += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + + // For "border" or "margin", add border + if ( box !== "padding" ) { + delta += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + + // But still keep track of it otherwise + } else { + extra += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + + // If we get here with a border-box (content + padding + border), we're seeking "content" or + // "padding" or "margin" + } else { + + // For "content", subtract padding + if ( box === "content" ) { + delta -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + } + + // For "content" or "padding", subtract border + if ( box !== "margin" ) { + delta -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + } + } + + // Account for positive content-box scroll gutter when requested by providing computedVal + if ( !isBorderBox && computedVal >= 0 ) { + + // offsetWidth/offsetHeight is a rounded sum of content, padding, scroll gutter, and border + // Assuming integer scroll gutter, subtract the rest and round down + delta += Math.max( 0, Math.ceil( + elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - + computedVal - + delta - + extra - + 0.5 + + // If offsetWidth/offsetHeight is unknown, then we can't determine content-box scroll gutter + // Use an explicit zero to avoid NaN (gh-3964) + ) ) || 0; + } + + return delta; +} + +function getWidthOrHeight( elem, dimension, extra ) { + + // Start with computed style + var styles = getStyles( elem ), + + // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-4322). + // Fake content-box until we know it's needed to know the true value. + boxSizingNeeded = !support.boxSizingReliable() || extra, + isBorderBox = boxSizingNeeded && + jQuery.css( elem, "boxSizing", false, styles ) === "border-box", + valueIsBorderBox = isBorderBox, + + val = curCSS( elem, dimension, styles ), + offsetProp = "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ); + + // Support: Firefox <=54 + // Return a confounding non-pixel value or feign ignorance, as appropriate. + if ( rnumnonpx.test( val ) ) { + if ( !extra ) { + return val; + } + val = "auto"; + } + + + // Support: IE 9 - 11 only + // Use offsetWidth/offsetHeight for when box sizing is unreliable. + // In those cases, the computed value can be trusted to be border-box. + if ( ( !support.boxSizingReliable() && isBorderBox || + + // Support: IE 10 - 11+, Edge 15 - 18+ + // IE/Edge misreport `getComputedStyle` of table rows with width/height + // set in CSS while `offset*` properties report correct values. + // Interestingly, in some cases IE 9 doesn't suffer from this issue. + !support.reliableTrDimensions() && nodeName( elem, "tr" ) || + + // Fall back to offsetWidth/offsetHeight when value is "auto" + // This happens for inline elements with no explicit setting (gh-3571) + val === "auto" || + + // Support: Android <=4.1 - 4.3 only + // Also use offsetWidth/offsetHeight for misreported inline dimensions (gh-3602) + !parseFloat( val ) && jQuery.css( elem, "display", false, styles ) === "inline" ) && + + // Make sure the element is visible & connected + elem.getClientRects().length ) { + + isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box"; + + // Where available, offsetWidth/offsetHeight approximate border box dimensions. + // Where not available (e.g., SVG), assume unreliable box-sizing and interpret the + // retrieved value as a content box dimension. + valueIsBorderBox = offsetProp in elem; + if ( valueIsBorderBox ) { + val = elem[ offsetProp ]; + } + } + + // Normalize "" and auto + val = parseFloat( val ) || 0; + + // Adjust for the element's box model + return ( val + + boxModelAdjustment( + elem, + dimension, + extra || ( isBorderBox ? "border" : "content" ), + valueIsBorderBox, + styles, + + // Provide the current computed size to request scroll gutter calculation (gh-3589) + val + ) + ) + "px"; +} + +jQuery.extend( { + + // Add in style property hooks for overriding the default + // behavior of getting and setting a style property + cssHooks: { + opacity: { + get: function( elem, computed ) { + if ( computed ) { + + // We should always get a number back from opacity + var ret = curCSS( elem, "opacity" ); + return ret === "" ? "1" : ret; + } + } + } + }, + + // Don't automatically add "px" to these possibly-unitless properties + cssNumber: { + "animationIterationCount": true, + "columnCount": true, + "fillOpacity": true, + "flexGrow": true, + "flexShrink": true, + "fontWeight": true, + "gridArea": true, + "gridColumn": true, + "gridColumnEnd": true, + "gridColumnStart": true, + "gridRow": true, + "gridRowEnd": true, + "gridRowStart": true, + "lineHeight": true, + "opacity": true, + "order": true, + "orphans": true, + "widows": true, + "zIndex": true, + "zoom": true + }, + + // Add in properties whose names you wish to fix before + // setting or getting the value + cssProps: {}, + + // Get and set the style property on a DOM Node + style: function( elem, name, value, extra ) { + + // Don't set styles on text and comment nodes + if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { + return; + } + + // Make sure that we're working with the right name + var ret, type, hooks, + origName = camelCase( name ), + isCustomProp = rcustomProp.test( name ), + style = elem.style; + + // Make sure that we're working with the right name. We don't + // want to query the value if it is a CSS custom property + // since they are user-defined. + if ( !isCustomProp ) { + name = finalPropName( origName ); + } + + // Gets hook for the prefixed version, then unprefixed version + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // Check if we're setting a value + if ( value !== undefined ) { + type = typeof value; + + // Convert "+=" or "-=" to relative numbers (#7345) + if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) { + value = adjustCSS( elem, name, ret ); + + // Fixes bug #9237 + type = "number"; + } + + // Make sure that null and NaN values aren't set (#7116) + if ( value == null || value !== value ) { + return; + } + + // If a number was passed in, add the unit (except for certain CSS properties) + // The isCustomProp check can be removed in jQuery 4.0 when we only auto-append + // "px" to a few hardcoded values. + if ( type === "number" && !isCustomProp ) { + value += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? "" : "px" ); + } + + // background-* props affect original clone's values + if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) { + style[ name ] = "inherit"; + } + + // If a hook was provided, use that value, otherwise just set the specified value + if ( !hooks || !( "set" in hooks ) || + ( value = hooks.set( elem, value, extra ) ) !== undefined ) { + + if ( isCustomProp ) { + style.setProperty( name, value ); + } else { + style[ name ] = value; + } + } + + } else { + + // If a hook was provided get the non-computed value from there + if ( hooks && "get" in hooks && + ( ret = hooks.get( elem, false, extra ) ) !== undefined ) { + + return ret; + } + + // Otherwise just get the value from the style object + return style[ name ]; + } + }, + + css: function( elem, name, extra, styles ) { + var val, num, hooks, + origName = camelCase( name ), + isCustomProp = rcustomProp.test( name ); + + // Make sure that we're working with the right name. We don't + // want to modify the value if it is a CSS custom property + // since they are user-defined. + if ( !isCustomProp ) { + name = finalPropName( origName ); + } + + // Try prefixed name followed by the unprefixed name + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // If a hook was provided get the computed value from there + if ( hooks && "get" in hooks ) { + val = hooks.get( elem, true, extra ); + } + + // Otherwise, if a way to get the computed value exists, use that + if ( val === undefined ) { + val = curCSS( elem, name, styles ); + } + + // Convert "normal" to computed value + if ( val === "normal" && name in cssNormalTransform ) { + val = cssNormalTransform[ name ]; + } + + // Make numeric if forced or a qualifier was provided and val looks numeric + if ( extra === "" || extra ) { + num = parseFloat( val ); + return extra === true || isFinite( num ) ? num || 0 : val; + } + + return val; + } +} ); + +jQuery.each( [ "height", "width" ], function( _i, dimension ) { + jQuery.cssHooks[ dimension ] = { + get: function( elem, computed, extra ) { + if ( computed ) { + + // Certain elements can have dimension info if we invisibly show them + // but it must have a current display style that would benefit + return rdisplayswap.test( jQuery.css( elem, "display" ) ) && + + // Support: Safari 8+ + // Table columns in Safari have non-zero offsetWidth & zero + // getBoundingClientRect().width unless display is changed. + // Support: IE <=11 only + // Running getBoundingClientRect on a disconnected node + // in IE throws an error. + ( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ? + swap( elem, cssShow, function() { + return getWidthOrHeight( elem, dimension, extra ); + } ) : + getWidthOrHeight( elem, dimension, extra ); + } + }, + + set: function( elem, value, extra ) { + var matches, + styles = getStyles( elem ), + + // Only read styles.position if the test has a chance to fail + // to avoid forcing a reflow. + scrollboxSizeBuggy = !support.scrollboxSize() && + styles.position === "absolute", + + // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-3991) + boxSizingNeeded = scrollboxSizeBuggy || extra, + isBorderBox = boxSizingNeeded && + jQuery.css( elem, "boxSizing", false, styles ) === "border-box", + subtract = extra ? + boxModelAdjustment( + elem, + dimension, + extra, + isBorderBox, + styles + ) : + 0; + + // Account for unreliable border-box dimensions by comparing offset* to computed and + // faking a content-box to get border and padding (gh-3699) + if ( isBorderBox && scrollboxSizeBuggy ) { + subtract -= Math.ceil( + elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - + parseFloat( styles[ dimension ] ) - + boxModelAdjustment( elem, dimension, "border", false, styles ) - + 0.5 + ); + } + + // Convert to pixels if value adjustment is needed + if ( subtract && ( matches = rcssNum.exec( value ) ) && + ( matches[ 3 ] || "px" ) !== "px" ) { + + elem.style[ dimension ] = value; + value = jQuery.css( elem, dimension ); + } + + return setPositiveNumber( elem, value, subtract ); + } + }; +} ); + +jQuery.cssHooks.marginLeft = addGetHookIf( support.reliableMarginLeft, + function( elem, computed ) { + if ( computed ) { + return ( parseFloat( curCSS( elem, "marginLeft" ) ) || + elem.getBoundingClientRect().left - + swap( elem, { marginLeft: 0 }, function() { + return elem.getBoundingClientRect().left; + } ) + ) + "px"; + } + } +); + +// These hooks are used by animate to expand properties +jQuery.each( { + margin: "", + padding: "", + border: "Width" +}, function( prefix, suffix ) { + jQuery.cssHooks[ prefix + suffix ] = { + expand: function( value ) { + var i = 0, + expanded = {}, + + // Assumes a single number if not a string + parts = typeof value === "string" ? value.split( " " ) : [ value ]; + + for ( ; i < 4; i++ ) { + expanded[ prefix + cssExpand[ i ] + suffix ] = + parts[ i ] || parts[ i - 2 ] || parts[ 0 ]; + } + + return expanded; + } + }; + + if ( prefix !== "margin" ) { + jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber; + } +} ); + +jQuery.fn.extend( { + css: function( name, value ) { + return access( this, function( elem, name, value ) { + var styles, len, + map = {}, + i = 0; + + if ( Array.isArray( name ) ) { + styles = getStyles( elem ); + len = name.length; + + for ( ; i < len; i++ ) { + map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles ); + } + + return map; + } + + return value !== undefined ? + jQuery.style( elem, name, value ) : + jQuery.css( elem, name ); + }, name, value, arguments.length > 1 ); + } +} ); + + +function Tween( elem, options, prop, end, easing ) { + return new Tween.prototype.init( elem, options, prop, end, easing ); +} +jQuery.Tween = Tween; + +Tween.prototype = { + constructor: Tween, + init: function( elem, options, prop, end, easing, unit ) { + this.elem = elem; + this.prop = prop; + this.easing = easing || jQuery.easing._default; + this.options = options; + this.start = this.now = this.cur(); + this.end = end; + this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" ); + }, + cur: function() { + var hooks = Tween.propHooks[ this.prop ]; + + return hooks && hooks.get ? + hooks.get( this ) : + Tween.propHooks._default.get( this ); + }, + run: function( percent ) { + var eased, + hooks = Tween.propHooks[ this.prop ]; + + if ( this.options.duration ) { + this.pos = eased = jQuery.easing[ this.easing ]( + percent, this.options.duration * percent, 0, 1, this.options.duration + ); + } else { + this.pos = eased = percent; + } + this.now = ( this.end - this.start ) * eased + this.start; + + if ( this.options.step ) { + this.options.step.call( this.elem, this.now, this ); + } + + if ( hooks && hooks.set ) { + hooks.set( this ); + } else { + Tween.propHooks._default.set( this ); + } + return this; + } +}; + +Tween.prototype.init.prototype = Tween.prototype; + +Tween.propHooks = { + _default: { + get: function( tween ) { + var result; + + // Use a property on the element directly when it is not a DOM element, + // or when there is no matching style property that exists. + if ( tween.elem.nodeType !== 1 || + tween.elem[ tween.prop ] != null && tween.elem.style[ tween.prop ] == null ) { + return tween.elem[ tween.prop ]; + } + + // Passing an empty string as a 3rd parameter to .css will automatically + // attempt a parseFloat and fallback to a string if the parse fails. + // Simple values such as "10px" are parsed to Float; + // complex values such as "rotate(1rad)" are returned as-is. + result = jQuery.css( tween.elem, tween.prop, "" ); + + // Empty strings, null, undefined and "auto" are converted to 0. + return !result || result === "auto" ? 0 : result; + }, + set: function( tween ) { + + // Use step hook for back compat. + // Use cssHook if its there. + // Use .style if available and use plain properties where available. + if ( jQuery.fx.step[ tween.prop ] ) { + jQuery.fx.step[ tween.prop ]( tween ); + } else if ( tween.elem.nodeType === 1 && ( + jQuery.cssHooks[ tween.prop ] || + tween.elem.style[ finalPropName( tween.prop ) ] != null ) ) { + jQuery.style( tween.elem, tween.prop, tween.now + tween.unit ); + } else { + tween.elem[ tween.prop ] = tween.now; + } + } + } +}; + +// Support: IE <=9 only +// Panic based approach to setting things on disconnected nodes +Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = { + set: function( tween ) { + if ( tween.elem.nodeType && tween.elem.parentNode ) { + tween.elem[ tween.prop ] = tween.now; + } + } +}; + +jQuery.easing = { + linear: function( p ) { + return p; + }, + swing: function( p ) { + return 0.5 - Math.cos( p * Math.PI ) / 2; + }, + _default: "swing" +}; + +jQuery.fx = Tween.prototype.init; + +// Back compat <1.8 extension point +jQuery.fx.step = {}; + + + + +var + fxNow, inProgress, + rfxtypes = /^(?:toggle|show|hide)$/, + rrun = /queueHooks$/; + +function schedule() { + if ( inProgress ) { + if ( document.hidden === false && window.requestAnimationFrame ) { + window.requestAnimationFrame( schedule ); + } else { + window.setTimeout( schedule, jQuery.fx.interval ); + } + + jQuery.fx.tick(); + } +} + +// Animations created synchronously will run synchronously +function createFxNow() { + window.setTimeout( function() { + fxNow = undefined; + } ); + return ( fxNow = Date.now() ); +} + +// Generate parameters to create a standard animation +function genFx( type, includeWidth ) { + var which, + i = 0, + attrs = { height: type }; + + // If we include width, step value is 1 to do all cssExpand values, + // otherwise step value is 2 to skip over Left and Right + includeWidth = includeWidth ? 1 : 0; + for ( ; i < 4; i += 2 - includeWidth ) { + which = cssExpand[ i ]; + attrs[ "margin" + which ] = attrs[ "padding" + which ] = type; + } + + if ( includeWidth ) { + attrs.opacity = attrs.width = type; + } + + return attrs; +} + +function createTween( value, prop, animation ) { + var tween, + collection = ( Animation.tweeners[ prop ] || [] ).concat( Animation.tweeners[ "*" ] ), + index = 0, + length = collection.length; + for ( ; index < length; index++ ) { + if ( ( tween = collection[ index ].call( animation, prop, value ) ) ) { + + // We're done with this property + return tween; + } + } +} + +function defaultPrefilter( elem, props, opts ) { + var prop, value, toggle, hooks, oldfire, propTween, restoreDisplay, display, + isBox = "width" in props || "height" in props, + anim = this, + orig = {}, + style = elem.style, + hidden = elem.nodeType && isHiddenWithinTree( elem ), + dataShow = dataPriv.get( elem, "fxshow" ); + + // Queue-skipping animations hijack the fx hooks + if ( !opts.queue ) { + hooks = jQuery._queueHooks( elem, "fx" ); + if ( hooks.unqueued == null ) { + hooks.unqueued = 0; + oldfire = hooks.empty.fire; + hooks.empty.fire = function() { + if ( !hooks.unqueued ) { + oldfire(); + } + }; + } + hooks.unqueued++; + + anim.always( function() { + + // Ensure the complete handler is called before this completes + anim.always( function() { + hooks.unqueued--; + if ( !jQuery.queue( elem, "fx" ).length ) { + hooks.empty.fire(); + } + } ); + } ); + } + + // Detect show/hide animations + for ( prop in props ) { + value = props[ prop ]; + if ( rfxtypes.test( value ) ) { + delete props[ prop ]; + toggle = toggle || value === "toggle"; + if ( value === ( hidden ? "hide" : "show" ) ) { + + // Pretend to be hidden if this is a "show" and + // there is still data from a stopped show/hide + if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) { + hidden = true; + + // Ignore all other no-op show/hide data + } else { + continue; + } + } + orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop ); + } + } + + // Bail out if this is a no-op like .hide().hide() + propTween = !jQuery.isEmptyObject( props ); + if ( !propTween && jQuery.isEmptyObject( orig ) ) { + return; + } + + // Restrict "overflow" and "display" styles during box animations + if ( isBox && elem.nodeType === 1 ) { + + // Support: IE <=9 - 11, Edge 12 - 15 + // Record all 3 overflow attributes because IE does not infer the shorthand + // from identically-valued overflowX and overflowY and Edge just mirrors + // the overflowX value there. + opts.overflow = [ style.overflow, style.overflowX, style.overflowY ]; + + // Identify a display type, preferring old show/hide data over the CSS cascade + restoreDisplay = dataShow && dataShow.display; + if ( restoreDisplay == null ) { + restoreDisplay = dataPriv.get( elem, "display" ); + } + display = jQuery.css( elem, "display" ); + if ( display === "none" ) { + if ( restoreDisplay ) { + display = restoreDisplay; + } else { + + // Get nonempty value(s) by temporarily forcing visibility + showHide( [ elem ], true ); + restoreDisplay = elem.style.display || restoreDisplay; + display = jQuery.css( elem, "display" ); + showHide( [ elem ] ); + } + } + + // Animate inline elements as inline-block + if ( display === "inline" || display === "inline-block" && restoreDisplay != null ) { + if ( jQuery.css( elem, "float" ) === "none" ) { + + // Restore the original display value at the end of pure show/hide animations + if ( !propTween ) { + anim.done( function() { + style.display = restoreDisplay; + } ); + if ( restoreDisplay == null ) { + display = style.display; + restoreDisplay = display === "none" ? "" : display; + } + } + style.display = "inline-block"; + } + } + } + + if ( opts.overflow ) { + style.overflow = "hidden"; + anim.always( function() { + style.overflow = opts.overflow[ 0 ]; + style.overflowX = opts.overflow[ 1 ]; + style.overflowY = opts.overflow[ 2 ]; + } ); + } + + // Implement show/hide animations + propTween = false; + for ( prop in orig ) { + + // General show/hide setup for this element animation + if ( !propTween ) { + if ( dataShow ) { + if ( "hidden" in dataShow ) { + hidden = dataShow.hidden; + } + } else { + dataShow = dataPriv.access( elem, "fxshow", { display: restoreDisplay } ); + } + + // Store hidden/visible for toggle so `.stop().toggle()` "reverses" + if ( toggle ) { + dataShow.hidden = !hidden; + } + + // Show elements before animating them + if ( hidden ) { + showHide( [ elem ], true ); + } + + /* eslint-disable no-loop-func */ + + anim.done( function() { + + /* eslint-enable no-loop-func */ + + // The final step of a "hide" animation is actually hiding the element + if ( !hidden ) { + showHide( [ elem ] ); + } + dataPriv.remove( elem, "fxshow" ); + for ( prop in orig ) { + jQuery.style( elem, prop, orig[ prop ] ); + } + } ); + } + + // Per-property setup + propTween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim ); + if ( !( prop in dataShow ) ) { + dataShow[ prop ] = propTween.start; + if ( hidden ) { + propTween.end = propTween.start; + propTween.start = 0; + } + } + } +} + +function propFilter( props, specialEasing ) { + var index, name, easing, value, hooks; + + // camelCase, specialEasing and expand cssHook pass + for ( index in props ) { + name = camelCase( index ); + easing = specialEasing[ name ]; + value = props[ index ]; + if ( Array.isArray( value ) ) { + easing = value[ 1 ]; + value = props[ index ] = value[ 0 ]; + } + + if ( index !== name ) { + props[ name ] = value; + delete props[ index ]; + } + + hooks = jQuery.cssHooks[ name ]; + if ( hooks && "expand" in hooks ) { + value = hooks.expand( value ); + delete props[ name ]; + + // Not quite $.extend, this won't overwrite existing keys. + // Reusing 'index' because we have the correct "name" + for ( index in value ) { + if ( !( index in props ) ) { + props[ index ] = value[ index ]; + specialEasing[ index ] = easing; + } + } + } else { + specialEasing[ name ] = easing; + } + } +} + +function Animation( elem, properties, options ) { + var result, + stopped, + index = 0, + length = Animation.prefilters.length, + deferred = jQuery.Deferred().always( function() { + + // Don't match elem in the :animated selector + delete tick.elem; + } ), + tick = function() { + if ( stopped ) { + return false; + } + var currentTime = fxNow || createFxNow(), + remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ), + + // Support: Android 2.3 only + // Archaic crash bug won't allow us to use `1 - ( 0.5 || 0 )` (#12497) + temp = remaining / animation.duration || 0, + percent = 1 - temp, + index = 0, + length = animation.tweens.length; + + for ( ; index < length; index++ ) { + animation.tweens[ index ].run( percent ); + } + + deferred.notifyWith( elem, [ animation, percent, remaining ] ); + + // If there's more to do, yield + if ( percent < 1 && length ) { + return remaining; + } + + // If this was an empty animation, synthesize a final progress notification + if ( !length ) { + deferred.notifyWith( elem, [ animation, 1, 0 ] ); + } + + // Resolve the animation and report its conclusion + deferred.resolveWith( elem, [ animation ] ); + return false; + }, + animation = deferred.promise( { + elem: elem, + props: jQuery.extend( {}, properties ), + opts: jQuery.extend( true, { + specialEasing: {}, + easing: jQuery.easing._default + }, options ), + originalProperties: properties, + originalOptions: options, + startTime: fxNow || createFxNow(), + duration: options.duration, + tweens: [], + createTween: function( prop, end ) { + var tween = jQuery.Tween( elem, animation.opts, prop, end, + animation.opts.specialEasing[ prop ] || animation.opts.easing ); + animation.tweens.push( tween ); + return tween; + }, + stop: function( gotoEnd ) { + var index = 0, + + // If we are going to the end, we want to run all the tweens + // otherwise we skip this part + length = gotoEnd ? animation.tweens.length : 0; + if ( stopped ) { + return this; + } + stopped = true; + for ( ; index < length; index++ ) { + animation.tweens[ index ].run( 1 ); + } + + // Resolve when we played the last frame; otherwise, reject + if ( gotoEnd ) { + deferred.notifyWith( elem, [ animation, 1, 0 ] ); + deferred.resolveWith( elem, [ animation, gotoEnd ] ); + } else { + deferred.rejectWith( elem, [ animation, gotoEnd ] ); + } + return this; + } + } ), + props = animation.props; + + propFilter( props, animation.opts.specialEasing ); + + for ( ; index < length; index++ ) { + result = Animation.prefilters[ index ].call( animation, elem, props, animation.opts ); + if ( result ) { + if ( isFunction( result.stop ) ) { + jQuery._queueHooks( animation.elem, animation.opts.queue ).stop = + result.stop.bind( result ); + } + return result; + } + } + + jQuery.map( props, createTween, animation ); + + if ( isFunction( animation.opts.start ) ) { + animation.opts.start.call( elem, animation ); + } + + // Attach callbacks from options + animation + .progress( animation.opts.progress ) + .done( animation.opts.done, animation.opts.complete ) + .fail( animation.opts.fail ) + .always( animation.opts.always ); + + jQuery.fx.timer( + jQuery.extend( tick, { + elem: elem, + anim: animation, + queue: animation.opts.queue + } ) + ); + + return animation; +} + +jQuery.Animation = jQuery.extend( Animation, { + + tweeners: { + "*": [ function( prop, value ) { + var tween = this.createTween( prop, value ); + adjustCSS( tween.elem, prop, rcssNum.exec( value ), tween ); + return tween; + } ] + }, + + tweener: function( props, callback ) { + if ( isFunction( props ) ) { + callback = props; + props = [ "*" ]; + } else { + props = props.match( rnothtmlwhite ); + } + + var prop, + index = 0, + length = props.length; + + for ( ; index < length; index++ ) { + prop = props[ index ]; + Animation.tweeners[ prop ] = Animation.tweeners[ prop ] || []; + Animation.tweeners[ prop ].unshift( callback ); + } + }, + + prefilters: [ defaultPrefilter ], + + prefilter: function( callback, prepend ) { + if ( prepend ) { + Animation.prefilters.unshift( callback ); + } else { + Animation.prefilters.push( callback ); + } + } +} ); + +jQuery.speed = function( speed, easing, fn ) { + var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : { + complete: fn || !fn && easing || + isFunction( speed ) && speed, + duration: speed, + easing: fn && easing || easing && !isFunction( easing ) && easing + }; + + // Go to the end state if fx are off + if ( jQuery.fx.off ) { + opt.duration = 0; + + } else { + if ( typeof opt.duration !== "number" ) { + if ( opt.duration in jQuery.fx.speeds ) { + opt.duration = jQuery.fx.speeds[ opt.duration ]; + + } else { + opt.duration = jQuery.fx.speeds._default; + } + } + } + + // Normalize opt.queue - true/undefined/null -> "fx" + if ( opt.queue == null || opt.queue === true ) { + opt.queue = "fx"; + } + + // Queueing + opt.old = opt.complete; + + opt.complete = function() { + if ( isFunction( opt.old ) ) { + opt.old.call( this ); + } + + if ( opt.queue ) { + jQuery.dequeue( this, opt.queue ); + } + }; + + return opt; +}; + +jQuery.fn.extend( { + fadeTo: function( speed, to, easing, callback ) { + + // Show any hidden elements after setting opacity to 0 + return this.filter( isHiddenWithinTree ).css( "opacity", 0 ).show() + + // Animate to the value specified + .end().animate( { opacity: to }, speed, easing, callback ); + }, + animate: function( prop, speed, easing, callback ) { + var empty = jQuery.isEmptyObject( prop ), + optall = jQuery.speed( speed, easing, callback ), + doAnimation = function() { + + // Operate on a copy of prop so per-property easing won't be lost + var anim = Animation( this, jQuery.extend( {}, prop ), optall ); + + // Empty animations, or finishing resolves immediately + if ( empty || dataPriv.get( this, "finish" ) ) { + anim.stop( true ); + } + }; + doAnimation.finish = doAnimation; + + return empty || optall.queue === false ? + this.each( doAnimation ) : + this.queue( optall.queue, doAnimation ); + }, + stop: function( type, clearQueue, gotoEnd ) { + var stopQueue = function( hooks ) { + var stop = hooks.stop; + delete hooks.stop; + stop( gotoEnd ); + }; + + if ( typeof type !== "string" ) { + gotoEnd = clearQueue; + clearQueue = type; + type = undefined; + } + if ( clearQueue ) { + this.queue( type || "fx", [] ); + } + + return this.each( function() { + var dequeue = true, + index = type != null && type + "queueHooks", + timers = jQuery.timers, + data = dataPriv.get( this ); + + if ( index ) { + if ( data[ index ] && data[ index ].stop ) { + stopQueue( data[ index ] ); + } + } else { + for ( index in data ) { + if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) { + stopQueue( data[ index ] ); + } + } + } + + for ( index = timers.length; index--; ) { + if ( timers[ index ].elem === this && + ( type == null || timers[ index ].queue === type ) ) { + + timers[ index ].anim.stop( gotoEnd ); + dequeue = false; + timers.splice( index, 1 ); + } + } + + // Start the next in the queue if the last step wasn't forced. + // Timers currently will call their complete callbacks, which + // will dequeue but only if they were gotoEnd. + if ( dequeue || !gotoEnd ) { + jQuery.dequeue( this, type ); + } + } ); + }, + finish: function( type ) { + if ( type !== false ) { + type = type || "fx"; + } + return this.each( function() { + var index, + data = dataPriv.get( this ), + queue = data[ type + "queue" ], + hooks = data[ type + "queueHooks" ], + timers = jQuery.timers, + length = queue ? queue.length : 0; + + // Enable finishing flag on private data + data.finish = true; + + // Empty the queue first + jQuery.queue( this, type, [] ); + + if ( hooks && hooks.stop ) { + hooks.stop.call( this, true ); + } + + // Look for any active animations, and finish them + for ( index = timers.length; index--; ) { + if ( timers[ index ].elem === this && timers[ index ].queue === type ) { + timers[ index ].anim.stop( true ); + timers.splice( index, 1 ); + } + } + + // Look for any animations in the old queue and finish them + for ( index = 0; index < length; index++ ) { + if ( queue[ index ] && queue[ index ].finish ) { + queue[ index ].finish.call( this ); + } + } + + // Turn off finishing flag + delete data.finish; + } ); + } +} ); + +jQuery.each( [ "toggle", "show", "hide" ], function( _i, name ) { + var cssFn = jQuery.fn[ name ]; + jQuery.fn[ name ] = function( speed, easing, callback ) { + return speed == null || typeof speed === "boolean" ? + cssFn.apply( this, arguments ) : + this.animate( genFx( name, true ), speed, easing, callback ); + }; +} ); + +// Generate shortcuts for custom animations +jQuery.each( { + slideDown: genFx( "show" ), + slideUp: genFx( "hide" ), + slideToggle: genFx( "toggle" ), + fadeIn: { opacity: "show" }, + fadeOut: { opacity: "hide" }, + fadeToggle: { opacity: "toggle" } +}, function( name, props ) { + jQuery.fn[ name ] = function( speed, easing, callback ) { + return this.animate( props, speed, easing, callback ); + }; +} ); + +jQuery.timers = []; +jQuery.fx.tick = function() { + var timer, + i = 0, + timers = jQuery.timers; + + fxNow = Date.now(); + + for ( ; i < timers.length; i++ ) { + timer = timers[ i ]; + + // Run the timer and safely remove it when done (allowing for external removal) + if ( !timer() && timers[ i ] === timer ) { + timers.splice( i--, 1 ); + } + } + + if ( !timers.length ) { + jQuery.fx.stop(); + } + fxNow = undefined; +}; + +jQuery.fx.timer = function( timer ) { + jQuery.timers.push( timer ); + jQuery.fx.start(); +}; + +jQuery.fx.interval = 13; +jQuery.fx.start = function() { + if ( inProgress ) { + return; + } + + inProgress = true; + schedule(); +}; + +jQuery.fx.stop = function() { + inProgress = null; +}; + +jQuery.fx.speeds = { + slow: 600, + fast: 200, + + // Default speed + _default: 400 +}; + + +// Based off of the plugin by Clint Helfers, with permission. +// https://web.archive.org/web/20100324014747/http://blindsignals.com/index.php/2009/07/jquery-delay/ +jQuery.fn.delay = function( time, type ) { + time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; + type = type || "fx"; + + return this.queue( type, function( next, hooks ) { + var timeout = window.setTimeout( next, time ); + hooks.stop = function() { + window.clearTimeout( timeout ); + }; + } ); +}; + + +( function() { + var input = document.createElement( "input" ), + select = document.createElement( "select" ), + opt = select.appendChild( document.createElement( "option" ) ); + + input.type = "checkbox"; + + // Support: Android <=4.3 only + // Default value for a checkbox should be "on" + support.checkOn = input.value !== ""; + + // Support: IE <=11 only + // Must access selectedIndex to make default options select + support.optSelected = opt.selected; + + // Support: IE <=11 only + // An input loses its value after becoming a radio + input = document.createElement( "input" ); + input.value = "t"; + input.type = "radio"; + support.radioValue = input.value === "t"; +} )(); + + +var boolHook, + attrHandle = jQuery.expr.attrHandle; + +jQuery.fn.extend( { + attr: function( name, value ) { + return access( this, jQuery.attr, name, value, arguments.length > 1 ); + }, + + removeAttr: function( name ) { + return this.each( function() { + jQuery.removeAttr( this, name ); + } ); + } +} ); + +jQuery.extend( { + attr: function( elem, name, value ) { + var ret, hooks, + nType = elem.nodeType; + + // Don't get/set attributes on text, comment and attribute nodes + if ( nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + // Fallback to prop when attributes are not supported + if ( typeof elem.getAttribute === "undefined" ) { + return jQuery.prop( elem, name, value ); + } + + // Attribute hooks are determined by the lowercase version + // Grab necessary hook if one is defined + if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { + hooks = jQuery.attrHooks[ name.toLowerCase() ] || + ( jQuery.expr.match.bool.test( name ) ? boolHook : undefined ); + } + + if ( value !== undefined ) { + if ( value === null ) { + jQuery.removeAttr( elem, name ); + return; + } + + if ( hooks && "set" in hooks && + ( ret = hooks.set( elem, value, name ) ) !== undefined ) { + return ret; + } + + elem.setAttribute( name, value + "" ); + return value; + } + + if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { + return ret; + } + + ret = jQuery.find.attr( elem, name ); + + // Non-existent attributes return null, we normalize to undefined + return ret == null ? undefined : ret; + }, + + attrHooks: { + type: { + set: function( elem, value ) { + if ( !support.radioValue && value === "radio" && + nodeName( elem, "input" ) ) { + var val = elem.value; + elem.setAttribute( "type", value ); + if ( val ) { + elem.value = val; + } + return value; + } + } + } + }, + + removeAttr: function( elem, value ) { + var name, + i = 0, + + // Attribute names can contain non-HTML whitespace characters + // https://html.spec.whatwg.org/multipage/syntax.html#attributes-2 + attrNames = value && value.match( rnothtmlwhite ); + + if ( attrNames && elem.nodeType === 1 ) { + while ( ( name = attrNames[ i++ ] ) ) { + elem.removeAttribute( name ); + } + } + } +} ); + +// Hooks for boolean attributes +boolHook = { + set: function( elem, value, name ) { + if ( value === false ) { + + // Remove boolean attributes when set to false + jQuery.removeAttr( elem, name ); + } else { + elem.setAttribute( name, name ); + } + return name; + } +}; + +jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( _i, name ) { + var getter = attrHandle[ name ] || jQuery.find.attr; + + attrHandle[ name ] = function( elem, name, isXML ) { + var ret, handle, + lowercaseName = name.toLowerCase(); + + if ( !isXML ) { + + // Avoid an infinite loop by temporarily removing this function from the getter + handle = attrHandle[ lowercaseName ]; + attrHandle[ lowercaseName ] = ret; + ret = getter( elem, name, isXML ) != null ? + lowercaseName : + null; + attrHandle[ lowercaseName ] = handle; + } + return ret; + }; +} ); + + + + +var rfocusable = /^(?:input|select|textarea|button)$/i, + rclickable = /^(?:a|area)$/i; + +jQuery.fn.extend( { + prop: function( name, value ) { + return access( this, jQuery.prop, name, value, arguments.length > 1 ); + }, + + removeProp: function( name ) { + return this.each( function() { + delete this[ jQuery.propFix[ name ] || name ]; + } ); + } +} ); + +jQuery.extend( { + prop: function( elem, name, value ) { + var ret, hooks, + nType = elem.nodeType; + + // Don't get/set properties on text, comment and attribute nodes + if ( nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { + + // Fix name and attach hooks + name = jQuery.propFix[ name ] || name; + hooks = jQuery.propHooks[ name ]; + } + + if ( value !== undefined ) { + if ( hooks && "set" in hooks && + ( ret = hooks.set( elem, value, name ) ) !== undefined ) { + return ret; + } + + return ( elem[ name ] = value ); + } + + if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { + return ret; + } + + return elem[ name ]; + }, + + propHooks: { + tabIndex: { + get: function( elem ) { + + // Support: IE <=9 - 11 only + // elem.tabIndex doesn't always return the + // correct value when it hasn't been explicitly set + // https://web.archive.org/web/20141116233347/http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ + // Use proper attribute retrieval(#12072) + var tabindex = jQuery.find.attr( elem, "tabindex" ); + + if ( tabindex ) { + return parseInt( tabindex, 10 ); + } + + if ( + rfocusable.test( elem.nodeName ) || + rclickable.test( elem.nodeName ) && + elem.href + ) { + return 0; + } + + return -1; + } + } + }, + + propFix: { + "for": "htmlFor", + "class": "className" + } +} ); + +// Support: IE <=11 only +// Accessing the selectedIndex property +// forces the browser to respect setting selected +// on the option +// The getter ensures a default option is selected +// when in an optgroup +// eslint rule "no-unused-expressions" is disabled for this code +// since it considers such accessions noop +if ( !support.optSelected ) { + jQuery.propHooks.selected = { + get: function( elem ) { + + /* eslint no-unused-expressions: "off" */ + + var parent = elem.parentNode; + if ( parent && parent.parentNode ) { + parent.parentNode.selectedIndex; + } + return null; + }, + set: function( elem ) { + + /* eslint no-unused-expressions: "off" */ + + var parent = elem.parentNode; + if ( parent ) { + parent.selectedIndex; + + if ( parent.parentNode ) { + parent.parentNode.selectedIndex; + } + } + } + }; +} + +jQuery.each( [ + "tabIndex", + "readOnly", + "maxLength", + "cellSpacing", + "cellPadding", + "rowSpan", + "colSpan", + "useMap", + "frameBorder", + "contentEditable" +], function() { + jQuery.propFix[ this.toLowerCase() ] = this; +} ); + + + + + // Strip and collapse whitespace according to HTML spec + // https://infra.spec.whatwg.org/#strip-and-collapse-ascii-whitespace + function stripAndCollapse( value ) { + var tokens = value.match( rnothtmlwhite ) || []; + return tokens.join( " " ); + } + + +function getClass( elem ) { + return elem.getAttribute && elem.getAttribute( "class" ) || ""; +} + +function classesToArray( value ) { + if ( Array.isArray( value ) ) { + return value; + } + if ( typeof value === "string" ) { + return value.match( rnothtmlwhite ) || []; + } + return []; +} + +jQuery.fn.extend( { + addClass: function( value ) { + var classes, elem, cur, curValue, clazz, j, finalValue, + i = 0; + + if ( isFunction( value ) ) { + return this.each( function( j ) { + jQuery( this ).addClass( value.call( this, j, getClass( this ) ) ); + } ); + } + + classes = classesToArray( value ); + + if ( classes.length ) { + while ( ( elem = this[ i++ ] ) ) { + curValue = getClass( elem ); + cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); + + if ( cur ) { + j = 0; + while ( ( clazz = classes[ j++ ] ) ) { + if ( cur.indexOf( " " + clazz + " " ) < 0 ) { + cur += clazz + " "; + } + } + + // Only assign if different to avoid unneeded rendering. + finalValue = stripAndCollapse( cur ); + if ( curValue !== finalValue ) { + elem.setAttribute( "class", finalValue ); + } + } + } + } + + return this; + }, + + removeClass: function( value ) { + var classes, elem, cur, curValue, clazz, j, finalValue, + i = 0; + + if ( isFunction( value ) ) { + return this.each( function( j ) { + jQuery( this ).removeClass( value.call( this, j, getClass( this ) ) ); + } ); + } + + if ( !arguments.length ) { + return this.attr( "class", "" ); + } + + classes = classesToArray( value ); + + if ( classes.length ) { + while ( ( elem = this[ i++ ] ) ) { + curValue = getClass( elem ); + + // This expression is here for better compressibility (see addClass) + cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); + + if ( cur ) { + j = 0; + while ( ( clazz = classes[ j++ ] ) ) { + + // Remove *all* instances + while ( cur.indexOf( " " + clazz + " " ) > -1 ) { + cur = cur.replace( " " + clazz + " ", " " ); + } + } + + // Only assign if different to avoid unneeded rendering. + finalValue = stripAndCollapse( cur ); + if ( curValue !== finalValue ) { + elem.setAttribute( "class", finalValue ); + } + } + } + } + + return this; + }, + + toggleClass: function( value, stateVal ) { + var type = typeof value, + isValidValue = type === "string" || Array.isArray( value ); + + if ( typeof stateVal === "boolean" && isValidValue ) { + return stateVal ? this.addClass( value ) : this.removeClass( value ); + } + + if ( isFunction( value ) ) { + return this.each( function( i ) { + jQuery( this ).toggleClass( + value.call( this, i, getClass( this ), stateVal ), + stateVal + ); + } ); + } + + return this.each( function() { + var className, i, self, classNames; + + if ( isValidValue ) { + + // Toggle individual class names + i = 0; + self = jQuery( this ); + classNames = classesToArray( value ); + + while ( ( className = classNames[ i++ ] ) ) { + + // Check each className given, space separated list + if ( self.hasClass( className ) ) { + self.removeClass( className ); + } else { + self.addClass( className ); + } + } + + // Toggle whole class name + } else if ( value === undefined || type === "boolean" ) { + className = getClass( this ); + if ( className ) { + + // Store className if set + dataPriv.set( this, "__className__", className ); + } + + // If the element has a class name or if we're passed `false`, + // then remove the whole classname (if there was one, the above saved it). + // Otherwise bring back whatever was previously saved (if anything), + // falling back to the empty string if nothing was stored. + if ( this.setAttribute ) { + this.setAttribute( "class", + className || value === false ? + "" : + dataPriv.get( this, "__className__" ) || "" + ); + } + } + } ); + }, + + hasClass: function( selector ) { + var className, elem, + i = 0; + + className = " " + selector + " "; + while ( ( elem = this[ i++ ] ) ) { + if ( elem.nodeType === 1 && + ( " " + stripAndCollapse( getClass( elem ) ) + " " ).indexOf( className ) > -1 ) { + return true; + } + } + + return false; + } +} ); + + + + +var rreturn = /\r/g; + +jQuery.fn.extend( { + val: function( value ) { + var hooks, ret, valueIsFunction, + elem = this[ 0 ]; + + if ( !arguments.length ) { + if ( elem ) { + hooks = jQuery.valHooks[ elem.type ] || + jQuery.valHooks[ elem.nodeName.toLowerCase() ]; + + if ( hooks && + "get" in hooks && + ( ret = hooks.get( elem, "value" ) ) !== undefined + ) { + return ret; + } + + ret = elem.value; + + // Handle most common string cases + if ( typeof ret === "string" ) { + return ret.replace( rreturn, "" ); + } + + // Handle cases where value is null/undef or number + return ret == null ? "" : ret; + } + + return; + } + + valueIsFunction = isFunction( value ); + + return this.each( function( i ) { + var val; + + if ( this.nodeType !== 1 ) { + return; + } + + if ( valueIsFunction ) { + val = value.call( this, i, jQuery( this ).val() ); + } else { + val = value; + } + + // Treat null/undefined as ""; convert numbers to string + if ( val == null ) { + val = ""; + + } else if ( typeof val === "number" ) { + val += ""; + + } else if ( Array.isArray( val ) ) { + val = jQuery.map( val, function( value ) { + return value == null ? "" : value + ""; + } ); + } + + hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; + + // If set returns undefined, fall back to normal setting + if ( !hooks || !( "set" in hooks ) || hooks.set( this, val, "value" ) === undefined ) { + this.value = val; + } + } ); + } +} ); + +jQuery.extend( { + valHooks: { + option: { + get: function( elem ) { + + var val = jQuery.find.attr( elem, "value" ); + return val != null ? + val : + + // Support: IE <=10 - 11 only + // option.text throws exceptions (#14686, #14858) + // Strip and collapse whitespace + // https://html.spec.whatwg.org/#strip-and-collapse-whitespace + stripAndCollapse( jQuery.text( elem ) ); + } + }, + select: { + get: function( elem ) { + var value, option, i, + options = elem.options, + index = elem.selectedIndex, + one = elem.type === "select-one", + values = one ? null : [], + max = one ? index + 1 : options.length; + + if ( index < 0 ) { + i = max; + + } else { + i = one ? index : 0; + } + + // Loop through all the selected options + for ( ; i < max; i++ ) { + option = options[ i ]; + + // Support: IE <=9 only + // IE8-9 doesn't update selected after form reset (#2551) + if ( ( option.selected || i === index ) && + + // Don't return options that are disabled or in a disabled optgroup + !option.disabled && + ( !option.parentNode.disabled || + !nodeName( option.parentNode, "optgroup" ) ) ) { + + // Get the specific value for the option + value = jQuery( option ).val(); + + // We don't need an array for one selects + if ( one ) { + return value; + } + + // Multi-Selects return an array + values.push( value ); + } + } + + return values; + }, + + set: function( elem, value ) { + var optionSet, option, + options = elem.options, + values = jQuery.makeArray( value ), + i = options.length; + + while ( i-- ) { + option = options[ i ]; + + /* eslint-disable no-cond-assign */ + + if ( option.selected = + jQuery.inArray( jQuery.valHooks.option.get( option ), values ) > -1 + ) { + optionSet = true; + } + + /* eslint-enable no-cond-assign */ + } + + // Force browsers to behave consistently when non-matching value is set + if ( !optionSet ) { + elem.selectedIndex = -1; + } + return values; + } + } + } +} ); + +// Radios and checkboxes getter/setter +jQuery.each( [ "radio", "checkbox" ], function() { + jQuery.valHooks[ this ] = { + set: function( elem, value ) { + if ( Array.isArray( value ) ) { + return ( elem.checked = jQuery.inArray( jQuery( elem ).val(), value ) > -1 ); + } + } + }; + if ( !support.checkOn ) { + jQuery.valHooks[ this ].get = function( elem ) { + return elem.getAttribute( "value" ) === null ? "on" : elem.value; + }; + } +} ); + + + + +// Return jQuery for attributes-only inclusion + + +support.focusin = "onfocusin" in window; + + +var rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, + stopPropagationCallback = function( e ) { + e.stopPropagation(); + }; + +jQuery.extend( jQuery.event, { + + trigger: function( event, data, elem, onlyHandlers ) { + + var i, cur, tmp, bubbleType, ontype, handle, special, lastElement, + eventPath = [ elem || document ], + type = hasOwn.call( event, "type" ) ? event.type : event, + namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split( "." ) : []; + + cur = lastElement = tmp = elem = elem || document; + + // Don't do events on text and comment nodes + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + // focus/blur morphs to focusin/out; ensure we're not firing them right now + if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { + return; + } + + if ( type.indexOf( "." ) > -1 ) { + + // Namespaced trigger; create a regexp to match event type in handle() + namespaces = type.split( "." ); + type = namespaces.shift(); + namespaces.sort(); + } + ontype = type.indexOf( ":" ) < 0 && "on" + type; + + // Caller can pass in a jQuery.Event object, Object, or just an event type string + event = event[ jQuery.expando ] ? + event : + new jQuery.Event( type, typeof event === "object" && event ); + + // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true) + event.isTrigger = onlyHandlers ? 2 : 3; + event.namespace = namespaces.join( "." ); + event.rnamespace = event.namespace ? + new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ) : + null; + + // Clean up the event in case it is being reused + event.result = undefined; + if ( !event.target ) { + event.target = elem; + } + + // Clone any incoming data and prepend the event, creating the handler arg list + data = data == null ? + [ event ] : + jQuery.makeArray( data, [ event ] ); + + // Allow special events to draw outside the lines + special = jQuery.event.special[ type ] || {}; + if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { + return; + } + + // Determine event propagation path in advance, per W3C events spec (#9951) + // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) + if ( !onlyHandlers && !special.noBubble && !isWindow( elem ) ) { + + bubbleType = special.delegateType || type; + if ( !rfocusMorph.test( bubbleType + type ) ) { + cur = cur.parentNode; + } + for ( ; cur; cur = cur.parentNode ) { + eventPath.push( cur ); + tmp = cur; + } + + // Only add window if we got to document (e.g., not plain obj or detached DOM) + if ( tmp === ( elem.ownerDocument || document ) ) { + eventPath.push( tmp.defaultView || tmp.parentWindow || window ); + } + } + + // Fire handlers on the event path + i = 0; + while ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) { + lastElement = cur; + event.type = i > 1 ? + bubbleType : + special.bindType || type; + + // jQuery handler + handle = ( + dataPriv.get( cur, "events" ) || Object.create( null ) + )[ event.type ] && + dataPriv.get( cur, "handle" ); + if ( handle ) { + handle.apply( cur, data ); + } + + // Native handler + handle = ontype && cur[ ontype ]; + if ( handle && handle.apply && acceptData( cur ) ) { + event.result = handle.apply( cur, data ); + if ( event.result === false ) { + event.preventDefault(); + } + } + } + event.type = type; + + // If nobody prevented the default action, do it now + if ( !onlyHandlers && !event.isDefaultPrevented() ) { + + if ( ( !special._default || + special._default.apply( eventPath.pop(), data ) === false ) && + acceptData( elem ) ) { + + // Call a native DOM method on the target with the same name as the event. + // Don't do default actions on window, that's where global variables be (#6170) + if ( ontype && isFunction( elem[ type ] ) && !isWindow( elem ) ) { + + // Don't re-trigger an onFOO event when we call its FOO() method + tmp = elem[ ontype ]; + + if ( tmp ) { + elem[ ontype ] = null; + } + + // Prevent re-triggering of the same event, since we already bubbled it above + jQuery.event.triggered = type; + + if ( event.isPropagationStopped() ) { + lastElement.addEventListener( type, stopPropagationCallback ); + } + + elem[ type ](); + + if ( event.isPropagationStopped() ) { + lastElement.removeEventListener( type, stopPropagationCallback ); + } + + jQuery.event.triggered = undefined; + + if ( tmp ) { + elem[ ontype ] = tmp; + } + } + } + } + + return event.result; + }, + + // Piggyback on a donor event to simulate a different one + // Used only for `focus(in | out)` events + simulate: function( type, elem, event ) { + var e = jQuery.extend( + new jQuery.Event(), + event, + { + type: type, + isSimulated: true + } + ); + + jQuery.event.trigger( e, null, elem ); + } + +} ); + +jQuery.fn.extend( { + + trigger: function( type, data ) { + return this.each( function() { + jQuery.event.trigger( type, data, this ); + } ); + }, + triggerHandler: function( type, data ) { + var elem = this[ 0 ]; + if ( elem ) { + return jQuery.event.trigger( type, data, elem, true ); + } + } +} ); + + +// Support: Firefox <=44 +// Firefox doesn't have focus(in | out) events +// Related ticket - https://bugzilla.mozilla.org/show_bug.cgi?id=687787 +// +// Support: Chrome <=48 - 49, Safari <=9.0 - 9.1 +// focus(in | out) events fire after focus & blur events, +// which is spec violation - http://www.w3.org/TR/DOM-Level-3-Events/#events-focusevent-event-order +// Related ticket - https://bugs.chromium.org/p/chromium/issues/detail?id=449857 +if ( !support.focusin ) { + jQuery.each( { focus: "focusin", blur: "focusout" }, function( orig, fix ) { + + // Attach a single capturing handler on the document while someone wants focusin/focusout + var handler = function( event ) { + jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ) ); + }; + + jQuery.event.special[ fix ] = { + setup: function() { + + // Handle: regular nodes (via `this.ownerDocument`), window + // (via `this.document`) & document (via `this`). + var doc = this.ownerDocument || this.document || this, + attaches = dataPriv.access( doc, fix ); + + if ( !attaches ) { + doc.addEventListener( orig, handler, true ); + } + dataPriv.access( doc, fix, ( attaches || 0 ) + 1 ); + }, + teardown: function() { + var doc = this.ownerDocument || this.document || this, + attaches = dataPriv.access( doc, fix ) - 1; + + if ( !attaches ) { + doc.removeEventListener( orig, handler, true ); + dataPriv.remove( doc, fix ); + + } else { + dataPriv.access( doc, fix, attaches ); + } + } + }; + } ); +} +var location = window.location; + +var nonce = { guid: Date.now() }; + +var rquery = ( /\?/ ); + + + +// Cross-browser xml parsing +jQuery.parseXML = function( data ) { + var xml; + if ( !data || typeof data !== "string" ) { + return null; + } + + // Support: IE 9 - 11 only + // IE throws on parseFromString with invalid input. + try { + xml = ( new window.DOMParser() ).parseFromString( data, "text/xml" ); + } catch ( e ) { + xml = undefined; + } + + if ( !xml || xml.getElementsByTagName( "parsererror" ).length ) { + jQuery.error( "Invalid XML: " + data ); + } + return xml; +}; + + +var + rbracket = /\[\]$/, + rCRLF = /\r?\n/g, + rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i, + rsubmittable = /^(?:input|select|textarea|keygen)/i; + +function buildParams( prefix, obj, traditional, add ) { + var name; + + if ( Array.isArray( obj ) ) { + + // Serialize array item. + jQuery.each( obj, function( i, v ) { + if ( traditional || rbracket.test( prefix ) ) { + + // Treat each array item as a scalar. + add( prefix, v ); + + } else { + + // Item is non-scalar (array or object), encode its numeric index. + buildParams( + prefix + "[" + ( typeof v === "object" && v != null ? i : "" ) + "]", + v, + traditional, + add + ); + } + } ); + + } else if ( !traditional && toType( obj ) === "object" ) { + + // Serialize object item. + for ( name in obj ) { + buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add ); + } + + } else { + + // Serialize scalar item. + add( prefix, obj ); + } +} + +// Serialize an array of form elements or a set of +// key/values into a query string +jQuery.param = function( a, traditional ) { + var prefix, + s = [], + add = function( key, valueOrFunction ) { + + // If value is a function, invoke it and use its return value + var value = isFunction( valueOrFunction ) ? + valueOrFunction() : + valueOrFunction; + + s[ s.length ] = encodeURIComponent( key ) + "=" + + encodeURIComponent( value == null ? "" : value ); + }; + + if ( a == null ) { + return ""; + } + + // If an array was passed in, assume that it is an array of form elements. + if ( Array.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) { + + // Serialize the form elements + jQuery.each( a, function() { + add( this.name, this.value ); + } ); + + } else { + + // If traditional, encode the "old" way (the way 1.3.2 or older + // did it), otherwise encode params recursively. + for ( prefix in a ) { + buildParams( prefix, a[ prefix ], traditional, add ); + } + } + + // Return the resulting serialization + return s.join( "&" ); +}; + +jQuery.fn.extend( { + serialize: function() { + return jQuery.param( this.serializeArray() ); + }, + serializeArray: function() { + return this.map( function() { + + // Can add propHook for "elements" to filter or add form elements + var elements = jQuery.prop( this, "elements" ); + return elements ? jQuery.makeArray( elements ) : this; + } ) + .filter( function() { + var type = this.type; + + // Use .is( ":disabled" ) so that fieldset[disabled] works + return this.name && !jQuery( this ).is( ":disabled" ) && + rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) && + ( this.checked || !rcheckableType.test( type ) ); + } ) + .map( function( _i, elem ) { + var val = jQuery( this ).val(); + + if ( val == null ) { + return null; + } + + if ( Array.isArray( val ) ) { + return jQuery.map( val, function( val ) { + return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; + } ); + } + + return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; + } ).get(); + } +} ); + + +var + r20 = /%20/g, + rhash = /#.*$/, + rantiCache = /([?&])_=[^&]*/, + rheaders = /^(.*?):[ \t]*([^\r\n]*)$/mg, + + // #7653, #8125, #8152: local protocol detection + rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/, + rnoContent = /^(?:GET|HEAD)$/, + rprotocol = /^\/\//, + + /* Prefilters + * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example) + * 2) These are called: + * - BEFORE asking for a transport + * - AFTER param serialization (s.data is a string if s.processData is true) + * 3) key is the dataType + * 4) the catchall symbol "*" can be used + * 5) execution will start with transport dataType and THEN continue down to "*" if needed + */ + prefilters = {}, + + /* Transports bindings + * 1) key is the dataType + * 2) the catchall symbol "*" can be used + * 3) selection will start with transport dataType and THEN go to "*" if needed + */ + transports = {}, + + // Avoid comment-prolog char sequence (#10098); must appease lint and evade compression + allTypes = "*/".concat( "*" ), + + // Anchor tag for parsing the document origin + originAnchor = document.createElement( "a" ); + originAnchor.href = location.href; + +// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport +function addToPrefiltersOrTransports( structure ) { + + // dataTypeExpression is optional and defaults to "*" + return function( dataTypeExpression, func ) { + + if ( typeof dataTypeExpression !== "string" ) { + func = dataTypeExpression; + dataTypeExpression = "*"; + } + + var dataType, + i = 0, + dataTypes = dataTypeExpression.toLowerCase().match( rnothtmlwhite ) || []; + + if ( isFunction( func ) ) { + + // For each dataType in the dataTypeExpression + while ( ( dataType = dataTypes[ i++ ] ) ) { + + // Prepend if requested + if ( dataType[ 0 ] === "+" ) { + dataType = dataType.slice( 1 ) || "*"; + ( structure[ dataType ] = structure[ dataType ] || [] ).unshift( func ); + + // Otherwise append + } else { + ( structure[ dataType ] = structure[ dataType ] || [] ).push( func ); + } + } + } + }; +} + +// Base inspection function for prefilters and transports +function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) { + + var inspected = {}, + seekingTransport = ( structure === transports ); + + function inspect( dataType ) { + var selected; + inspected[ dataType ] = true; + jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) { + var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR ); + if ( typeof dataTypeOrTransport === "string" && + !seekingTransport && !inspected[ dataTypeOrTransport ] ) { + + options.dataTypes.unshift( dataTypeOrTransport ); + inspect( dataTypeOrTransport ); + return false; + } else if ( seekingTransport ) { + return !( selected = dataTypeOrTransport ); + } + } ); + return selected; + } + + return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" ); +} + +// A special extend for ajax options +// that takes "flat" options (not to be deep extended) +// Fixes #9887 +function ajaxExtend( target, src ) { + var key, deep, + flatOptions = jQuery.ajaxSettings.flatOptions || {}; + + for ( key in src ) { + if ( src[ key ] !== undefined ) { + ( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ]; + } + } + if ( deep ) { + jQuery.extend( true, target, deep ); + } + + return target; +} + +/* Handles responses to an ajax request: + * - finds the right dataType (mediates between content-type and expected dataType) + * - returns the corresponding response + */ +function ajaxHandleResponses( s, jqXHR, responses ) { + + var ct, type, finalDataType, firstDataType, + contents = s.contents, + dataTypes = s.dataTypes; + + // Remove auto dataType and get content-type in the process + while ( dataTypes[ 0 ] === "*" ) { + dataTypes.shift(); + if ( ct === undefined ) { + ct = s.mimeType || jqXHR.getResponseHeader( "Content-Type" ); + } + } + + // Check if we're dealing with a known content-type + if ( ct ) { + for ( type in contents ) { + if ( contents[ type ] && contents[ type ].test( ct ) ) { + dataTypes.unshift( type ); + break; + } + } + } + + // Check to see if we have a response for the expected dataType + if ( dataTypes[ 0 ] in responses ) { + finalDataType = dataTypes[ 0 ]; + } else { + + // Try convertible dataTypes + for ( type in responses ) { + if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[ 0 ] ] ) { + finalDataType = type; + break; + } + if ( !firstDataType ) { + firstDataType = type; + } + } + + // Or just use first one + finalDataType = finalDataType || firstDataType; + } + + // If we found a dataType + // We add the dataType to the list if needed + // and return the corresponding response + if ( finalDataType ) { + if ( finalDataType !== dataTypes[ 0 ] ) { + dataTypes.unshift( finalDataType ); + } + return responses[ finalDataType ]; + } +} + +/* Chain conversions given the request and the original response + * Also sets the responseXXX fields on the jqXHR instance + */ +function ajaxConvert( s, response, jqXHR, isSuccess ) { + var conv2, current, conv, tmp, prev, + converters = {}, + + // Work with a copy of dataTypes in case we need to modify it for conversion + dataTypes = s.dataTypes.slice(); + + // Create converters map with lowercased keys + if ( dataTypes[ 1 ] ) { + for ( conv in s.converters ) { + converters[ conv.toLowerCase() ] = s.converters[ conv ]; + } + } + + current = dataTypes.shift(); + + // Convert to each sequential dataType + while ( current ) { + + if ( s.responseFields[ current ] ) { + jqXHR[ s.responseFields[ current ] ] = response; + } + + // Apply the dataFilter if provided + if ( !prev && isSuccess && s.dataFilter ) { + response = s.dataFilter( response, s.dataType ); + } + + prev = current; + current = dataTypes.shift(); + + if ( current ) { + + // There's only work to do if current dataType is non-auto + if ( current === "*" ) { + + current = prev; + + // Convert response if prev dataType is non-auto and differs from current + } else if ( prev !== "*" && prev !== current ) { + + // Seek a direct converter + conv = converters[ prev + " " + current ] || converters[ "* " + current ]; + + // If none found, seek a pair + if ( !conv ) { + for ( conv2 in converters ) { + + // If conv2 outputs current + tmp = conv2.split( " " ); + if ( tmp[ 1 ] === current ) { + + // If prev can be converted to accepted input + conv = converters[ prev + " " + tmp[ 0 ] ] || + converters[ "* " + tmp[ 0 ] ]; + if ( conv ) { + + // Condense equivalence converters + if ( conv === true ) { + conv = converters[ conv2 ]; + + // Otherwise, insert the intermediate dataType + } else if ( converters[ conv2 ] !== true ) { + current = tmp[ 0 ]; + dataTypes.unshift( tmp[ 1 ] ); + } + break; + } + } + } + } + + // Apply converter (if not an equivalence) + if ( conv !== true ) { + + // Unless errors are allowed to bubble, catch and return them + if ( conv && s.throws ) { + response = conv( response ); + } else { + try { + response = conv( response ); + } catch ( e ) { + return { + state: "parsererror", + error: conv ? e : "No conversion from " + prev + " to " + current + }; + } + } + } + } + } + } + + return { state: "success", data: response }; +} + +jQuery.extend( { + + // Counter for holding the number of active queries + active: 0, + + // Last-Modified header cache for next request + lastModified: {}, + etag: {}, + + ajaxSettings: { + url: location.href, + type: "GET", + isLocal: rlocalProtocol.test( location.protocol ), + global: true, + processData: true, + async: true, + contentType: "application/x-www-form-urlencoded; charset=UTF-8", + + /* + timeout: 0, + data: null, + dataType: null, + username: null, + password: null, + cache: null, + throws: false, + traditional: false, + headers: {}, + */ + + accepts: { + "*": allTypes, + text: "text/plain", + html: "text/html", + xml: "application/xml, text/xml", + json: "application/json, text/javascript" + }, + + contents: { + xml: /\bxml\b/, + html: /\bhtml/, + json: /\bjson\b/ + }, + + responseFields: { + xml: "responseXML", + text: "responseText", + json: "responseJSON" + }, + + // Data converters + // Keys separate source (or catchall "*") and destination types with a single space + converters: { + + // Convert anything to text + "* text": String, + + // Text to html (true = no transformation) + "text html": true, + + // Evaluate text as a json expression + "text json": JSON.parse, + + // Parse text as xml + "text xml": jQuery.parseXML + }, + + // For options that shouldn't be deep extended: + // you can add your own custom options here if + // and when you create one that shouldn't be + // deep extended (see ajaxExtend) + flatOptions: { + url: true, + context: true + } + }, + + // Creates a full fledged settings object into target + // with both ajaxSettings and settings fields. + // If target is omitted, writes into ajaxSettings. + ajaxSetup: function( target, settings ) { + return settings ? + + // Building a settings object + ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) : + + // Extending ajaxSettings + ajaxExtend( jQuery.ajaxSettings, target ); + }, + + ajaxPrefilter: addToPrefiltersOrTransports( prefilters ), + ajaxTransport: addToPrefiltersOrTransports( transports ), + + // Main method + ajax: function( url, options ) { + + // If url is an object, simulate pre-1.5 signature + if ( typeof url === "object" ) { + options = url; + url = undefined; + } + + // Force options to be an object + options = options || {}; + + var transport, + + // URL without anti-cache param + cacheURL, + + // Response headers + responseHeadersString, + responseHeaders, + + // timeout handle + timeoutTimer, + + // Url cleanup var + urlAnchor, + + // Request state (becomes false upon send and true upon completion) + completed, + + // To know if global events are to be dispatched + fireGlobals, + + // Loop variable + i, + + // uncached part of the url + uncached, + + // Create the final options object + s = jQuery.ajaxSetup( {}, options ), + + // Callbacks context + callbackContext = s.context || s, + + // Context for global events is callbackContext if it is a DOM node or jQuery collection + globalEventContext = s.context && + ( callbackContext.nodeType || callbackContext.jquery ) ? + jQuery( callbackContext ) : + jQuery.event, + + // Deferreds + deferred = jQuery.Deferred(), + completeDeferred = jQuery.Callbacks( "once memory" ), + + // Status-dependent callbacks + statusCode = s.statusCode || {}, + + // Headers (they are sent all at once) + requestHeaders = {}, + requestHeadersNames = {}, + + // Default abort message + strAbort = "canceled", + + // Fake xhr + jqXHR = { + readyState: 0, + + // Builds headers hashtable if needed + getResponseHeader: function( key ) { + var match; + if ( completed ) { + if ( !responseHeaders ) { + responseHeaders = {}; + while ( ( match = rheaders.exec( responseHeadersString ) ) ) { + responseHeaders[ match[ 1 ].toLowerCase() + " " ] = + ( responseHeaders[ match[ 1 ].toLowerCase() + " " ] || [] ) + .concat( match[ 2 ] ); + } + } + match = responseHeaders[ key.toLowerCase() + " " ]; + } + return match == null ? null : match.join( ", " ); + }, + + // Raw string + getAllResponseHeaders: function() { + return completed ? responseHeadersString : null; + }, + + // Caches the header + setRequestHeader: function( name, value ) { + if ( completed == null ) { + name = requestHeadersNames[ name.toLowerCase() ] = + requestHeadersNames[ name.toLowerCase() ] || name; + requestHeaders[ name ] = value; + } + return this; + }, + + // Overrides response content-type header + overrideMimeType: function( type ) { + if ( completed == null ) { + s.mimeType = type; + } + return this; + }, + + // Status-dependent callbacks + statusCode: function( map ) { + var code; + if ( map ) { + if ( completed ) { + + // Execute the appropriate callbacks + jqXHR.always( map[ jqXHR.status ] ); + } else { + + // Lazy-add the new callbacks in a way that preserves old ones + for ( code in map ) { + statusCode[ code ] = [ statusCode[ code ], map[ code ] ]; + } + } + } + return this; + }, + + // Cancel the request + abort: function( statusText ) { + var finalText = statusText || strAbort; + if ( transport ) { + transport.abort( finalText ); + } + done( 0, finalText ); + return this; + } + }; + + // Attach deferreds + deferred.promise( jqXHR ); + + // Add protocol if not provided (prefilters might expect it) + // Handle falsy url in the settings object (#10093: consistency with old signature) + // We also use the url parameter if available + s.url = ( ( url || s.url || location.href ) + "" ) + .replace( rprotocol, location.protocol + "//" ); + + // Alias method option to type as per ticket #12004 + s.type = options.method || options.type || s.method || s.type; + + // Extract dataTypes list + s.dataTypes = ( s.dataType || "*" ).toLowerCase().match( rnothtmlwhite ) || [ "" ]; + + // A cross-domain request is in order when the origin doesn't match the current origin. + if ( s.crossDomain == null ) { + urlAnchor = document.createElement( "a" ); + + // Support: IE <=8 - 11, Edge 12 - 15 + // IE throws exception on accessing the href property if url is malformed, + // e.g. http://example.com:80x/ + try { + urlAnchor.href = s.url; + + // Support: IE <=8 - 11 only + // Anchor's host property isn't correctly set when s.url is relative + urlAnchor.href = urlAnchor.href; + s.crossDomain = originAnchor.protocol + "//" + originAnchor.host !== + urlAnchor.protocol + "//" + urlAnchor.host; + } catch ( e ) { + + // If there is an error parsing the URL, assume it is crossDomain, + // it can be rejected by the transport if it is invalid + s.crossDomain = true; + } + } + + // Convert data if not already a string + if ( s.data && s.processData && typeof s.data !== "string" ) { + s.data = jQuery.param( s.data, s.traditional ); + } + + // Apply prefilters + inspectPrefiltersOrTransports( prefilters, s, options, jqXHR ); + + // If request was aborted inside a prefilter, stop there + if ( completed ) { + return jqXHR; + } + + // We can fire global events as of now if asked to + // Don't fire events if jQuery.event is undefined in an AMD-usage scenario (#15118) + fireGlobals = jQuery.event && s.global; + + // Watch for a new set of requests + if ( fireGlobals && jQuery.active++ === 0 ) { + jQuery.event.trigger( "ajaxStart" ); + } + + // Uppercase the type + s.type = s.type.toUpperCase(); + + // Determine if request has content + s.hasContent = !rnoContent.test( s.type ); + + // Save the URL in case we're toying with the If-Modified-Since + // and/or If-None-Match header later on + // Remove hash to simplify url manipulation + cacheURL = s.url.replace( rhash, "" ); + + // More options handling for requests with no content + if ( !s.hasContent ) { + + // Remember the hash so we can put it back + uncached = s.url.slice( cacheURL.length ); + + // If data is available and should be processed, append data to url + if ( s.data && ( s.processData || typeof s.data === "string" ) ) { + cacheURL += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data; + + // #9682: remove data so that it's not used in an eventual retry + delete s.data; + } + + // Add or update anti-cache param if needed + if ( s.cache === false ) { + cacheURL = cacheURL.replace( rantiCache, "$1" ); + uncached = ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ( nonce.guid++ ) + + uncached; + } + + // Put hash and anti-cache on the URL that will be requested (gh-1732) + s.url = cacheURL + uncached; + + // Change '%20' to '+' if this is encoded form body content (gh-2658) + } else if ( s.data && s.processData && + ( s.contentType || "" ).indexOf( "application/x-www-form-urlencoded" ) === 0 ) { + s.data = s.data.replace( r20, "+" ); + } + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + if ( jQuery.lastModified[ cacheURL ] ) { + jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] ); + } + if ( jQuery.etag[ cacheURL ] ) { + jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] ); + } + } + + // Set the correct header, if data is being sent + if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) { + jqXHR.setRequestHeader( "Content-Type", s.contentType ); + } + + // Set the Accepts header for the server, depending on the dataType + jqXHR.setRequestHeader( + "Accept", + s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[ 0 ] ] ? + s.accepts[ s.dataTypes[ 0 ] ] + + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) : + s.accepts[ "*" ] + ); + + // Check for headers option + for ( i in s.headers ) { + jqXHR.setRequestHeader( i, s.headers[ i ] ); + } + + // Allow custom headers/mimetypes and early abort + if ( s.beforeSend && + ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || completed ) ) { + + // Abort if not done already and return + return jqXHR.abort(); + } + + // Aborting is no longer a cancellation + strAbort = "abort"; + + // Install callbacks on deferreds + completeDeferred.add( s.complete ); + jqXHR.done( s.success ); + jqXHR.fail( s.error ); + + // Get transport + transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR ); + + // If no transport, we auto-abort + if ( !transport ) { + done( -1, "No Transport" ); + } else { + jqXHR.readyState = 1; + + // Send global event + if ( fireGlobals ) { + globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] ); + } + + // If request was aborted inside ajaxSend, stop there + if ( completed ) { + return jqXHR; + } + + // Timeout + if ( s.async && s.timeout > 0 ) { + timeoutTimer = window.setTimeout( function() { + jqXHR.abort( "timeout" ); + }, s.timeout ); + } + + try { + completed = false; + transport.send( requestHeaders, done ); + } catch ( e ) { + + // Rethrow post-completion exceptions + if ( completed ) { + throw e; + } + + // Propagate others as results + done( -1, e ); + } + } + + // Callback for when everything is done + function done( status, nativeStatusText, responses, headers ) { + var isSuccess, success, error, response, modified, + statusText = nativeStatusText; + + // Ignore repeat invocations + if ( completed ) { + return; + } + + completed = true; + + // Clear timeout if it exists + if ( timeoutTimer ) { + window.clearTimeout( timeoutTimer ); + } + + // Dereference transport for early garbage collection + // (no matter how long the jqXHR object will be used) + transport = undefined; + + // Cache response headers + responseHeadersString = headers || ""; + + // Set readyState + jqXHR.readyState = status > 0 ? 4 : 0; + + // Determine if successful + isSuccess = status >= 200 && status < 300 || status === 304; + + // Get response data + if ( responses ) { + response = ajaxHandleResponses( s, jqXHR, responses ); + } + + // Use a noop converter for missing script + if ( !isSuccess && jQuery.inArray( "script", s.dataTypes ) > -1 ) { + s.converters[ "text script" ] = function() {}; + } + + // Convert no matter what (that way responseXXX fields are always set) + response = ajaxConvert( s, response, jqXHR, isSuccess ); + + // If successful, handle type chaining + if ( isSuccess ) { + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + modified = jqXHR.getResponseHeader( "Last-Modified" ); + if ( modified ) { + jQuery.lastModified[ cacheURL ] = modified; + } + modified = jqXHR.getResponseHeader( "etag" ); + if ( modified ) { + jQuery.etag[ cacheURL ] = modified; + } + } + + // if no content + if ( status === 204 || s.type === "HEAD" ) { + statusText = "nocontent"; + + // if not modified + } else if ( status === 304 ) { + statusText = "notmodified"; + + // If we have data, let's convert it + } else { + statusText = response.state; + success = response.data; + error = response.error; + isSuccess = !error; + } + } else { + + // Extract error from statusText and normalize for non-aborts + error = statusText; + if ( status || !statusText ) { + statusText = "error"; + if ( status < 0 ) { + status = 0; + } + } + } + + // Set data for the fake xhr object + jqXHR.status = status; + jqXHR.statusText = ( nativeStatusText || statusText ) + ""; + + // Success/Error + if ( isSuccess ) { + deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] ); + } else { + deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] ); + } + + // Status-dependent callbacks + jqXHR.statusCode( statusCode ); + statusCode = undefined; + + if ( fireGlobals ) { + globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError", + [ jqXHR, s, isSuccess ? success : error ] ); + } + + // Complete + completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] ); + + if ( fireGlobals ) { + globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] ); + + // Handle the global AJAX counter + if ( !( --jQuery.active ) ) { + jQuery.event.trigger( "ajaxStop" ); + } + } + } + + return jqXHR; + }, + + getJSON: function( url, data, callback ) { + return jQuery.get( url, data, callback, "json" ); + }, + + getScript: function( url, callback ) { + return jQuery.get( url, undefined, callback, "script" ); + } +} ); + +jQuery.each( [ "get", "post" ], function( _i, method ) { + jQuery[ method ] = function( url, data, callback, type ) { + + // Shift arguments if data argument was omitted + if ( isFunction( data ) ) { + type = type || callback; + callback = data; + data = undefined; + } + + // The url can be an options object (which then must have .url) + return jQuery.ajax( jQuery.extend( { + url: url, + type: method, + dataType: type, + data: data, + success: callback + }, jQuery.isPlainObject( url ) && url ) ); + }; +} ); + +jQuery.ajaxPrefilter( function( s ) { + var i; + for ( i in s.headers ) { + if ( i.toLowerCase() === "content-type" ) { + s.contentType = s.headers[ i ] || ""; + } + } +} ); + + +jQuery._evalUrl = function( url, options, doc ) { + return jQuery.ajax( { + url: url, + + // Make this explicit, since user can override this through ajaxSetup (#11264) + type: "GET", + dataType: "script", + cache: true, + async: false, + global: false, + + // Only evaluate the response if it is successful (gh-4126) + // dataFilter is not invoked for failure responses, so using it instead + // of the default converter is kludgy but it works. + converters: { + "text script": function() {} + }, + dataFilter: function( response ) { + jQuery.globalEval( response, options, doc ); + } + } ); +}; + + +jQuery.fn.extend( { + wrapAll: function( html ) { + var wrap; + + if ( this[ 0 ] ) { + if ( isFunction( html ) ) { + html = html.call( this[ 0 ] ); + } + + // The elements to wrap the target around + wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true ); + + if ( this[ 0 ].parentNode ) { + wrap.insertBefore( this[ 0 ] ); + } + + wrap.map( function() { + var elem = this; + + while ( elem.firstElementChild ) { + elem = elem.firstElementChild; + } + + return elem; + } ).append( this ); + } + + return this; + }, + + wrapInner: function( html ) { + if ( isFunction( html ) ) { + return this.each( function( i ) { + jQuery( this ).wrapInner( html.call( this, i ) ); + } ); + } + + return this.each( function() { + var self = jQuery( this ), + contents = self.contents(); + + if ( contents.length ) { + contents.wrapAll( html ); + + } else { + self.append( html ); + } + } ); + }, + + wrap: function( html ) { + var htmlIsFunction = isFunction( html ); + + return this.each( function( i ) { + jQuery( this ).wrapAll( htmlIsFunction ? html.call( this, i ) : html ); + } ); + }, + + unwrap: function( selector ) { + this.parent( selector ).not( "body" ).each( function() { + jQuery( this ).replaceWith( this.childNodes ); + } ); + return this; + } +} ); + + +jQuery.expr.pseudos.hidden = function( elem ) { + return !jQuery.expr.pseudos.visible( elem ); +}; +jQuery.expr.pseudos.visible = function( elem ) { + return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length ); +}; + + + + +jQuery.ajaxSettings.xhr = function() { + try { + return new window.XMLHttpRequest(); + } catch ( e ) {} +}; + +var xhrSuccessStatus = { + + // File protocol always yields status code 0, assume 200 + 0: 200, + + // Support: IE <=9 only + // #1450: sometimes IE returns 1223 when it should be 204 + 1223: 204 + }, + xhrSupported = jQuery.ajaxSettings.xhr(); + +support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported ); +support.ajax = xhrSupported = !!xhrSupported; + +jQuery.ajaxTransport( function( options ) { + var callback, errorCallback; + + // Cross domain only allowed if supported through XMLHttpRequest + if ( support.cors || xhrSupported && !options.crossDomain ) { + return { + send: function( headers, complete ) { + var i, + xhr = options.xhr(); + + xhr.open( + options.type, + options.url, + options.async, + options.username, + options.password + ); + + // Apply custom fields if provided + if ( options.xhrFields ) { + for ( i in options.xhrFields ) { + xhr[ i ] = options.xhrFields[ i ]; + } + } + + // Override mime type if needed + if ( options.mimeType && xhr.overrideMimeType ) { + xhr.overrideMimeType( options.mimeType ); + } + + // X-Requested-With header + // For cross-domain requests, seeing as conditions for a preflight are + // akin to a jigsaw puzzle, we simply never set it to be sure. + // (it can always be set on a per-request basis or even using ajaxSetup) + // For same-domain requests, won't change header if already provided. + if ( !options.crossDomain && !headers[ "X-Requested-With" ] ) { + headers[ "X-Requested-With" ] = "XMLHttpRequest"; + } + + // Set headers + for ( i in headers ) { + xhr.setRequestHeader( i, headers[ i ] ); + } + + // Callback + callback = function( type ) { + return function() { + if ( callback ) { + callback = errorCallback = xhr.onload = + xhr.onerror = xhr.onabort = xhr.ontimeout = + xhr.onreadystatechange = null; + + if ( type === "abort" ) { + xhr.abort(); + } else if ( type === "error" ) { + + // Support: IE <=9 only + // On a manual native abort, IE9 throws + // errors on any property access that is not readyState + if ( typeof xhr.status !== "number" ) { + complete( 0, "error" ); + } else { + complete( + + // File: protocol always yields status 0; see #8605, #14207 + xhr.status, + xhr.statusText + ); + } + } else { + complete( + xhrSuccessStatus[ xhr.status ] || xhr.status, + xhr.statusText, + + // Support: IE <=9 only + // IE9 has no XHR2 but throws on binary (trac-11426) + // For XHR2 non-text, let the caller handle it (gh-2498) + ( xhr.responseType || "text" ) !== "text" || + typeof xhr.responseText !== "string" ? + { binary: xhr.response } : + { text: xhr.responseText }, + xhr.getAllResponseHeaders() + ); + } + } + }; + }; + + // Listen to events + xhr.onload = callback(); + errorCallback = xhr.onerror = xhr.ontimeout = callback( "error" ); + + // Support: IE 9 only + // Use onreadystatechange to replace onabort + // to handle uncaught aborts + if ( xhr.onabort !== undefined ) { + xhr.onabort = errorCallback; + } else { + xhr.onreadystatechange = function() { + + // Check readyState before timeout as it changes + if ( xhr.readyState === 4 ) { + + // Allow onerror to be called first, + // but that will not handle a native abort + // Also, save errorCallback to a variable + // as xhr.onerror cannot be accessed + window.setTimeout( function() { + if ( callback ) { + errorCallback(); + } + } ); + } + }; + } + + // Create the abort callback + callback = callback( "abort" ); + + try { + + // Do send the request (this may raise an exception) + xhr.send( options.hasContent && options.data || null ); + } catch ( e ) { + + // #14683: Only rethrow if this hasn't been notified as an error yet + if ( callback ) { + throw e; + } + } + }, + + abort: function() { + if ( callback ) { + callback(); + } + } + }; + } +} ); + + + + +// Prevent auto-execution of scripts when no explicit dataType was provided (See gh-2432) +jQuery.ajaxPrefilter( function( s ) { + if ( s.crossDomain ) { + s.contents.script = false; + } +} ); + +// Install script dataType +jQuery.ajaxSetup( { + accepts: { + script: "text/javascript, application/javascript, " + + "application/ecmascript, application/x-ecmascript" + }, + contents: { + script: /\b(?:java|ecma)script\b/ + }, + converters: { + "text script": function( text ) { + jQuery.globalEval( text ); + return text; + } + } +} ); + +// Handle cache's special case and crossDomain +jQuery.ajaxPrefilter( "script", function( s ) { + if ( s.cache === undefined ) { + s.cache = false; + } + if ( s.crossDomain ) { + s.type = "GET"; + } +} ); + +// Bind script tag hack transport +jQuery.ajaxTransport( "script", function( s ) { + + // This transport only deals with cross domain or forced-by-attrs requests + if ( s.crossDomain || s.scriptAttrs ) { + var script, callback; + return { + send: function( _, complete ) { + script = jQuery( " + + + + + + + + + + + + + + +
+
+
+ + +
+ + +

Index

+ +
+ A + | B + | C + | D + | E + | F + | G + | H + | L + | M + | N + | O + | P + | R + | S + | V + | W + | X + +
+

A

+ + + +
+ +

B

+ + + +
+ +

C

+ + + +
+ +

D

+ + + +
+ +

E

+ + +
+ +

F

+ + + +
+ +

G

+ + + +
+ +

H

+ + +
+ +

L

+ + +
+ +

M

+ + + +
+ +

N

+ + + +
+ +

O

+ + + +
+ +

P

+ + + +
+ +

R

+ + + +
+ +

S

+ + + +
+ +

V

+ + + +
+ +

W

+ + + +
+ +

X

+ + +
+ + + +
+ +
+
+ +
+
+ + + + \ No newline at end of file diff --git a/cleverhans_v3.1.0/docs/index.html b/cleverhans_v3.1.0/docs/index.html new file mode 100644 index 000000000..1fc91786b --- /dev/null +++ b/cleverhans_v3.1.0/docs/index.html @@ -0,0 +1,109 @@ + + + + + + + + CleverHans Documentation — CleverHans documentation + + + + + + + + + + + + + + + + + + +
+
+
+ + +
+ +
+

CleverHans Documentation

+

This documentation is auto-generated from the docstrings of modules of the current master branch of cleverhans.

+

To get started, we recommend reading the github readme. Afterwards, you can learn more by looking at the following modules:

+ +
+
+

Indices and tables

+ +
+ + +
+ +
+
+ +
+
+ + + + \ No newline at end of file diff --git a/cleverhans_v3.1.0/docs/objects.inv b/cleverhans_v3.1.0/docs/objects.inv new file mode 100644 index 000000000..ce83d4005 Binary files /dev/null and b/cleverhans_v3.1.0/docs/objects.inv differ diff --git a/cleverhans_v3.1.0/docs/py-modindex.html b/cleverhans_v3.1.0/docs/py-modindex.html new file mode 100644 index 000000000..448755228 --- /dev/null +++ b/cleverhans_v3.1.0/docs/py-modindex.html @@ -0,0 +1,124 @@ + + + + + + + + Python Module Index — CleverHans documentation + + + + + + + + + + + + + + + + + + + + +
+
+
+ + +
+ + +

Python Module Index

+ +
+ c +
+ + + + + + + + + + + + + + + + +
 
+ c
+ cleverhans +
    + cleverhans.attacks +
    + cleverhans.devtools +
    + cleverhans.model +
+ + +
+ +
+
+ +
+
+ + + + \ No newline at end of file diff --git a/cleverhans_v3.1.0/docs/search.html b/cleverhans_v3.1.0/docs/search.html new file mode 100644 index 000000000..fae373875 --- /dev/null +++ b/cleverhans_v3.1.0/docs/search.html @@ -0,0 +1,104 @@ + + + + + + + + Search — CleverHans documentation + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + +
+ +

Search

+
+ +

+ Please activate JavaScript to enable the search + functionality. +

+
+

+ Searching for multiple words only shows matches that contain + all words. +

+
+ + + +
+ +
+ +
+ +
+ +
+
+ +
+
+ + + + \ No newline at end of file diff --git a/cleverhans_v3.1.0/docs/searchindex.js b/cleverhans_v3.1.0/docs/searchindex.js new file mode 100644 index 000000000..7eb741c88 --- /dev/null +++ b/cleverhans_v3.1.0/docs/searchindex.js @@ -0,0 +1 @@ +Search.setIndex({docnames:["README","index","source/attacks","source/devtools","source/future","source/model"],envversion:{"sphinx.domains.c":2,"sphinx.domains.changeset":1,"sphinx.domains.citation":1,"sphinx.domains.cpp":3,"sphinx.domains.index":1,"sphinx.domains.javascript":2,"sphinx.domains.math":2,"sphinx.domains.python":2,"sphinx.domains.rst":2,"sphinx.domains.std":1,"sphinx.ext.viewcode":1,sphinx:56},filenames:["README.md","index.md","source/attacks.md","source/devtools.md","source/future.md","source/model.md"],objects:{"cleverhans.attacks":{ABCMeta:[2,1,1,""],Attack:[2,1,1,""],BasicIterativeMethod:[2,1,1,""],BoundaryAttackPlusPlus:[2,3,1,""],CallableModelWrapper:[2,1,1,""],CarliniWagnerL2:[2,1,1,""],DeepFool:[2,1,1,""],ElasticNetMethod:[2,1,1,""],FastFeatureAdversaries:[2,1,1,""],FastGradientMethod:[2,1,1,""],HopSkipJumpAttack:[2,1,1,""],LBFGS:[2,1,1,""],MadryEtAl:[2,1,1,""],MaxConfidence:[2,1,1,""],Model:[2,1,1,""],MomentumIterativeMethod:[2,1,1,""],Noise:[2,1,1,""],ProjectedGradientDescent:[2,1,1,""],SPSA:[2,1,1,""],SaliencyMapMethod:[2,1,1,""],Semantic:[2,1,1,""],SparseL1Descent:[2,1,1,""],SpatialTransformationMethod:[2,1,1,""],VirtualAdversarialMethod:[2,1,1,""],clip_eta:[2,3,1,""],fgm:[2,3,1,""],optimize_linear:[2,3,1,""],projected_optimization:[2,3,1,""],reduce_max:[2,3,1,""],reduce_mean:[2,3,1,""],reduce_sum:[2,3,1,""],softmax_cross_entropy_with_logits:[2,3,1,""],vatm:[2,3,1,""],wrapper_warning:[2,3,1,""],wrapper_warning_logits:[2,3,1,""],xrange:[2,4,1,""]},"cleverhans.attacks.ABCMeta":{register:[2,2,1,""]},"cleverhans.attacks.Attack":{construct_graph:[2,2,1,""],construct_variables:[2,2,1,""],generate:[2,2,1,""],generate_np:[2,2,1,""],get_or_guess_labels:[2,2,1,""],parse_params:[2,2,1,""]},"cleverhans.attacks.CallableModelWrapper":{fprop:[2,2,1,""]},"cleverhans.attacks.CarliniWagnerL2":{generate:[2,2,1,""],parse_params:[2,2,1,""]},"cleverhans.attacks.DeepFool":{generate:[2,2,1,""],parse_params:[2,2,1,""]},"cleverhans.attacks.ElasticNetMethod":{generate:[2,2,1,""],parse_params:[2,2,1,""]},"cleverhans.attacks.FastFeatureAdversaries":{attack_single_step:[2,2,1,""],generate:[2,2,1,""],parse_params:[2,2,1,""]},"cleverhans.attacks.FastGradientMethod":{generate:[2,2,1,""],parse_params:[2,2,1,""]},"cleverhans.attacks.HopSkipJumpAttack":{generate:[2,2,1,""],generate_np:[2,2,1,""],parse_params:[2,2,1,""]},"cleverhans.attacks.LBFGS":{generate:[2,2,1,""],parse_params:[2,2,1,""]},"cleverhans.attacks.MaxConfidence":{attack:[2,2,1,""],attack_class:[2,2,1,""],generate:[2,2,1,""],parse_params:[2,2,1,""]},"cleverhans.attacks.Model":{O_FEATURES:[2,4,1,""],O_LOGITS:[2,4,1,""],O_PROBS:[2,4,1,""],fprop:[2,2,1,""],get_layer:[2,2,1,""],get_layer_names:[2,2,1,""],get_logits:[2,2,1,""],get_params:[2,2,1,""],get_predicted_class:[2,2,1,""],get_probs:[2,2,1,""],make_input_placeholder:[2,2,1,""],make_label_placeholder:[2,2,1,""],make_params:[2,2,1,""]},"cleverhans.attacks.MomentumIterativeMethod":{generate:[2,2,1,""],parse_params:[2,2,1,""]},"cleverhans.attacks.Noise":{generate:[2,2,1,""],parse_params:[2,2,1,""]},"cleverhans.attacks.ProjectedGradientDescent":{FGM_CLASS:[2,4,1,""],generate:[2,2,1,""],parse_params:[2,2,1,""]},"cleverhans.attacks.SPSA":{DEFAULT_DELTA:[2,4,1,""],DEFAULT_LEARNING_RATE:[2,4,1,""],DEFAULT_SPSA_ITERS:[2,4,1,""],DEFAULT_SPSA_SAMPLES:[2,4,1,""],generate:[2,2,1,""],generate_np:[2,2,1,""]},"cleverhans.attacks.SaliencyMapMethod":{generate:[2,2,1,""],parse_params:[2,2,1,""]},"cleverhans.attacks.Semantic":{generate:[2,2,1,""]},"cleverhans.attacks.SparseL1Descent":{generate:[2,2,1,""],parse_params:[2,2,1,""]},"cleverhans.attacks.SpatialTransformationMethod":{generate:[2,2,1,""],parse_params:[2,2,1,""]},"cleverhans.attacks.VirtualAdversarialMethod":{generate:[2,2,1,""],parse_params:[2,2,1,""]},"cleverhans.model":{CallableModelWrapper:[5,1,1,""],Model:[5,1,1,""],NoSuchLayerError:[5,5,1,""],wrapper_warning:[5,3,1,""],wrapper_warning_logits:[5,3,1,""]},"cleverhans.model.CallableModelWrapper":{fprop:[5,2,1,""]},"cleverhans.model.Model":{O_FEATURES:[5,4,1,""],O_LOGITS:[5,4,1,""],O_PROBS:[5,4,1,""],fprop:[5,2,1,""],get_layer:[5,2,1,""],get_layer_names:[5,2,1,""],get_logits:[5,2,1,""],get_params:[5,2,1,""],get_predicted_class:[5,2,1,""],get_probs:[5,2,1,""],make_input_placeholder:[5,2,1,""],make_label_placeholder:[5,2,1,""],make_params:[5,2,1,""]},cleverhans:{attacks:[2,0,0,"-"],devtools:[4,0,0,"-"],model:[5,0,0,"-"]}},objnames:{"0":["py","module","Python module"],"1":["py","class","Python class"],"2":["py","method","Python method"],"3":["py","function","Python function"],"4":["py","attribute","Python attribute"],"5":["py","exception","Python exception"]},objtypes:{"0":"py:module","1":"py:class","2":"py:method","3":"py:function","4":"py:attribute","5":"py:exception"},terms:{"001":2,"00420":2,"005":2,"00677":2,"01236":2,"02144":2,"02533":2,"04114":2,"04599":2,"04644":2,"05122":2,"05666":2,"06081":2,"06083":2,"06857":2,"07528":2,"100":2,"1000":2,"10000":2,"128":2,"13000":2,"1312":2,"1412":2,"1507":2,"1511":2,"1607":2,"1608":2,"1611":2,"1703":2,"1706":2,"1709":2,"1710":2,"1802":2,"1904":2,"2014":2,"2016":2,"2017":2,"2018":2,"2019":2,"6199":2,"6572":2,"abstract":[2,5],"boolean":2,"case":2,"class":[2,5],"default":[2,5],"final":[2,5],"float":2,"function":[2,5],"import":[0,2],"int":2,"new":2,"public":2,"return":[2,5],"static":0,"super":2,"true":[2,5],"try":2,For:2,The:[0,2,5],Use:2,Used:[2,5],Using:[2,5],_build:0,_project_perturb:2,abc:2,abcmeta:2,abl:0,abort:2,abort_earli:2,abs:2,acceler:2,access:[2,5],accident:2,accord:2,act:2,action:2,actual:[2,5],adam:2,add:0,addit:2,adversari:2,after:[2,5],afterward:1,against:2,aim:2,algorithm:2,alia:2,all:[0,2,5],allow:2,alon:2,along:2,alreadi:2,also:[2,5],angl:2,angle_max:2,angle_min:2,angular2html:0,ani:[2,5],appli:2,approach:2,approxim:2,arg:2,arg_typ:2,argmax_:2,argument:2,around:2,arrai:2,arxiv:2,assert:2,assign:2,assum:2,attack:[0,1,5],attack_class:2,attack_single_step:2,attain:2,attribut:2,auto:1,autodoc:0,automag:[2,5],automat:[2,5],avoid:2,axi:2,back:2,ball:2,base:[0,2,5],base_attack:2,basic:2,basiciterativemethod:2,batch:2,batch_siz:2,becaus:[2,5],been:[2,5],befor:2,being:2,below:2,beta:2,better:2,between:2,binari:2,binary_search_step:2,black:2,black_border_s:2,boneh:2,bool:2,border:2,bounc:2,bound:2,boundari:2,boundaryattackplusplu:2,box:2,branch:[0,1],build:0,built:2,bundl:2,cach:2,calcul:2,call:[2,5],callabl:[2,5],callable_fn:[2,5],callablemodelwrapp:[2,5],can:[0,1,2,5],cannot:0,carlini:2,carliniwagnerl2:2,center:2,chang:[2,5],check:[2,5],chen:2,child:2,choic:[0,2],choos:[0,2],chosen:2,classif:2,classifi:2,cleverhan:[0,2,5],click:0,clip:2,clip_eta:2,clip_grad:2,clip_max:2,clip_min:2,clockwis:2,code:[2,5],combin:2,come:2,compar:2,competit:2,complement:2,compon:2,compos:2,comput:[2,5],computation:2,concept:2,concret:2,confid:2,configur:2,consid:2,consist:2,constant:2,constraint:2,construct:2,construct_graph:2,construct_vari:2,constructor:[2,5],contain:2,content:[2,5],context:[2,5],control:2,conveni:0,converg:2,convolut:2,coordin:2,copi:0,core:[2,5],correct:2,cost:2,counter:2,cover:2,craft:2,creat:[0,2,5],criterion:2,cross:[2,5],current:[1,2],danger:[2,5],data:2,debug:2,decai:2,decay_factor:2,decis:2,decision_rul:2,decor:2,decreas:2,deepfool:2,default_delta:2,default_learning_r:2,default_rand_init:2,default_spsa_it:2,default_spsa_sampl:2,defens:2,defin:[2,5],delta:2,depend:[2,5],deprec:[2,5],descend:2,descent:2,describ:2,design:2,desir:2,detail:2,detect:[2,5],devic:[2,5],dict:[2,5],dictionari:[2,5],differ:[0,2],differenti:2,dim:2,dimens:2,direct:2,directli:[2,5],directori:0,discret:2,distanc:2,distort:2,doc:0,docsourc:0,docstr:1,document:[0,2],doe:[2,5],domain:2,done:2,dong:2,dot:2,down:0,dramat:2,drop:0,dtype:2,dtypestr:2,dure:2,dx_max:2,dx_min:2,dy_max:2,dy_min:2,each:2,earli:2,early_stop_loss_threshold:2,effect:2,effici:2,either:2,elast:2,elasticnetmethod:2,element:2,enabl:[0,2],encod:2,end:2,enforc:2,entropi:[2,5],environ:0,eps:2,eps_it:2,epsilon:2,estim:2,eta:2,evalu:2,even:[2,5],everi:2,exampl:[2,5],except:[0,5],exist:5,expens:2,explain:2,expos:[2,5],express:2,extend:2,extra:[2,5],factor:2,fail:[0,2],failur:2,fals:[2,5],fast:2,fast_gradient_method:2,faster:2,fastfeatureadversari:2,fastgradientmethod:2,featur:[2,5],fed:[2,5],feedabl:2,few:2,fewer:2,fgm:2,fgm_class:2,file:0,find:2,finit:2,first:[0,2],fix:2,float32:2,folder:0,follow:[0,1,2],forum:2,forward:[2,5],found:2,fprop:[2,5],free:2,from:[0,1,2,5],full:2,g_feat:2,gamma:2,gener:[0,1,2],generate_np:2,geometr:2,geometric_progress:2,get:[0,1,2],get_lay:[2,5],get_layer_nam:[2,5],get_logit:[2,5],get_or_guess_label:2,get_param:[2,5],get_predicted_class:[2,5],get_prob:[2,5],github:[0,1],give:2,given:2,good:2,goodfellow:2,grad:2,grad_spars:2,gradient:[2,5],graph:2,greatli:2,grid:2,grid_search:2,guarante:2,guid:2,h1g0pia9tq:2,hack:0,half:2,handl:2,hard:2,has:[2,5],hash_kei:2,have:[0,2,5],help:2,helper:2,here:2,hidden:[2,5],high:2,higher:2,hold:2,hood:[2,5],hopskipjumpattack:2,hot:2,how:2,hparam:[2,5],html:0,http:2,hurt:2,hyperparamet:2,idea:2,identifi:2,ignor:2,imag:2,image_target:2,imagenet:2,implement:[2,5],importantli:2,improv:2,incept:[2,5],includ:2,incorrect:[2,5],increas:2,index:[0,1,2],indic:0,inf:2,infer:2,infin:2,info:2,initi:2,initial_const:2,initial_num_ev:2,input:[2,5],input_imag:2,insert:2,instal:0,instead:2,intend:2,intention:2,interfac:[2,5],intern:2,interv:2,introduc:2,invok:[2,5],is_debug:2,is_target:2,issu:[2,5],issubclass:2,iter:2,itself:2,jacobian:2,jekyl:0,jordan:2,just:[2,5],keep:[0,2],kei:2,kera:[2,5],keras_tensor:0,keyword:2,known:2,kurakin:2,kwarg:[2,5],label:[2,5],larg:2,larger:2,largest:2,later:[2,5],layer:[2,5],lbfg:2,leak:2,learn:[1,2],learning_r:2,least:2,leav:2,length:2,less:2,level:2,librari:0,like:2,linear:2,linf:2,link:2,list:[0,2,5],local:[0,2],log:0,logit:[2,5],look:1,loop:2,loss:[2,5],loss_fn:2,lower:2,made:2,madri:2,madryet:2,mai:[2,5],make:[0,2,5],make_input_placehold:[2,5],make_label_placehold:[2,5],make_param:[2,5],makefil:0,manag:[2,5],mani:2,map:[2,5],mask:[2,5],master:[0,1],max_it:2,max_iter:2,max_num_ev:2,max_val:2,maxconfid:2,maximum:2,mean:[2,5],measur:2,memori:2,metaclass:2,method:[2,5],mimic:2,minim:2,minimum:2,misclassif:2,mix:2,miyato:2,model:[1,2],moder:2,modifi:2,modul:[0,1],momentum:2,momentumiterativemethod:2,more:[1,2],most:2,move:[0,2],mro:2,much:2,multipl:[2,5],must:2,n_angl:2,n_dx:2,n_dy:2,n_sampl:2,name:[0,2,5],namespac:2,nb_candid:2,nb_class:[2,5],nb_iter:2,need:[2,5],needs_dummy_fprop:[2,5],neg:2,negat:2,neither:2,net:2,network:[2,5],neural:[2,5],nip:2,nois:2,nojekyl:0,non:2,none:[2,5],nor:2,norm:2,normal:2,nosuchlayererror:5,note:[0,2,5],num_iter:2,num_step:2,number:2,numer:2,numpi:2,o_featur:[2,5],o_logit:[2,5],o_prob:[2,5],object:[2,5],off:2,often:2,one:2,onli:2,openreview:2,oper:2,optim:2,optimal_perturb:2,optimize_linear:2,option:[0,2,5],ord:2,order:2,org:[0,2],orient:2,origin:2,other:2,otherwis:[0,2],our:[0,2],out:2,outperform:2,output:[0,2,5],output_lay:[2,5],over:2,overriden:2,overshoot:2,own:2,packag:[2,5],page:[0,1],paper:2,papernot:2,param:[2,5],paramet:[2,5],pars:2,parse_param:2,part:[2,5],particularli:2,pass:[2,5],pdf:2,percent:2,percentag:2,percentil:2,perform:2,perturb:2,pick:2,pip:0,pixel:2,place:[2,5],placehold:[2,5],pleas:0,point:2,posit:2,possibl:2,pre:[2,5],predict:[2,5],prepar:0,prevent:2,preview:0,previou:2,print:2,prob:[2,5],probabl:[2,5],procedur:2,process:2,produc:[2,5],progress:2,project:2,project_perturb:2,projected_gradient_desc:2,projected_optim:2,projectedgradientdesc:2,propag:[2,5],properti:2,propos:2,provid:[2,5],publish:0,purturb:2,put:[2,5],py_func:2,queri:2,radiu:2,rais:[0,5],rand_init:2,rand_init_ep:2,rand_minmax:2,random:2,rang:2,rate:2,rather:2,ratio:2,reach:2,read:[1,2],readm:1,realiz:[2,5],reason:2,recip:2,recommend:[1,2],reduce_max:2,reduce_mean:2,reduce_sum:2,refer:[2,5],region:2,regist:2,reject:2,rel:2,relat:5,remov:[2,5],repo:0,repositori:0,repres:[2,5],represent:[2,5],request:5,requir:2,reshap:[2,5],resolut:2,respect:[2,5],result:[2,5],retain:2,robust:2,root:0,rotat:2,run:[0,2],runtim:2,sabour:2,salienc:2,saliencymapmethod:2,same:[2,5],sampl:2,saniti:2,sanity_check:2,save:2,scalar:2,scale:2,scope:[2,5],scroll:0,search:[1,2],section:0,see:[0,2,5],seed:2,select:2,self:[2,5],semant:2,sentinel:2,separ:0,serv:0,sess:2,session:2,set:[0,2,5],sever:[2,5],shape:2,should:[2,5],show:2,side:2,sign:2,signatur:2,silent:[2,5],similar:2,simpl:2,simplifi:[2,5],simultan:2,singl:[2,5],site:0,size:2,slower:2,smaller:2,smooth:2,softmax:[2,5],softmax_cross_entropy_with_logit:2,softmax_cross_entropy_with_logits_v2:2,solv:2,some:2,soon:2,sourc:[0,2,5],space:2,spars:2,sparsel1desc:2,sparsiti:2,spatial:2,spatialtransformationmethod:2,special:2,specif:[0,2,5],specifi:2,speed:2,sphinx:0,spsa:[0,2],spsa_it:2,spsa_sampl:2,sqrt:2,start:[1,2],steepest:2,step:2,stepsiz:2,stepsize_search:2,still:[0,2,5],stop:2,store:2,str:[2,5],strang:2,stronger:2,strongli:2,structur:2,stuck:2,sub:2,subclass:2,success:2,suppli:[2,5],sure:0,symbol:[2,5],symbolic_impl:2,tab:0,take:2,target:2,target_i:2,tensor:[2,5],tensoradam:2,tensorflow:2,tensorflow_addon:0,tensoroptim:2,term:2,termin:2,test:2,tfa:0,than:2,thei:[2,5],them:[0,2,5],therefor:2,theta:2,thi:[0,1,2,5],those:2,three:2,threshold:2,through:2,thu:[0,2],time:[2,5],too:2,total:2,trade:2,tradeoff:2,train:2,tramer:2,transfer:2,transform:2,translat:2,treat:[2,5],tri:2,tricki:2,true_i:2,tune:2,turn:[2,5],type:2,typic:2,uesato:2,unabl:2,uncom:0,under:[0,2,5],underli:2,uniformli:2,uniqu:2,unit:2,univers:2,unless:2,unnorm:2,unrel:2,untarget:2,until:2,updat:2,usag:2,use:[2,5],used:2,useful:2,user:[0,2,5],uses:2,using:[0,2],valid:[2,5],valu:[2,5],valueerror:5,vanish:2,variabl:[2,5],variant:2,variat:2,vastli:2,vatm:2,vector:2,verbos:2,version:2,via:2,virtual:[0,2],virtualadversarialmethod:2,virutalen:0,wagner:2,wai:[2,5],wainwright:2,want:0,warn:[0,2,5],weak:2,websit:0,were:2,when:[0,2,5],where:[0,2],whether:[0,2,5],which:2,while_loop:2,white:2,without:2,won:2,work:2,wors:[2,5],would:[2,5],wrap:[2,5],wrapper:[2,5],wrapper_warn:[2,5],wrapper_warning_logit:[2,5],write:2,x_t:2,x_val:2,xrang:2,y_target:2,yield:2,you:[0,1,2],your:[0,2]},titles:["<no title>","CleverHans Documentation","attacks module","devtools module","future module","model module"],titleterms:{attack:2,cleverhan:1,devtool:3,document:1,futur:4,indic:1,model:5,modul:[2,3,4,5],tabl:1}}) \ No newline at end of file diff --git a/cleverhans_v3.1.0/docs/source/attacks.html b/cleverhans_v3.1.0/docs/source/attacks.html new file mode 100644 index 000000000..bcc1a10a0 --- /dev/null +++ b/cleverhans_v3.1.0/docs/source/attacks.html @@ -0,0 +1,1856 @@ + + + + + + + + attacks module — CleverHans documentation + + + + + + + + + + + + + + + + + + + +
+
+
+ + +
+ +
+

attacks module

+

The Attack class, providing a universal abstract interface describing attacks, and many implementations of it.

+
+
+class cleverhans.attacks.ABCMeta(name, bases, namespace, **kwargs)[source]
+

Bases: type

+

Metaclass for defining Abstract Base Classes (ABCs).

+

Use this metaclass to create an ABC. An ABC can be subclassed +directly, and then acts as a mix-in class. You can also register +unrelated concrete classes (even built-in classes) and unrelated +ABCs as ‘virtual subclasses’ – these and their descendants will +be considered subclasses of the registering ABC by the built-in +issubclass() function, but the registering ABC won’t show up in +their MRO (Method Resolution Order) nor will method +implementations defined by the registering ABC be callable (not +even via super()).

+
+
+register(subclass)[source]
+

Register a virtual subclass of an ABC.

+

Returns the subclass, to allow usage as a class decorator.

+
+ +
+ +
+
+class cleverhans.attacks.Attack(model, sess=None, dtypestr='float32', **kwargs)[source]
+

Bases: object

+

Abstract base class for all attack classes.

+
+
+construct_graph(fixed, feedable, x_val, hash_key)[source]
+

Construct the graph required to run the attack through generate_np.

+
+
Parameters
+
    +
  • fixed – Structural elements that require defining a new graph.

  • +
  • feedable – Arguments that can be fed to the same graph when +they take different values.

  • +
  • x_val – symbolic adversarial example

  • +
  • hash_key – the key used to store this graph in our cache

  • +
+
+
+
+ +
+
+construct_variables(kwargs)[source]
+

Construct the inputs to the attack graph to be used by generate_np.

+
+
Parameters
+

kwargs – Keyword arguments to generate_np.

+
+
Returns
+

Structural arguments +Feedable arguments +Output of arg_type describing feedable arguments +A unique key

+
+
+
+ +
+
+generate(x, **kwargs)[source]
+

Generate the attack’s symbolic graph for adversarial examples. This +method should be overriden in any child class that implements an +attack that is expressable symbolically. Otherwise, it will wrap the +numerical implementation as a symbolic operator.

+
+
Parameters
+
    +
  • x – The model’s symbolic inputs.

  • +
  • **kwargs

    optional parameters used by child classes. +Each child class defines additional parameters as needed. +Child classes that use the following concepts should use the following +names:

    +
    +

    clip_min: minimum feature value +clip_max: maximum feature value +eps: size of norm constraint on adversarial perturbation +ord: order of norm constraint +nb_iter: number of iterations +eps_iter: size of norm constraint on iteration +y_target: if specified, the attack is targeted. +y: Do not specify if y_target is specified.

    +
    +

    If specified, the attack is untargeted, aims to make the output +class not be y. +If neither y_target nor y is specified, y is inferred by having +the model classify the input.

    +
    +
    +

    For other concepts, it’s generally a good idea to read other classes +and check for name consistency.

    +

  • +
+
+
Returns
+

A symbolic representation of the adversarial examples.

+
+
+
+ +
+
+generate_np(x_val, **kwargs)[source]
+

Generate adversarial examples and return them as a NumPy array. +Sub-classes should not implement this method unless they must +perform special handling of arguments.

+
+
Parameters
+
    +
  • x_val – A NumPy array with the original inputs.

  • +
  • **kwargs

    optional parameters used by child classes.

    +

  • +
+
+
Returns
+

A NumPy array holding the adversarial examples.

+
+
+
+ +
+
+get_or_guess_labels(x, kwargs)[source]
+

Get the label to use in generating an adversarial example for x. +The kwargs are fed directly from the kwargs of the attack. +If ‘y’ is in kwargs, then assume it’s an untargeted attack and +use that as the label. +If ‘y_target’ is in kwargs and is not none, then assume it’s a +targeted attack and use that as the label. +Otherwise, use the model’s prediction as the label and perform an +untargeted attack.

+
+ +
+
+parse_params(params=None)[source]
+

Take in a dictionary of parameters and applies attack-specific checks +before saving them as attributes.

+
+
Parameters
+

params – a dictionary of attack-specific parameters

+
+
Returns
+

True when parsing was successful

+
+
+
+ +
+ +
+
+class cleverhans.attacks.BasicIterativeMethod(model, sess=None, dtypestr='float32', **kwargs)[source]
+

Bases: cleverhans.attacks.projected_gradient_descent.ProjectedGradientDescent

+

The BasicIterativeMethod attack.

+
+ +
+
+cleverhans.attacks.BoundaryAttackPlusPlus(model, sess, dtypestr='float32', **kwargs)[source]
+

A previous name used for HopSkipJumpAttack.

+
+ +
+
+class cleverhans.attacks.CallableModelWrapper(callable_fn, output_layer)[source]
+

Bases: cleverhans.model.Model

+

A wrapper that turns a callable into a valid Model

+
+
+fprop(x, **kwargs)[source]
+

Forward propagation to compute the model outputs. +:param x: A symbolic representation of the network input +:return: A dictionary mapping layer names to the symbolic

+
+

representation of their output.

+
+
+ +
+ +
+
+class cleverhans.attacks.CarliniWagnerL2(model, sess, dtypestr='float32', **kwargs)[source]
+

Bases: cleverhans.attacks.attack.Attack

+

This attack was originally proposed by Carlini and Wagner. It is an +iterative attack that finds adversarial examples on many defenses that +are robust to other attacks. +Paper link: https://arxiv.org/abs/1608.04644

+

At a high level, this attack is an iterative attack using Adam and +a specially-chosen loss function to find adversarial examples with +lower distortion than other attacks. This comes at the cost of speed, +as this attack is often much slower than others.

+
+
Parameters
+
    +
  • model – cleverhans.model.Model

  • +
  • sess – tf.Session

  • +
  • dtypestr – dtype of the data

  • +
  • kwargs – passed through to super constructor

  • +
+
+
+
+
+generate(x, **kwargs)[source]
+

Return a tensor that constructs adversarial examples for the given +input. Generate uses tf.py_func in order to operate over tensors.

+
+
Parameters
+
    +
  • x – A tensor with the inputs.

  • +
  • kwargs – See parse_params

  • +
+
+
+
+ +
+
+parse_params(y=None, y_target=None, batch_size=1, confidence=0, learning_rate=0.005, binary_search_steps=5, max_iterations=1000, abort_early=True, initial_const=0.01, clip_min=0, clip_max=1)[source]
+
+
Parameters
+
    +
  • y – (optional) A tensor with the true labels for an untargeted +attack. If None (and y_target is None) then use the +original labels the classifier assigns.

  • +
  • y_target – (optional) A tensor with the target labels for a +targeted attack.

  • +
  • confidence – Confidence of adversarial examples: higher produces +examples with larger l2 distortion, but more +strongly classified as adversarial.

  • +
  • batch_size – Number of attacks to run simultaneously.

  • +
  • learning_rate – The learning rate for the attack algorithm. +Smaller values produce better results but are +slower to converge.

  • +
  • binary_search_steps – The number of times we perform binary +search to find the optimal tradeoff- +constant between norm of the purturbation +and confidence of the classification.

  • +
  • max_iterations – The maximum number of iterations. Setting this +to a larger value will produce lower distortion +results. Using only a few iterations requires +a larger learning rate, and will produce larger +distortion results.

  • +
  • abort_early – If true, allows early aborts if gradient descent +is unable to make progress (i.e., gets stuck in +a local minimum).

  • +
  • initial_const – The initial tradeoff-constant to use to tune the +relative importance of size of the perturbation +and confidence of classification. +If binary_search_steps is large, the initial +constant is not important. A smaller value of +this constant gives lower distortion results.

  • +
  • clip_min – (optional float) Minimum input component value

  • +
  • clip_max – (optional float) Maximum input component value

  • +
+
+
+
+ +
+ +
+
+class cleverhans.attacks.DeepFool(model, sess, dtypestr='float32', **kwargs)[source]
+

Bases: cleverhans.attacks.attack.Attack

+

DeepFool is an untargeted & iterative attack which is based on an +iterative linearization of the classifier. The implementation here +is w.r.t. the L2 norm. +Paper link: “https://arxiv.org/pdf/1511.04599.pdf

+
+
Parameters
+
    +
  • model – cleverhans.model.Model

  • +
  • sess – tf.Session

  • +
  • dtypestr – dtype of the data

  • +
  • kwargs – passed through to super constructor

  • +
+
+
+
+
+generate(x, **kwargs)[source]
+

Generate symbolic graph for adversarial examples and return.

+
+
Parameters
+
    +
  • x – The model’s symbolic inputs.

  • +
  • kwargs – See parse_params

  • +
+
+
+
+ +
+
+parse_params(nb_candidate=10, overshoot=0.02, max_iter=50, clip_min=0.0, clip_max=1.0, **kwargs)[source]
+
+
Parameters
+
    +
  • nb_candidate – The number of classes to test against, i.e., +deepfool only consider nb_candidate classes when +attacking(thus accelerate speed). The nb_candidate +classes are chosen according to the prediction +confidence during implementation.

  • +
  • overshoot – A termination criterion to prevent vanishing updates

  • +
  • max_iter – Maximum number of iteration for deepfool

  • +
  • clip_min – Minimum component value for clipping

  • +
  • clip_max – Maximum component value for clipping

  • +
+
+
+
+ +
+ +
+
+class cleverhans.attacks.ElasticNetMethod(model, sess, dtypestr='float32', **kwargs)[source]
+

Bases: cleverhans.attacks.attack.Attack

+

This attack features L1-oriented adversarial examples and includes +the C&W L2 attack as a special case (when beta is set to 0). +Adversarial examples attain similar performance to those +generated by the C&W L2 attack in the white-box case, +and more importantly, have improved transferability properties +and complement adversarial training. +Paper link: https://arxiv.org/abs/1709.04114

+
+
Parameters
+
    +
  • model – cleverhans.model.Model

  • +
  • sess – tf.Session

  • +
  • dtypestr – dtype of the data

  • +
  • kwargs – passed through to super constructor

  • +
+
+
+
+
+generate(x, **kwargs)[source]
+

Return a tensor that constructs adversarial examples for the given +input. Generate uses tf.py_func in order to operate over tensors.

+
+
Parameters
+
    +
  • x – (required) A tensor with the inputs.

  • +
  • kwargs – See parse_params

  • +
+
+
+
+ +
+
+parse_params(y=None, y_target=None, beta=0.01, decision_rule='EN', batch_size=1, confidence=0, learning_rate=0.01, binary_search_steps=9, max_iterations=1000, abort_early=False, initial_const=0.001, clip_min=0, clip_max=1)[source]
+
+
Parameters
+
    +
  • y – (optional) A tensor with the true labels for an untargeted +attack. If None (and y_target is None) then use the +original labels the classifier assigns.

  • +
  • y_target – (optional) A tensor with the target labels for a +targeted attack.

  • +
  • beta – Trades off L2 distortion with L1 distortion: higher +produces examples with lower L1 distortion, at the +cost of higher L2 (and typically Linf) distortion

  • +
  • decision_rule – EN or L1. Select final adversarial example from +all successful examples based on the least +elastic-net or L1 distortion criterion.

  • +
  • confidence – Confidence of adversarial examples: higher produces +examples with larger l2 distortion, but more +strongly classified as adversarial.

  • +
  • batch_size – Number of attacks to run simultaneously.

  • +
  • learning_rate – The learning rate for the attack algorithm. +Smaller values produce better results but are +slower to converge.

  • +
  • binary_search_steps – The number of times we perform binary +search to find the optimal tradeoff- +constant between norm of the perturbation +and confidence of the classification. Set +‘initial_const’ to a large value and fix +this param to 1 for speed.

  • +
  • max_iterations – The maximum number of iterations. Setting this +to a larger value will produce lower distortion +results. Using only a few iterations requires +a larger learning rate, and will produce larger +distortion results.

  • +
  • abort_early – If true, allows early abort when the total +loss starts to increase (greatly speeds up attack, +but hurts performance, particularly on ImageNet)

  • +
  • initial_const – The initial tradeoff-constant to use to tune the +relative importance of size of the perturbation +and confidence of classification. +If binary_search_steps is large, the initial +constant is not important. A smaller value of +this constant gives lower distortion results. +For computational efficiency, fix +binary_search_steps to 1 and set this param +to a large value.

  • +
  • clip_min – (optional float) Minimum input component value

  • +
  • clip_max – (optional float) Maximum input component value

  • +
+
+
+
+ +
+ +
+
+class cleverhans.attacks.FastFeatureAdversaries(model, sess=None, dtypestr='float32', **kwargs)[source]
+

Bases: cleverhans.attacks.attack.Attack

+

This is a fast implementation of “Feature Adversaries”, an attack +against a target internal representation of a model. +“Feature adversaries” were originally introduced in (Sabour et al. 2016), +where the optimization was done using LBFGS. +Paper link: https://arxiv.org/abs/1511.05122

+

This implementation is similar to “Basic Iterative Method” +(Kurakin et al. 2016) but applied to the internal representations.

+
+
Parameters
+
    +
  • model – cleverhans.model.Model

  • +
  • sess – optional tf.Session

  • +
  • dtypestr – dtype of the data

  • +
  • kwargs – passed through to super constructor

  • +
+
+
+
+
+attack_single_step(x, eta, g_feat)[source]
+

TensorFlow implementation of the Fast Feature Gradient. This is a +single step attack similar to Fast Gradient Method that attacks an +internal representation.

+
+
Parameters
+
    +
  • x – the input placeholder

  • +
  • eta – A tensor the same shape as x that holds the perturbation.

  • +
  • g_feat – model’s internal tensor for guide

  • +
+
+
Returns
+

a tensor for the adversarial example

+
+
+
+ +
+
+generate(x, g, **kwargs)[source]
+

Generate symbolic graph for adversarial examples and return.

+
+
Parameters
+
    +
  • x – The model’s symbolic inputs.

  • +
  • g – The target value of the symbolic representation

  • +
  • kwargs – See parse_params

  • +
+
+
+
+ +
+
+parse_params(layer=None, eps=0.3, eps_iter=0.05, nb_iter=10, ord=inf, clip_min=None, clip_max=None, **kwargs)[source]
+

Take in a dictionary of parameters and applies attack-specific checks +before saving them as attributes.

+

Attack-specific parameters:

+
+
Parameters
+
    +
  • layer – (required str) name of the layer to target.

  • +
  • eps – (optional float) maximum distortion of adversarial example +compared to original input

  • +
  • eps_iter – (optional float) step size for each attack iteration

  • +
  • nb_iter – (optional int) Number of attack iterations.

  • +
  • ord – (optional) Order of the norm (mimics Numpy). +Possible values: np.inf, 1 or 2.

  • +
  • clip_min – (optional float) Minimum input component value

  • +
  • clip_max – (optional float) Maximum input component value

  • +
+
+
+
+ +
+ +
+
+class cleverhans.attacks.FastGradientMethod(model, sess=None, dtypestr='float32', **kwargs)[source]
+

Bases: cleverhans.attacks.attack.Attack

+

This attack was originally implemented by Goodfellow et al. (2014) with the +infinity norm (and is known as the “Fast Gradient Sign Method”). This +implementation extends the attack to other norms, and is therefore called +the Fast Gradient Method. +Paper link: https://arxiv.org/abs/1412.6572

+
+
Parameters
+
    +
  • model – cleverhans.model.Model

  • +
  • sess – optional tf.Session

  • +
  • dtypestr – dtype of the data

  • +
  • kwargs – passed through to super constructor

  • +
+
+
+
+
+generate(x, **kwargs)[source]
+

Returns the graph for Fast Gradient Method adversarial examples.

+
+
Parameters
+
    +
  • x – The model’s symbolic inputs.

  • +
  • kwargs – See parse_params

  • +
+
+
+
+ +
+
+parse_params(eps=0.3, ord=inf, loss_fn=<function softmax_cross_entropy_with_logits>, y=None, y_target=None, clip_min=None, clip_max=None, clip_grad=False, sanity_checks=True, **kwargs)[source]
+

Take in a dictionary of parameters and applies attack-specific checks +before saving them as attributes.

+

Attack-specific parameters:

+
+
Parameters
+
    +
  • eps – (optional float) attack step size (input variation)

  • +
  • ord – (optional) Order of the norm (mimics NumPy). +Possible values: np.inf, 1 or 2.

  • +
  • loss_fn – Loss function that takes (labels, logits) as arguments and returns loss

  • +
  • y – (optional) A tensor with the true labels. Only provide +this parameter if you’d like to use true labels when crafting +adversarial samples. Otherwise, model predictions are used as +labels to avoid the “label leaking” effect (explained in this +paper: https://arxiv.org/abs/1611.01236). Default is None. +Labels should be one-hot-encoded.

  • +
  • y_target – (optional) A tensor with the labels to target. Leave +y_target=None if y is also set. Labels should be +one-hot-encoded.

  • +
  • clip_min – (optional float) Minimum input component value

  • +
  • clip_max – (optional float) Maximum input component value

  • +
  • clip_grad – (optional bool) Ignore gradient components +at positions where the input is already at the boundary +of the domain, and the update step will get clipped out.

  • +
  • sanity_checks – bool, if True, include asserts +(Turn them off to use less runtime / memory or for unit tests that +intentionally pass strange input)

  • +
+
+
+
+ +
+ +
+
+class cleverhans.attacks.HopSkipJumpAttack(model, sess, dtypestr='float32', **kwargs)[source]
+

Bases: cleverhans.attacks.attack.Attack

+

HopSkipJumpAttack was originally proposed by Chen, Jordan and Wainwright. +It is a decision-based attack that requires access to output +labels of a model alone. +Paper link: https://arxiv.org/abs/1904.02144 +At a high level, this attack is an iterative attack composed of three +steps: Binary search to approach the boundary; gradient estimation; +stepsize search. HopSkipJumpAttack requires fewer model queries than +Boundary Attack which was based on rejective sampling. +:param model: cleverhans.model.Model +:param sess: tf.Session +:param dtypestr: dtype of the data +:param kwargs: passed through to super constructor. +see parse_params for details.

+
+
+generate(x, **kwargs)[source]
+

Return a tensor that constructs adversarial examples for the given +input. Generate uses tf.py_func in order to operate over tensors. +:param x: A tensor with the inputs. +:param kwargs: See parse_params

+
+ +
+
+generate_np(x, **kwargs)[source]
+

Generate adversarial images in a for loop. +:param y: An array of shape (n, nb_classes) for true labels. +:param y_target: An array of shape (n, nb_classes) for target labels. +Required for targeted attack. +:param image_target: An array of shape (n, **image shape) for initial +target images. Required for targeted attack.

+

See parse_params for other kwargs.

+
+ +
+
+parse_params(y_target=None, image_target=None, initial_num_evals=100, max_num_evals=10000, stepsize_search='geometric_progression', num_iterations=64, gamma=1.0, constraint='l2', batch_size=128, verbose=True, clip_min=0, clip_max=1)[source]
+
+
Parameters
+
    +
  • y – A tensor of shape (1, nb_classes) for true labels.

  • +
  • y_target – A tensor of shape (1, nb_classes) for target labels.

  • +
+
+
+

Required for targeted attack. +:param image_target: A tensor of shape (1, **image shape) for initial +target images. Required for targeted attack. +:param initial_num_evals: initial number of evaluations for

+
+

gradient estimation.

+
+
+
Parameters
+
    +
  • max_num_evals – maximum number of evaluations for gradient estimation.

  • +
  • stepsize_search

    How to search for stepsize; choices are +‘geometric_progression’, ‘grid_search’. +‘geometric progression’ initializes the stepsize

    +
    +

    by ||x_t - x||_p / sqrt(iteration), and keep +decreasing by half until reaching the target +side of the boundary. ‘grid_search’ chooses the +optimal epsilon over a grid, in the scale of +||x_t - x||_p.

    +
    +

  • +
  • num_iterations – The number of iterations.

  • +
  • gamma – The binary search threshold theta is gamma / d^{3/2} for +l2 attack and gamma / d^2 for linf attack.

  • +
  • constraint – The distance to optimize; choices are ‘l2’, ‘linf’.

  • +
  • batch_size – batch_size for model prediction.

  • +
  • verbose – (boolean) Whether distance at each step is printed.

  • +
  • clip_min – (optional float) Minimum input component value

  • +
  • clip_max – (optional float) Maximum input component value

  • +
+
+
+
+ +
+ +
+
+class cleverhans.attacks.LBFGS(model, sess, dtypestr='float32', **kwargs)[source]
+

Bases: cleverhans.attacks.attack.Attack

+

LBFGS is the first adversarial attack for convolutional neural networks, +and is a target & iterative attack. +Paper link: “https://arxiv.org/pdf/1312.6199.pdf” +:param model: cleverhans.model.Model +:param sess: tf.Session +:param dtypestr: dtype of the data +:param kwargs: passed through to super constructor

+
+
+generate(x, **kwargs)[source]
+

Return a tensor that constructs adversarial examples for the given +input. Generate uses tf.py_func in order to operate over tensors. +:param x: (required) A tensor with the inputs. +:param kwargs: See parse_params

+
+ +
+
+parse_params(y_target=None, batch_size=1, binary_search_steps=5, max_iterations=1000, initial_const=0.01, clip_min=0, clip_max=1)[source]
+
+
Parameters
+
    +
  • y_target – (optional) A tensor with the one-hot target labels.

  • +
  • batch_size – The number of inputs to include in a batch and +process simultaneously.

  • +
  • binary_search_steps – The number of times we perform binary +search to find the optimal tradeoff- +constant between norm of the purturbation +and cross-entropy loss of classification.

  • +
  • max_iterations – The maximum number of iterations.

  • +
  • initial_const – The initial tradeoff-constant to use to tune the +relative importance of size of the perturbation +and cross-entropy loss of the classification.

  • +
  • clip_min – (optional float) Minimum input component value

  • +
  • clip_max – (optional float) Maximum input component value

  • +
+
+
+
+ +
+ +
+
+class cleverhans.attacks.MadryEtAl(model, sess=None, dtypestr='float32', **kwargs)[source]
+

Bases: cleverhans.attacks.projected_gradient_descent.ProjectedGradientDescent

+

The attack from Madry et al 2017

+
+ +
+
+class cleverhans.attacks.MaxConfidence(model, sess=None, base_attacker=None)[source]
+

Bases: cleverhans.attacks.attack.Attack

+

The MaxConfidence attack.

+

An attack designed for use against models that use confidence thresholding +as a defense. +If the underlying optimizer is optimal, this attack procedure gives the +optimal failure rate for every confidence threshold t > 0.5.

+

Publication: https://openreview.net/forum?id=H1g0piA9tQ

+
+
Parameters
+
    +
  • model – cleverhans.model.Model

  • +
  • sess – optional tf.session.Session

  • +
  • base_attacker – cleverhans.attacks.Attack

  • +
+
+
+
+
+attack(x, true_y)[source]
+

Runs the untargeted attack. +:param x: The input +:param true_y: The correct label for x. This attack aims to produce misclassification.

+
+ +
+
+attack_class(x, target_y)[source]
+

Run the attack on a specific target class. +:param x: tf Tensor. The input example. +:param target_y: tf Tensor. The attacker’s desired target class. +Returns:

+
+

A targeted adversarial example, intended to be classified as the target class.

+
+
+ +
+
+generate(x, **kwargs)[source]
+

Generate symbolic graph for adversarial examples and return.

+
+
Parameters
+
    +
  • x – The model’s symbolic inputs.

  • +
  • kwargs – Keyword arguments for the base attacker

  • +
+
+
+
+ +
+
+parse_params(y=None, nb_classes=10, **kwargs)[source]
+

Take in a dictionary of parameters and applies attack-specific checks +before saving them as attributes.

+
+
Parameters
+

params – a dictionary of attack-specific parameters

+
+
Returns
+

True when parsing was successful

+
+
+
+ +
+ +
+
+class cleverhans.attacks.Model(scope=None, nb_classes=None, hparams=None, needs_dummy_fprop=False)[source]
+

Bases: object

+

An abstract interface for model wrappers that exposes model symbols +needed for making an attack. This abstraction removes the dependency on +any specific neural network package (e.g. Keras) from the core +code of CleverHans. It can also simplify exposing the hidden features of a +model when a specific package does not directly expose them.

+
+
+O_FEATURES = 'features'
+
+ +
+
+O_LOGITS = 'logits'
+
+ +
+
+O_PROBS = 'probs'
+
+ +
+
+fprop(x, **kwargs)[source]
+

Forward propagation to compute the model outputs. +:param x: A symbolic representation of the network input +:return: A dictionary mapping layer names to the symbolic

+
+

representation of their output.

+
+
+ +
+
+get_layer(x, layer, **kwargs)[source]
+

Return a layer output. +:param x: tensor, the input to the network. +:param layer: str, the name of the layer to compute. +:param **kwargs: dict, extra optional params to pass to self.fprop. +:return: the content of layer layer

+
+ +
+
+get_layer_names()[source]
+

Return the list of exposed layers for this model.

+
+ +
+
+get_logits(x, **kwargs)[source]
+
+
Parameters
+

x – A symbolic representation (Tensor) of the network input

+
+
Returns
+

A symbolic representation (Tensor) of the output logits

+
+
+

(i.e., the values fed as inputs to the softmax layer).

+
+ +
+
+get_params()[source]
+

Provides access to the model’s parameters. +:return: A list of all Variables defining the model parameters.

+
+ +
+
+get_predicted_class(x, **kwargs)[source]
+
+
Parameters
+

x – A symbolic representation (Tensor) of the network input

+
+
Returns
+

A symbolic representation (Tensor) of the predicted label

+
+
+
+ +
+
+get_probs(x, **kwargs)[source]
+
+
Parameters
+

x – A symbolic representation (Tensor) of the network input

+
+
Returns
+

A symbolic representation (Tensor) of the output

+
+
+

probabilities (i.e., the output values produced by the softmax layer).

+
+ +
+
+make_input_placeholder()[source]
+

Create and return a placeholder representing an input to the model.

+

This method should respect context managers (e.g. “with tf.device”) +and should not just return a reference to a single pre-created +placeholder.

+
+ +
+
+make_label_placeholder()[source]
+

Create and return a placeholder representing class labels.

+

This method should respect context managers (e.g. “with tf.device”) +and should not just return a reference to a single pre-created +placeholder.

+
+ +
+
+make_params()[source]
+

Create all Variables to be returned later by get_params. +By default this is a no-op. +Models that need their fprop to be called for their params to be +created can set needs_dummy_fprop=True in the constructor.

+
+ +
+ +
+
+class cleverhans.attacks.MomentumIterativeMethod(model, sess=None, dtypestr='float32', **kwargs)[source]
+

Bases: cleverhans.attacks.attack.Attack

+

The Momentum Iterative Method (Dong et al. 2017). This method won +the first places in NIPS 2017 Non-targeted Adversarial Attacks and +Targeted Adversarial Attacks. The original paper used hard labels +for this attack; no label smoothing. +Paper link: https://arxiv.org/pdf/1710.06081.pdf

+
+
Parameters
+
    +
  • model – cleverhans.model.Model

  • +
  • sess – optional tf.Session

  • +
  • dtypestr – dtype of the data

  • +
  • kwargs – passed through to super constructor

  • +
+
+
+
+
+generate(x, **kwargs)[source]
+

Generate symbolic graph for adversarial examples and return.

+
+
Parameters
+
    +
  • x – The model’s symbolic inputs.

  • +
  • kwargs – Keyword arguments. See parse_params for documentation.

  • +
+
+
+
+ +
+
+parse_params(eps=0.3, eps_iter=0.06, nb_iter=10, y=None, ord=inf, decay_factor=1.0, clip_min=None, clip_max=None, y_target=None, sanity_checks=True, **kwargs)[source]
+

Take in a dictionary of parameters and applies attack-specific checks +before saving them as attributes.

+

Attack-specific parameters:

+
+
Parameters
+
    +
  • eps – (optional float) maximum distortion of adversarial example +compared to original input

  • +
  • eps_iter – (optional float) step size for each attack iteration

  • +
  • nb_iter – (optional int) Number of attack iterations.

  • +
  • y – (optional) A tensor with the true labels.

  • +
  • y_target – (optional) A tensor with the labels to target. Leave +y_target=None if y is also set. Labels should be +one-hot-encoded.

  • +
  • ord – (optional) Order of the norm (mimics Numpy). +Possible values: np.inf, 1 or 2.

  • +
  • decay_factor – (optional) Decay factor for the momentum term.

  • +
  • clip_min – (optional float) Minimum input component value

  • +
  • clip_max – (optional float) Maximum input component value

  • +
+
+
+
+ +
+ +
+
+class cleverhans.attacks.Noise(model, sess=None, dtypestr='float32', **kwargs)[source]
+

Bases: cleverhans.attacks.attack.Attack

+

A weak attack that just picks a random point in the attacker’s action space. +When combined with an attack bundling function, this can be used to implement +random search.

+

References: +https://arxiv.org/abs/1802.00420 recommends random search to help identify

+
+

gradient masking.

+
+
+
https://openreview.net/forum?id=H1g0piA9tQ recommends using noise as part

of an attack bundling recipe combining many different optimizers to yield +a stronger optimizer.

+
+
+
+
Parameters
+
    +
  • model – cleverhans.model.Model

  • +
  • sess – optional tf.Session

  • +
  • dtypestr – dtype of the data

  • +
  • kwargs – passed through to super constructor

  • +
+
+
+
+
+generate(x, **kwargs)[source]
+

Generate symbolic graph for adversarial examples and return.

+
+
Parameters
+
    +
  • x – The model’s symbolic inputs.

  • +
  • kwargs – See parse_params

  • +
+
+
+
+ +
+
+parse_params(eps=0.3, ord=inf, clip_min=None, clip_max=None, **kwargs)[source]
+

Take in a dictionary of parameters and applies attack-specific checks +before saving them as attributes.

+

Attack-specific parameters:

+
+
Parameters
+
    +
  • eps – (optional float) maximum distortion of adversarial example +compared to original input

  • +
  • ord – (optional) Order of the norm (mimics Numpy). +Possible values: np.inf

  • +
  • clip_min – (optional float) Minimum input component value

  • +
  • clip_max – (optional float) Maximum input component value

  • +
+
+
+
+ +
+ +
+
+class cleverhans.attacks.ProjectedGradientDescent(model, sess=None, dtypestr='float32', default_rand_init=True, **kwargs)[source]
+

Bases: cleverhans.attacks.attack.Attack

+

This class implements either the Basic Iterative Method +(Kurakin et al. 2016) when rand_init is set to 0. or the +Madry et al. (2017) method when rand_minmax is larger than 0. +Paper link (Kurakin et al. 2016): https://arxiv.org/pdf/1607.02533.pdf +Paper link (Madry et al. 2017): https://arxiv.org/pdf/1706.06083.pdf

+
+
Parameters
+
    +
  • model – cleverhans.model.Model

  • +
  • sess – optional tf.Session

  • +
  • dtypestr – dtype of the data

  • +
  • default_rand_init – whether to use random initialization by default

  • +
  • kwargs – passed through to super constructor

  • +
+
+
+
+
+FGM_CLASS
+

alias of cleverhans.attacks.fast_gradient_method.FastGradientMethod

+
+ +
+
+generate(x, **kwargs)[source]
+

Generate symbolic graph for adversarial examples and return.

+
+
Parameters
+
    +
  • x – The model’s symbolic inputs.

  • +
  • kwargs – See parse_params

  • +
+
+
+
+ +
+
+parse_params(eps=0.3, eps_iter=0.05, nb_iter=10, y=None, ord=inf, loss_fn=<function softmax_cross_entropy_with_logits>, clip_min=None, clip_max=None, y_target=None, rand_init=None, rand_init_eps=None, clip_grad=False, sanity_checks=True, **kwargs)[source]
+

Take in a dictionary of parameters and applies attack-specific checks +before saving them as attributes.

+

Attack-specific parameters:

+
+
Parameters
+
    +
  • eps – (optional float) maximum distortion of adversarial example +compared to original input

  • +
  • eps_iter – (optional float) step size for each attack iteration

  • +
  • nb_iter – (optional int) Number of attack iterations.

  • +
  • y – (optional) A tensor with the true labels.

  • +
  • y_target – (optional) A tensor with the labels to target. Leave +y_target=None if y is also set. Labels should be +one-hot-encoded.

  • +
  • ord – (optional) Order of the norm (mimics Numpy). +Possible values: np.inf, 1 or 2.

  • +
  • loss_fn – Loss function that takes (labels, logits) as arguments and returns loss

  • +
  • clip_min – (optional float) Minimum input component value

  • +
  • clip_max – (optional float) Maximum input component value

  • +
  • rand_init – (optional) Start the gradient descent from a point chosen +uniformly at random in the norm ball of radius +rand_init_eps

  • +
  • rand_init_eps – (optional float) size of the norm ball from which +the initial starting point is chosen. Defaults to eps

  • +
  • clip_grad – (optional bool) Ignore gradient components at positions +where the input is already at the boundary of the domain, +and the update step will get clipped out.

  • +
  • sanity_checks

    bool Insert tf asserts checking values +(Some tests need to run with no sanity checks because the

    +
    +

    tests intentionally configure the attack strangely)

    +
    +

  • +
+
+
+
+ +
+ +
+
+class cleverhans.attacks.SPSA(model, sess=None, dtypestr='float32', **kwargs)[source]
+

Bases: cleverhans.attacks.attack.Attack

+

This implements the SPSA adversary, as in https://arxiv.org/abs/1802.05666 +(Uesato et al. 2018). SPSA is a gradient-free optimization method, which +is useful when the model is non-differentiable, or more generally, the +gradients do not point in useful directions.

+
+
Parameters
+
    +
  • model – cleverhans.model.Model

  • +
  • sess – optional tf.Session

  • +
  • dtypestr – dtype of the data

  • +
  • kwargs – passed through to super constructor

  • +
+
+
+
+
+DEFAULT_DELTA = 0.01
+
+ +
+
+DEFAULT_LEARNING_RATE = 0.01
+
+ +
+
+DEFAULT_SPSA_ITERS = 1
+
+ +
+
+DEFAULT_SPSA_SAMPLES = 128
+
+ +
+
+generate(x, y=None, y_target=None, eps=None, clip_min=None, clip_max=None, nb_iter=None, is_targeted=None, early_stop_loss_threshold=None, learning_rate=0.01, delta=0.01, spsa_samples=128, batch_size=None, spsa_iters=1, is_debug=False, epsilon=None, num_steps=None)[source]
+

Generate symbolic graph for adversarial examples.

+
+
Parameters
+
    +
  • x – The model’s symbolic inputs. Must be a batch of size 1.

  • +
  • y – A Tensor or None. The index of the correct label.

  • +
  • y_target – A Tensor or None. The index of the target label in a +targeted attack.

  • +
  • eps – The size of the maximum perturbation, measured in the +L-infinity norm.

  • +
  • clip_min – If specified, the minimum input value

  • +
  • clip_max – If specified, the maximum input value

  • +
  • nb_iter – The number of optimization steps.

  • +
  • early_stop_loss_threshold – A float or None. If specified, the +attack will end as soon as the loss +is below early_stop_loss_threshold.

  • +
  • learning_rate – Learning rate of ADAM optimizer.

  • +
  • delta – Perturbation size used for SPSA approximation.

  • +
  • spsa_samples – Number of inputs to evaluate at a single time. +The true batch size (the number of evaluated +inputs for each update) is spsa_samples * +spsa_iters

  • +
  • batch_size – Deprecated param that is an alias for spsa_samples

  • +
  • spsa_iters – Number of model evaluations before performing an +update, where each evaluation is on spsa_samples +different inputs.

  • +
  • is_debug – If True, print the adversarial loss after each update.

  • +
  • epsilon – Deprecated alias for eps

  • +
  • num_steps – Deprecated alias for nb_iter.

  • +
  • is_targeted – Deprecated argument. Ignored.

  • +
+
+
+
+ +
+
+generate_np(x_val, **kwargs)[source]
+

Generate adversarial examples and return them as a NumPy array. +Sub-classes should not implement this method unless they must +perform special handling of arguments.

+
+
Parameters
+
    +
  • x_val – A NumPy array with the original inputs.

  • +
  • **kwargs

    optional parameters used by child classes.

    +

  • +
+
+
Returns
+

A NumPy array holding the adversarial examples.

+
+
+
+ +
+ +
+
+class cleverhans.attacks.SaliencyMapMethod(model, sess=None, dtypestr='float32', **kwargs)[source]
+

Bases: cleverhans.attacks.attack.Attack

+

The Jacobian-based Saliency Map Method (Papernot et al. 2016). +Paper link: https://arxiv.org/pdf/1511.07528.pdf

+
+
Parameters
+
    +
  • model – cleverhans.model.Model

  • +
  • sess – optional tf.Session

  • +
  • dtypestr – dtype of the data

  • +
  • kwargs – passed through to super constructor

  • +
+
+
Note
+

When not using symbolic implementation in generate, sess should +be provided

+
+
+
+
+generate(x, **kwargs)[source]
+

Generate symbolic graph for adversarial examples and return.

+
+
Parameters
+
    +
  • x – The model’s symbolic inputs.

  • +
  • kwargs – See parse_params

  • +
+
+
+
+ +
+
+parse_params(theta=1.0, gamma=1.0, clip_min=0.0, clip_max=1.0, y_target=None, symbolic_impl=True, **kwargs)[source]
+

Take in a dictionary of parameters and applies attack-specific checks +before saving them as attributes.

+

Attack-specific parameters:

+
+
Parameters
+
    +
  • theta – (optional float) Perturbation introduced to modified +components (can be positive or negative)

  • +
  • gamma – (optional float) Maximum percentage of perturbed features

  • +
  • clip_min – (optional float) Minimum component value for clipping

  • +
  • clip_max – (optional float) Maximum component value for clipping

  • +
  • y_target – (optional) Target tensor if the attack is targeted

  • +
+
+
+
+ +
+ +
+
+class cleverhans.attacks.Semantic(model, center, max_val=1.0, sess=None, dtypestr='float32', **kwargs)[source]
+

Bases: cleverhans.attacks.attack.Attack

+

Semantic adversarial examples

+

https://arxiv.org/abs/1703.06857

+

Note: data must either be centered (so that the negative image can be +made by simple negation) or must be in the interval [-1, 1]

+
+
Parameters
+
    +
  • model – cleverhans.model.Model

  • +
  • center – bool +If True, assumes data has 0 mean so the negative image is just negation. +If False, assumes data is in the interval [0, max_val]

  • +
  • max_val – float +Maximum value allowed in the input data

  • +
  • sess – optional tf.Session

  • +
  • dtypestr – dtype of data

  • +
  • kwargs – passed through to the super constructor

  • +
+
+
+
+
+generate(x, **kwargs)[source]
+

Generate the attack’s symbolic graph for adversarial examples. This +method should be overriden in any child class that implements an +attack that is expressable symbolically. Otherwise, it will wrap the +numerical implementation as a symbolic operator.

+
+
Parameters
+
    +
  • x – The model’s symbolic inputs.

  • +
  • **kwargs

    optional parameters used by child classes. +Each child class defines additional parameters as needed. +Child classes that use the following concepts should use the following +names:

    +
    +

    clip_min: minimum feature value +clip_max: maximum feature value +eps: size of norm constraint on adversarial perturbation +ord: order of norm constraint +nb_iter: number of iterations +eps_iter: size of norm constraint on iteration +y_target: if specified, the attack is targeted. +y: Do not specify if y_target is specified.

    +
    +

    If specified, the attack is untargeted, aims to make the output +class not be y. +If neither y_target nor y is specified, y is inferred by having +the model classify the input.

    +
    +
    +

    For other concepts, it’s generally a good idea to read other classes +and check for name consistency.

    +

  • +
+
+
Returns
+

A symbolic representation of the adversarial examples.

+
+
+
+ +
+ +
+
+class cleverhans.attacks.SparseL1Descent(model, sess=None, dtypestr='float32', **kwargs)[source]
+

Bases: cleverhans.attacks.attack.Attack

+

This class implements a variant of Projected Gradient Descent for the l1-norm +(Tramer and Boneh 2019). The l1-norm case is more tricky than the l-inf and l2 +cases covered by the ProjectedGradientDescent class, because the steepest +descent direction for the l1-norm is too sparse (it updates a single +coordinate in the adversarial perturbation in each step). This attack has an +additional parameter that controls the sparsity of the update step. For +moderately sparse update steps, the attack vastly outperforms Projected +Steepest Descent and is competitive with other attacks targeted at the l1-norm +such as the ElasticNetMethod attack (which is much more computationally +expensive). +Paper link (Tramer and Boneh 2019): https://arxiv.org/pdf/1904.13000.pdf

+
+
Parameters
+
    +
  • model – cleverhans.model.Model

  • +
  • sess – optional tf.Session

  • +
  • dtypestr – dtype of the data

  • +
  • kwargs – passed through to super constructor

  • +
+
+
+
+
+generate(x, **kwargs)[source]
+

Generate symbolic graph for adversarial examples and return.

+
+
Parameters
+
    +
  • x – The model’s symbolic inputs.

  • +
  • kwargs – See parse_params

  • +
+
+
+
+ +
+
+parse_params(eps=10.0, eps_iter=1.0, nb_iter=20, y=None, clip_min=None, clip_max=None, y_target=None, rand_init=False, clip_grad=False, grad_sparsity=99, sanity_checks=True, **kwargs)[source]
+

Take in a dictionary of parameters and applies attack-specific checks +before saving them as attributes.

+

Attack-specific parameters:

+
+
Parameters
+
    +
  • eps – (optional float) maximum distortion of adversarial example +compared to original input

  • +
  • eps_iter – (optional float) step size for each attack iteration

  • +
  • nb_iter – (optional int) Number of attack iterations.

  • +
  • y – (optional) A tensor with the true labels.

  • +
  • y_target – (optional) A tensor with the labels to target. Leave +y_target=None if y is also set. Labels should be +one-hot-encoded.

  • +
  • clip_min – (optional float) Minimum input component value

  • +
  • clip_max – (optional float) Maximum input component value

  • +
  • clip_grad – (optional bool) Ignore gradient components +at positions where the input is already at the boundary +of the domain, and the update step will get clipped out.

  • +
+
+
+
+
:param grad_sparsity (optional) Relative sparsity of the gradient update

step, in percent. Only gradient values larger +than this percentile are retained. This parameter can +be a scalar, or a vector of the same length as the +input batch dimension.

+
+
+
+
Parameters
+

sanity_checks

bool Insert tf asserts checking values +(Some tests need to run with no sanity checks because the

+
+

tests intentionally configure the attack strangely)

+
+

+
+
+
+ +
+ +
+
+class cleverhans.attacks.SpatialTransformationMethod(model, sess=None, dtypestr='float32', **kwargs)[source]
+

Bases: cleverhans.attacks.attack.Attack

+

Spatial transformation attack

+
+
+generate(x, **kwargs)[source]
+

Generate symbolic graph for adversarial examples and return. +:param x: The model’s symbolic inputs. +:param kwargs: See parse_params

+
+ +
+
+parse_params(n_samples=None, dx_min=- 0.1, dx_max=0.1, n_dxs=2, dy_min=- 0.1, dy_max=0.1, n_dys=2, angle_min=- 30, angle_max=30, n_angles=6, black_border_size=0, **kwargs)[source]
+

Take in a dictionary of parameters and applies attack-specific checks +before saving them as attributes. +:param n_samples: (optional) The number of transformations sampled to

+
+

construct the attack. Set it to None to run +full grid attack.

+
+
+
Parameters
+
    +
  • dx_min – (optional float) Minimum translation ratio along x-axis.

  • +
  • dx_max – (optional float) Maximum translation ratio along x-axis.

  • +
  • n_dxs – (optional int) Number of discretized translation ratios +along x-axis.

  • +
  • dy_min – (optional float) Minimum translation ratio along y-axis.

  • +
  • dy_max – (optional float) Maximum translation ratio along y-axis.

  • +
  • n_dys – (optional int) Number of discretized translation ratios +along y-axis.

  • +
  • angle_min – (optional float) Largest counter-clockwise rotation +angle.

  • +
  • angle_max – (optional float) Largest clockwise rotation angle.

  • +
  • n_angles – (optional int) Number of discretized angles.

  • +
  • black_border_size – (optional int) size of the black border in pixels.

  • +
+
+
+
+ +
+ +
+
+class cleverhans.attacks.VirtualAdversarialMethod(model, sess=None, dtypestr='float32', **kwargs)[source]
+

Bases: cleverhans.attacks.attack.Attack

+

This attack was originally proposed by Miyato et al. (2016) and was used +for virtual adversarial training. +Paper link: https://arxiv.org/abs/1507.00677

+
+
Parameters
+
    +
  • model – cleverhans.model.Model

  • +
  • sess – optional tf.Session

  • +
  • dtypestr – dtype of the data

  • +
  • kwargs – passed through to super constructor

  • +
+
+
+
+
+generate(x, **kwargs)[source]
+

Generate symbolic graph for adversarial examples and return.

+
+
Parameters
+
    +
  • x – The model’s symbolic inputs.

  • +
  • kwargs – See parse_params

  • +
+
+
+
+ +
+
+parse_params(eps=2.0, nb_iter=None, xi=1e-06, clip_min=None, clip_max=None, num_iterations=None, **kwargs)[source]
+

Take in a dictionary of parameters and applies attack-specific checks +before saving them as attributes.

+

Attack-specific parameters:

+
+
Parameters
+
    +
  • eps – (optional float )the epsilon (input variation parameter)

  • +
  • nb_iter – (optional) the number of iterations +Defaults to 1 if not specified

  • +
  • xi – (optional float) the finite difference parameter

  • +
  • clip_min – (optional float) Minimum input component value

  • +
  • clip_max – (optional float) Maximum input component value

  • +
  • num_iterations – Deprecated alias for nb_iter

  • +
+
+
+
+ +
+ +
+
+cleverhans.attacks.clip_eta(eta, ord, eps)[source]
+

Helper function to clip the perturbation to epsilon norm ball. +:param eta: A tensor with the current perturbation. +:param ord: Order of the norm (mimics Numpy).

+
+

Possible values: np.inf, 1 or 2.

+
+
+
Parameters
+

eps – Epsilon, bound of the perturbation.

+
+
+
+ +
+
+cleverhans.attacks.fgm(x, logits, y=None, eps=0.3, ord=inf, loss_fn=<function softmax_cross_entropy_with_logits>, clip_min=None, clip_max=None, clip_grad=False, targeted=False, sanity_checks=True)[source]
+

TensorFlow implementation of the Fast Gradient Method. +:param x: the input placeholder +:param logits: output of model.get_logits +:param y: (optional) A placeholder for the true labels. If targeted

+
+

is true, then provide the target label. Otherwise, only provide +this parameter if you’d like to use true labels when crafting +adversarial samples. Otherwise, model predictions are used as +labels to avoid the “label leaking” effect (explained in this +paper: https://arxiv.org/abs/1611.01236). Default is None. +Labels should be one-hot-encoded.

+
+
+
Parameters
+
    +
  • eps – the epsilon (input variation parameter)

  • +
  • ord – (optional) Order of the norm (mimics NumPy). +Possible values: np.inf, 1 or 2.

  • +
  • loss_fn – Loss function that takes (labels, logits) as arguments and returns loss

  • +
  • clip_min – Minimum float value for adversarial example components

  • +
  • clip_max – Maximum float value for adversarial example components

  • +
  • clip_grad – (optional bool) Ignore gradient components +at positions where the input is already at the boundary +of the domain, and the update step will get clipped out.

  • +
  • targeted – Is the attack targeted or untargeted? Untargeted, the +default, will try to make the label incorrect. Targeted +will instead try to move in the direction of being more +like y.

  • +
+
+
Returns
+

a tensor for the adversarial example

+
+
+
+ +
+
+cleverhans.attacks.optimize_linear(grad, eps, ord=inf)[source]
+

Solves for the optimal input to a linear function under a norm constraint.

+

Optimal_perturbation = argmax_{eta, ||eta||_{ord} < eps} dot(eta, grad)

+
+
Parameters
+
    +
  • grad – tf tensor containing a batch of gradients

  • +
  • eps – float scalar specifying size of constraint region

  • +
  • ord – int specifying order of norm

  • +
+
+
Returns
+

tf tensor containing optimal perturbation

+
+
+
+ +
+
+cleverhans.attacks.projected_optimization(loss_fn, input_image, label, epsilon, num_steps, clip_min=None, clip_max=None, optimizer=<cleverhans.attacks.spsa.TensorAdam object>, project_perturbation=<function _project_perturbation>, early_stop_loss_threshold=None, is_debug=False)[source]
+

Generic projected optimization, generalized to work with approximate +gradients. Used for e.g. the SPSA attack.

+
+
Args:
+
param loss_fn
+

A callable which takes input_image and label as +arguments, and returns a batch of loss values. Same +interface as TensorOptimizer.

+
+
param input_image
+

Tensor, a batch of images

+
+
param label
+

Tensor, a batch of labels

+
+
param epsilon
+

float, the L-infinity norm of the maximum allowable +perturbation

+
+
param num_steps
+

int, the number of steps of gradient descent

+
+
param clip_min
+

float, minimum pixel value

+
+
param clip_max
+

float, maximum pixel value

+
+
param optimizer
+

A TensorOptimizer object

+
+
param project_perturbation
+

A function, which will be used to enforce +some constraint. It should have the same +signature as _project_perturbation.

+
+
param early_stop_loss_threshold
+

A float or None. If specified, the attack will end if the loss is below +early_stop_loss_threshold.

+
+
+
Enabling this option can have several different effects:
    +
  • Setting the threshold to 0. guarantees that if a successful attack is found, it is returned. +This increases the attack success rate, because without early stopping the optimizer can accidentally +bounce back to a point where the attack fails.

  • +
  • Early stopping can make the attack run faster because it may run for fewer steps.

  • +
  • Early stopping can make the attack run slower because the loss must be calculated at each step. +The loss is not calculated as part of the normal SPSA optimization procedure. +For most reasonable choices of hyperparameters, early stopping makes the attack much faster because +it decreases the number of steps dramatically.

  • +
+
+
+
+
+
param is_debug
+

A bool. If True, print debug info for attack progress.

+
+
+
+
Returns:
+
adversarial version of input_image, with L-infinity difference less than

epsilon, which tries to minimize loss_fn.

+
+
+
+
+

Note that this function is not intended as an Attack by itself. Rather, it +is designed as a helper function which you can use to write your own attack +methods. The method uses a tf.while_loop to optimize a loss function in +a single sess.run() call.

+
+ +
+
+cleverhans.attacks.reduce_max(*args, **kwargs)
+

Issues a deprecation warning and passes through the arguments.

+
+ +
+
+cleverhans.attacks.reduce_mean(*args, **kwargs)
+

Issues a deprecation warning and passes through the arguments.

+
+ +
+
+cleverhans.attacks.reduce_sum(*args, **kwargs)
+

Issues a deprecation warning and passes through the arguments.

+
+ +
+
+cleverhans.attacks.softmax_cross_entropy_with_logits(sentinel=None, labels=None, logits=None, dim=- 1)[source]
+

Wrapper around tf.nn.softmax_cross_entropy_with_logits_v2 to handle +deprecated warning

+
+ +
+
+cleverhans.attacks.vatm(model, x, logits, eps, num_iterations=1, xi=1e-06, clip_min=None, clip_max=None, scope=None)[source]
+

Tensorflow implementation of the perturbation method used for virtual +adversarial training: https://arxiv.org/abs/1507.00677 +:param model: the model which returns the network unnormalized logits +:param x: the input placeholder +:param logits: the model’s unnormalized output tensor (the input to

+
+

the softmax layer)

+
+
+
Parameters
+
    +
  • eps – the epsilon (input variation parameter)

  • +
  • num_iterations – the number of iterations

  • +
  • xi – the finite difference parameter

  • +
  • clip_min – optional parameter that can be used to set a minimum +value for components of the example returned

  • +
  • clip_max – optional parameter that can be used to set a maximum +value for components of the example returned

  • +
  • seed – the seed for random generator

  • +
+
+
Returns
+

a tensor for the adversarial example

+
+
+
+ +
+
+cleverhans.attacks.wrapper_warning()[source]
+

Issue a deprecation warning. Used in multiple places that implemented +attacks by automatically wrapping a user-supplied callable with a +CallableModelWrapper with output_layer=”probs”. +Using “probs” as any part of the attack interface is dangerous. +We can’t just change output_layer to logits because: +- that would be a silent interface change. We’d have no way of detecting

+
+

code that still means to use probs. Note that we can’t just check whether +the final output op is a softmax—for example, Inception puts a reshape +after the softmax.

+
+
    +
  • automatically wrapping user-supplied callables with output_layer=’logits’ +is even worse, see wrapper_warning_logits

  • +
+

Note: this function will be removed at the same time as the code that +calls it.

+
+ +
+
+cleverhans.attacks.wrapper_warning_logits()[source]
+

Issue a deprecation warning. Used in multiple places that implemented +attacks by automatically wrapping a user-supplied callable with a +CallableModelWrapper with output_layer=”logits”. +This is dangerous because it is under-the-hood automagic that the user +may not realize has been invoked for them. If they pass a callable +that actually outputs probs, the probs will be treated as logits, +resulting in an incorrect cross-entropy loss and severe gradient +masking.

+
+ +
+
+cleverhans.attacks.xrange
+

alias of range

+
+ +
+ + +
+ +
+
+ +
+
+ + + + \ No newline at end of file diff --git a/cleverhans_v3.1.0/docs/source/devtools.html b/cleverhans_v3.1.0/docs/source/devtools.html new file mode 100644 index 000000000..df0329878 --- /dev/null +++ b/cleverhans_v3.1.0/docs/source/devtools.html @@ -0,0 +1,91 @@ + + + + + + + + devtools module — CleverHans documentation + + + + + + + + + + + + + + + + + +
+
+
+ + +
+ +
+

devtools module

+
+ + +
+ +
+
+ +
+
+ + + + \ No newline at end of file diff --git a/cleverhans_v3.1.0/docs/source/future.html b/cleverhans_v3.1.0/docs/source/future.html new file mode 100644 index 000000000..d8c7729f5 --- /dev/null +++ b/cleverhans_v3.1.0/docs/source/future.html @@ -0,0 +1,91 @@ + + + + + + + + future module — CleverHans documentation + + + + + + + + + + + + + + + + + +
+
+
+ + +
+ +
+

future module

+
+ + +
+ +
+
+ +
+
+ + + + \ No newline at end of file diff --git a/cleverhans_v3.1.0/docs/source/model.html b/cleverhans_v3.1.0/docs/source/model.html new file mode 100644 index 000000000..4492a0f3b --- /dev/null +++ b/cleverhans_v3.1.0/docs/source/model.html @@ -0,0 +1,282 @@ + + + + + + + + model module — CleverHans documentation + + + + + + + + + + + + + + + + + + +
+
+
+ + +
+ +
+

model module

+

The Model class and related functionality.

+
+
+class cleverhans.model.CallableModelWrapper(callable_fn, output_layer)[source]
+

Bases: cleverhans.model.Model

+

A wrapper that turns a callable into a valid Model

+
+
+fprop(x, **kwargs)[source]
+

Forward propagation to compute the model outputs. +:param x: A symbolic representation of the network input +:return: A dictionary mapping layer names to the symbolic

+
+

representation of their output.

+
+
+ +
+ +
+
+class cleverhans.model.Model(scope=None, nb_classes=None, hparams=None, needs_dummy_fprop=False)[source]
+

Bases: object

+

An abstract interface for model wrappers that exposes model symbols +needed for making an attack. This abstraction removes the dependency on +any specific neural network package (e.g. Keras) from the core +code of CleverHans. It can also simplify exposing the hidden features of a +model when a specific package does not directly expose them.

+
+
+O_FEATURES = 'features'
+
+ +
+
+O_LOGITS = 'logits'
+
+ +
+
+O_PROBS = 'probs'
+
+ +
+
+fprop(x, **kwargs)[source]
+

Forward propagation to compute the model outputs. +:param x: A symbolic representation of the network input +:return: A dictionary mapping layer names to the symbolic

+
+

representation of their output.

+
+
+ +
+
+get_layer(x, layer, **kwargs)[source]
+

Return a layer output. +:param x: tensor, the input to the network. +:param layer: str, the name of the layer to compute. +:param **kwargs: dict, extra optional params to pass to self.fprop. +:return: the content of layer layer

+
+ +
+
+get_layer_names()[source]
+

Return the list of exposed layers for this model.

+
+ +
+
+get_logits(x, **kwargs)[source]
+
+
Parameters
+

x – A symbolic representation (Tensor) of the network input

+
+
Returns
+

A symbolic representation (Tensor) of the output logits

+
+
+

(i.e., the values fed as inputs to the softmax layer).

+
+ +
+
+get_params()[source]
+

Provides access to the model’s parameters. +:return: A list of all Variables defining the model parameters.

+
+ +
+
+get_predicted_class(x, **kwargs)[source]
+
+
Parameters
+

x – A symbolic representation (Tensor) of the network input

+
+
Returns
+

A symbolic representation (Tensor) of the predicted label

+
+
+
+ +
+
+get_probs(x, **kwargs)[source]
+
+
Parameters
+

x – A symbolic representation (Tensor) of the network input

+
+
Returns
+

A symbolic representation (Tensor) of the output

+
+
+

probabilities (i.e., the output values produced by the softmax layer).

+
+ +
+
+make_input_placeholder()[source]
+

Create and return a placeholder representing an input to the model.

+

This method should respect context managers (e.g. “with tf.device”) +and should not just return a reference to a single pre-created +placeholder.

+
+ +
+
+make_label_placeholder()[source]
+

Create and return a placeholder representing class labels.

+

This method should respect context managers (e.g. “with tf.device”) +and should not just return a reference to a single pre-created +placeholder.

+
+ +
+
+make_params()[source]
+

Create all Variables to be returned later by get_params. +By default this is a no-op. +Models that need their fprop to be called for their params to be +created can set needs_dummy_fprop=True in the constructor.

+
+ +
+ +
+
+exception cleverhans.model.NoSuchLayerError[source]
+

Bases: ValueError

+

Raised when a layer that does not exist is requested.

+
+ +
+
+cleverhans.model.wrapper_warning()[source]
+

Issue a deprecation warning. Used in multiple places that implemented +attacks by automatically wrapping a user-supplied callable with a +CallableModelWrapper with output_layer=”probs”. +Using “probs” as any part of the attack interface is dangerous. +We can’t just change output_layer to logits because: +- that would be a silent interface change. We’d have no way of detecting

+
+

code that still means to use probs. Note that we can’t just check whether +the final output op is a softmax—for example, Inception puts a reshape +after the softmax.

+
+
    +
  • automatically wrapping user-supplied callables with output_layer=’logits’ +is even worse, see wrapper_warning_logits

  • +
+

Note: this function will be removed at the same time as the code that +calls it.

+
+ +
+
+cleverhans.model.wrapper_warning_logits()[source]
+

Issue a deprecation warning. Used in multiple places that implemented +attacks by automatically wrapping a user-supplied callable with a +CallableModelWrapper with output_layer=”logits”. +This is dangerous because it is under-the-hood automagic that the user +may not realize has been invoked for them. If they pass a callable +that actually outputs probs, the probs will be treated as logits, +resulting in an incorrect cross-entropy loss and severe gradient +masking.

+
+ +
+ + +
+ +
+
+ +
+
+ + + + \ No newline at end of file diff --git a/cleverhans_v3.1.0/docsource/Makefile b/cleverhans_v3.1.0/docsource/Makefile new file mode 100644 index 000000000..7ca6df2e8 --- /dev/null +++ b/cleverhans_v3.1.0/docsource/Makefile @@ -0,0 +1,30 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = python -msphinx +SPHINXPROJ = CleverHans +SOURCEDIR = . +BUILDDIR = _build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +full-api: + sphinx-apidoc --suffix .md -o source .. + + +.PHONY: help Makefile + +# To keep the source and output separate, but still be able to both publish +# on GitHub Pages and preview builds locally. +github: + @make html + @cp -a _build/html/. ../docs + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) \ No newline at end of file diff --git a/cleverhans_v3.1.0/docsource/README.md b/cleverhans_v3.1.0/docsource/README.md new file mode 100644 index 000000000..92ef60b43 --- /dev/null +++ b/cleverhans_v3.1.0/docsource/README.md @@ -0,0 +1,59 @@ +# Generate documentation + +To generate the documentation do: +`make github` + +The documentation files will be copied to the `cleverhans/docs` directory. + +### Preparation + +Please do: +`pip install sphinx` + +Add a `.nojekyll` file in the `cleverhans/docs` directory. When GitHub sees +a `.nojekyll` file, it serves the root `index.html` file. The `.nojekyll` file +indicates that we are not using Jekyll as our static site generator in this +repository. + +### Enable GitHub Pages for the GitHub repository + +1. Go to the repository on the GitHub website and make sure you are logged in. +2. Add a /docs directory to the master branch. Otherwise you do not get the + master branch /docs folder for the Source option in the drop-down list. +3. Click the Settings tab. You first go to the Options section. +4. Scroll down to the GitHub Pages section and choose the drop-down list under + Source. Note: Your choices will differ based on whether you’re in a User repo + or an Org repository. +5. To keep source and output HTML separate, choose master branch /docs folder + for Source. + +### Build Sphinx locally and publish on GitHub Pages + +We keep the source docsource and output docs separate, but still are able to +publish on GitHub Pages and preview builds locally. + +We have the following option in the Makefile: + +``` + github: + @make html + @cp -a _build/html/. ../docs +``` + +Thus, we can run `make github` from the `docsource` directory to generate a +local preview and move the docs where GitHub wants to serve them from. + +### Hacks + +If you cannot build the docs for attacks, uncomment +`import tensorflow_addons as tfa` in `cleverhans/attacks/spsa.py`. + +Otherwise: + +```angular2html +WARNING: autodoc: failed to import module 'attacks' from module 'cleverhans'; the following exception was raised: +cannot import name 'keras_tensor' +``` + +It is convenient to create a virtual environment to install all the specific +libraries (e.g. virutalen cleverhans). diff --git a/cleverhans_v3.1.0/docsource/_templates/layout.html b/cleverhans_v3.1.0/docsource/_templates/layout.html new file mode 100644 index 000000000..1dc0e4c06 --- /dev/null +++ b/cleverhans_v3.1.0/docsource/_templates/layout.html @@ -0,0 +1,5 @@ +{% extends '!layout.html' %} + +{% block footer %} + +{% endblock %} \ No newline at end of file diff --git a/cleverhans_v3.1.0/docsource/conf.py b/cleverhans_v3.1.0/docsource/conf.py new file mode 100644 index 000000000..0fca84f4b --- /dev/null +++ b/cleverhans_v3.1.0/docsource/conf.py @@ -0,0 +1,174 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# CleverHans documentation build configuration file, created by +# sphinx-quickstart on Wed Sep 20 15:14:07 2017. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, as shown here. +# +import os +import sys + +sys.path.insert(0, os.path.abspath("..")) + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +# +# needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + "sphinx.ext.autodoc", + "sphinx.ext.mathjax", + "sphinx.ext.viewcode", + "sphinx.ext.githubpages", +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ["_templates"] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# +# source_suffix = ['.rst', '.md'] +source_suffix = ".md" + +# The master toctree document. +master_doc = "index" + +# General information about the project. +project = "CleverHans" + +author = "Ian Goodfellow, Nicolas Papernot, Ryan Sheatsley" + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +# version = '2.0.0' +# The full version, including alpha/beta/rc tags. +# release = '2.0.0' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This patterns also effect to html_static_path and html_extra_path +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = "sphinx" + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = False + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = "alabaster" + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +# +# html_theme_options = {} + +# Add any paths that contain custom static files (such as style sheets) here, +# 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"] + +# Custom sidebar templates must be a dictionary that maps document names +# to template names. +# +# This is required for the alabaster theme +# refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars +html_sidebars = { + "**": [ + "about.html", + "navigation.html", + "relations.html", # needs 'show_related': True theme option to display + "searchbox.html", + "donate.html", + ] +} + +# -- Options for HTMLHelp output ------------------------------------------ + +# Output file base name for HTML help builder. +htmlhelp_basename = "CleverHansdoc" + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # + # 'papersize': 'letterpaper', + # The font size ('10pt', '11pt' or '12pt'). + # + # 'pointsize': '10pt', + # Additional stuff for the LaTeX preamble. + # + # 'preamble': '', + # Latex figure (float) alignment + # + # 'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + ( + master_doc, + "CleverHans.tex", + "CleverHans Documentation", + "Ian Goodfellow, Nicolas Papernot, Ryan Sheatsley", + "manual", + ), +] + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [(master_doc, "cleverhans", "CleverHans Documentation", [author], 1)] + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ( + master_doc, + "CleverHans", + "CleverHans Documentation", + author, + "CleverHans", + "One line description of project.", + "Miscellaneous", + ), +] diff --git a/cleverhans_v3.1.0/docsource/docs_requirements.txt b/cleverhans_v3.1.0/docsource/docs_requirements.txt new file mode 100644 index 000000000..b3a60b6da --- /dev/null +++ b/cleverhans_v3.1.0/docsource/docs_requirements.txt @@ -0,0 +1 @@ +tensorflow \ No newline at end of file diff --git a/cleverhans_v3.1.0/docsource/index.md b/cleverhans_v3.1.0/docsource/index.md new file mode 100644 index 000000000..117b95383 --- /dev/null +++ b/cleverhans_v3.1.0/docsource/index.md @@ -0,0 +1,35 @@ +.. CleverHans documentation master file, created by + sphinx-quickstart on Wed Sep 20 15:14:07 2017. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + + WARNING: This file has a markdown suffix, but is in fact .rst + +CleverHans Documentation +====================================== + + +This documentation is auto-generated from the docstrings of modules of the current `master` branch of `cleverhans +`_. + +To get started, we recommend reading the `github readme +`_. Afterwards, you can learn more by looking at the following modules: + + +.. toctree:: + :maxdepth: 4 + + source/attacks + + + source/model + + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` + diff --git a/cleverhans_v3.1.0/docsource/source/attacks.md b/cleverhans_v3.1.0/docsource/source/attacks.md new file mode 100644 index 000000000..e242b5638 --- /dev/null +++ b/cleverhans_v3.1.0/docsource/source/attacks.md @@ -0,0 +1,8 @@ +`attacks` module +-------------------------- + +.. automodule:: cleverhans.attacks + :members: + :imported-members: + :undoc-members: + :show-inheritance: diff --git a/cleverhans_v3.1.0/docsource/source/devtools.md b/cleverhans_v3.1.0/docsource/source/devtools.md new file mode 100644 index 000000000..07f3c8277 --- /dev/null +++ b/cleverhans_v3.1.0/docsource/source/devtools.md @@ -0,0 +1,7 @@ +`devtools` module +------------------------ + +.. automodule:: cleverhans.devtools + :members: + :undoc-members: + :show-inheritance: diff --git a/cleverhans_v3.1.0/docsource/source/future.md b/cleverhans_v3.1.0/docsource/source/future.md new file mode 100644 index 000000000..df369011b --- /dev/null +++ b/cleverhans_v3.1.0/docsource/source/future.md @@ -0,0 +1,7 @@ +`future` module +------------------------ + +.. automodule:: cleverhans.devtools + :members: + :undoc-members: + :show-inheritance: diff --git a/cleverhans_v3.1.0/docsource/source/model.md b/cleverhans_v3.1.0/docsource/source/model.md new file mode 100644 index 000000000..67f01a1ef --- /dev/null +++ b/cleverhans_v3.1.0/docsource/source/model.md @@ -0,0 +1,7 @@ +`model` module +------------------------ + +.. automodule:: cleverhans.model + :members: + :undoc-members: + :show-inheritance: diff --git a/cleverhans_v3.1.0/examples/README.md b/cleverhans_v3.1.0/examples/README.md new file mode 100644 index 000000000..895382671 --- /dev/null +++ b/cleverhans_v3.1.0/examples/README.md @@ -0,0 +1,12 @@ +# Examples + +* **CIFAR10 with FGSM using the TensorFlow backend** ([code](ex_cifar10_tf.py)): this tutorial covers how to train a CIFAR10 model using TensorFlow, +craft adversarial examples using the [fast gradient sign method](https://arxiv.org/abs/1412.6572), +and make the model more robust to adversarial +examples using adversarial training. +* **Adversarial faces using FGSM against Facenet library** ([code](facenet_adversarial_faces)): this example covers how to create adversarial faces using the [fast gradient sign method](https://arxiv.org/abs/1412.6572) over the Facenet library. + +# Additional dependencies + +Besides the standard CleverHans dependencies, these examples also require +`wget`. diff --git a/examples/RL-attack/README.md b/cleverhans_v3.1.0/examples/RL-attack/README.md similarity index 100% rename from examples/RL-attack/README.md rename to cleverhans_v3.1.0/examples/RL-attack/README.md diff --git a/cleverhans_v3.1.0/examples/RL-attack/enjoy-adv.py b/cleverhans_v3.1.0/examples/RL-attack/enjoy-adv.py new file mode 100644 index 000000000..2985a71c5 --- /dev/null +++ b/cleverhans_v3.1.0/examples/RL-attack/enjoy-adv.py @@ -0,0 +1,269 @@ +""" DQN - Test-time attacks + +============ Sample usage ============ +No attack, testing a DQN model of Breakout trained without parameter noise: +$> python3 enjoy-adv.py --env Breakout --model-dir \ + ./data/Breakout/model-100 --video ./Breakout.mp4 + +No attack, testing a DQN model of Breakout trained with parameter noise +(NoisyNet implementation): +$> python3 enjoy-adv.py --env Breakout --noisy \ + --model-dir ./data/Breakout/model-173000 --video ./Breakout.mp4 + +Whitebox FGSM attack, testing a DQN model of Breakout +trained without parameter noise: +$> python3 enjoy-adv.py --env Breakout --model-dir \ + ./data/Breakout/model-173000 --attack fgsm --video ./Breakout.mp4 + +Whitebox FGSM attack, testing a DQN model of Breakout +trained with parameter noise (NoisyNet implementation): +$> python3 enjoy-adv.py --env Breakout --noisy --model-dir \ + ./data/Breakout/model-173000 --attack fgsm --video ./Breakout.mp4 + +Blackbox FGSM attack, testing a DQN model of Breakout +trained without parameter noise: +$> python3 enjoy-adv.py --env Breakout --model-dir \ + ./data/Breakout/model-173000 --attack fgsm --blackbox \ + --model-dir2 ./data/Breakout/model-173000-2 --video ./Breakout.mp4 + +Blackbox FGSM attack, testing a DQN model of Breakout +trained with parameter noise (NoisyNet implementation), +replica model trained without parameter noise: +$> python3 enjoy-adv.py --env Breakout --noisy \ + --model-dir ./data/Breakout/model-173000 --attack fgsm --blackbox \ + --model-dir2 ./data/Breakout/model2-173000-2 --video ./Breakout.mp4 + +Blackbox FGSM attack, testing a DQN model of Breakout +trained with parameter noise (NoisyNet implementation), +replica model trained with parameter noise: +$> python3 enjoy-adv.py --env Breakout --noisy --model-dir \ + ./data/Breakout/model-173000 --attack fgsm --blackbox \ + --model-dir2 ./data/Breakout/model2-173000 --noisy2 --video ./Breakout.mp4 + +""" + +import argparse +import gym +import os +import numpy as np + +from gym.monitoring import VideoRecorder + +import rlattack.common.tf_util as U + +from rlattack import deepq +from rlattack.common.misc_util import ( + boolean_flag, + SimpleMonitor, +) +from rlattack.common.atari_wrappers_deprecated import wrap_dqn + + +# V: imports# +import tensorflow as tf +import cv2 +from collections import deque +from model import model, dueling_model +from statistics import statistics + + +class DQNModel: + """ + Creating Q-graph, FGSM graph + Supports loading multiple graphs - needed for blackbox attacks + """ + + def __init__(self, env, dueling, noisy, fname): + self.g = tf.Graph() + self.noisy = noisy + self.dueling = dueling + self.env = env + with self.g.as_default(): + self.act = deepq.build_act_enjoy( + make_obs_ph=lambda name: U.Uint8Input( + env.observation_space.shape, name=name + ), + q_func=dueling_model if dueling else model, + num_actions=env.action_space.n, + noisy=noisy, + ) + self.saver = tf.train.Saver() + self.sess = tf.Session(graph=self.g) + + if fname is not None: + print("Loading Model...") + self.saver.restore(self.sess, fname) + + def get_act(self): + return self.act + + def get_session(self): + return self.sess + + def craft_adv(self): + with self.sess.as_default(): + with self.g.as_default(): + craft_adv_obs = deepq.build_adv( + make_obs_tf=lambda name: U.Uint8Input( + self.env.observation_space.shape, name=name + ), + q_func=dueling_model if self.dueling else model, + num_actions=self.env.action_space.n, + epsilon=1.0 / 255.0, + noisy=self.noisy, + ) + return craft_adv_obs + + +def parse_args(): + parser = argparse.ArgumentParser("Run an already learned DQN model.") + # Environment + parser.add_argument("--env", type=str, required=True, help="name of the game") + parser.add_argument( + "--model-dir", type=str, default=None, help="load model from this directory. " + ) + parser.add_argument( + "--video", + type=str, + default=None, + help="Path to mp4 file where the \ + video of first episode will be recorded.", + ) + boolean_flag( + parser, + "stochastic", + default=True, + help="whether or not to use stochastic \ + actions according to models eps value", + ) + boolean_flag( + parser, "dueling", default=False, help="whether or not to use dueling model" + ) + # V: Attack Arguments# + parser.add_argument( + "--model-dir2", + type=str, + default=None, + help="load adversarial model from \ + this directory (blackbox attacks). ", + ) + parser.add_argument( + "--attack", type=str, default=None, help="Method to attack the model." + ) + boolean_flag(parser, "noisy", default=False, help="whether or not to NoisyNetwork") + boolean_flag(parser, "noisy2", default=False, help="whether or not to NoisyNetwork") + boolean_flag( + parser, "blackbox", default=False, help="whether or not to NoisyNetwork" + ) + + return parser.parse_args() + + +def make_env(game_name): + env = gym.make(game_name + "NoFrameskip-v4") + env = SimpleMonitor(env) + env = wrap_dqn(env) + return env + + +def play( + env, + act, + craft_adv_obs, + craft_adv_obs2, + stochastic, + video_path, + attack, + m_target, + m_adv, +): + num_episodes = 0 + num_moves = 0 + num_transfer = 0 + + video_recorder = None + video_recorder = VideoRecorder(env, video_path, enabled=video_path is not None) + obs = env.reset() + while True: + env.unwrapped.render() + video_recorder.capture_frame() + + # V: Attack # + if attack is not None: + # Craft adv. examples + with m_adv.get_session().as_default(): + adv_obs = craft_adv_obs(np.array(obs)[None], stochastic_adv=stochastic)[ + 0 + ] + with m_target.get_session().as_default(): + action = act(np.array(adv_obs)[None], stochastic=stochastic)[0] + action2 = act(np.array(obs)[None], stochastic=stochastic)[0] + num_moves += 1 + if action != action2: + num_transfer += 1 + else: + # Normal + action = act(np.array(obs)[None], stochastic=stochastic)[0] + + obs, rew, done, info = env.step(action) + if done: + obs = env.reset() + + if len(info["rewards"]) > num_episodes: + if len(info["rewards"]) == 1 and video_recorder.enabled: + # save video of first episode + print("Saved video.") + video_recorder.close() + video_recorder.enabled = False + print("Reward: " + str(info["rewards"][-1])) + num_episodes = len(info["rewards"]) + print("Episode: " + str(num_episodes)) + success = float(num_transfer / num_moves) * 100.0 + print("Percentage of successful attacks: " + str(success)) + num_moves = 0 + num_transfer = 0 + + +if __name__ == "__main__": + args = parse_args() + env = make_env(args.env) + g1 = tf.Graph() + g2 = tf.Graph() + with g1.as_default(): + m1 = DQNModel( + env, args.dueling, args.noisy, os.path.join(args.model_dir, "saved") + ) + if args.blackbox: + with g2.as_default(): + m2 = DQNModel( + env, args.dueling, args.noisy2, os.path.join(args.model_dir2, "saved") + ) + with m2.get_session().as_default(): + craft_adv_obs = m2.craft_adv() + with m1.get_session().as_default(): + craft_adv_obs2 = m1.craft_adv() + play( + env, + m1.get_act(), + craft_adv_obs, + craft_adv_obs2, + args.stochastic, + args.video, + args.attack, + m1, + m2, + ) + else: + with m1.get_session().as_default(): + craft_adv_obs = m1.craft_adv() + play( + env, + m1.get_act(), + craft_adv_obs, + None, + args.stochastic, + args.video, + args.attack, + m1, + m1, + ) diff --git a/cleverhans_v3.1.0/examples/RL-attack/model.py b/cleverhans_v3.1.0/examples/RL-attack/model.py new file mode 100644 index 000000000..b93a8180c --- /dev/null +++ b/cleverhans_v3.1.0/examples/RL-attack/model.py @@ -0,0 +1,97 @@ +import tensorflow as tf +import tensorflow.contrib.layers as layers +from rlattack.common.tf_util import noisy_dense + + +def model(img_in, num_actions, scope, noisy=False, reuse=False, concat_softmax=False): + with tf.variable_scope(scope, reuse=reuse): + out = img_in + with tf.variable_scope("convnet"): + # original architecture + out = layers.convolution2d( + out, num_outputs=32, kernel_size=8, stride=4, activation_fn=tf.nn.relu + ) + out = layers.convolution2d( + out, num_outputs=64, kernel_size=4, stride=2, activation_fn=tf.nn.relu + ) + out = layers.convolution2d( + out, num_outputs=64, kernel_size=3, stride=1, activation_fn=tf.nn.relu + ) + out = layers.flatten(out) + + with tf.variable_scope("action_value"): + if noisy: + # Apply noisy network on fully connected layers + # ref: https://arxiv.org/abs/1706.10295 + out = noisy_dense( + out, name="noisy_fc1", size=512, activation_fn=tf.nn.relu + ) + out = noisy_dense(out, name="noisy_fc2", size=num_actions) + else: + out = layers.fully_connected( + out, num_outputs=512, activation_fn=tf.nn.relu + ) + out = layers.fully_connected( + out, num_outputs=num_actions, activation_fn=None + ) + # V: Softmax - inspired by deep-rl-attack # + if concat_softmax: + out = tf.nn.softmax(out) + return out + + +def dueling_model( + img_in, num_actions, scope, noisy=False, reuse=False, concat_softmax=False +): + """As described in https://arxiv.org/abs/1511.06581""" + with tf.variable_scope(scope, reuse=reuse): + out = img_in + with tf.variable_scope("convnet"): + # original architecture + out = layers.convolution2d( + out, num_outputs=32, kernel_size=8, stride=4, activation_fn=tf.nn.relu + ) + out = layers.convolution2d( + out, num_outputs=64, kernel_size=4, stride=2, activation_fn=tf.nn.relu + ) + out = layers.convolution2d( + out, num_outputs=64, kernel_size=3, stride=1, activation_fn=tf.nn.relu + ) + out = layers.flatten(out) + + with tf.variable_scope("state_value"): + if noisy: + # Apply noisy network on fully connected layers + # ref: https://arxiv.org/abs/1706.10295 + state_hidden = noisy_dense( + out, name="noisy_fc1", size=512, activation_fn=tf.nn.relu + ) + state_score = noisy_dense(state_hidden, name="noisy_fc2", size=1) + else: + state_hidden = layers.fully_connected( + out, num_outputs=512, activation_fn=tf.nn.relu + ) + state_score = layers.fully_connected( + state_hidden, num_outputs=1, activation_fn=None + ) + with tf.variable_scope("action_value"): + if noisy: + # Apply noisy network on fully connected layers + # ref: https://arxiv.org/abs/1706.10295 + actions_hidden = noisy_dense( + out, name="noisy_fc1", size=512, activation_fn=tf.nn.relu + ) + action_scores = noisy_dense( + actions_hidden, name="noisy_fc2", size=num_actions + ) + else: + actions_hidden = layers.fully_connected( + out, num_outputs=512, activation_fn=tf.nn.relu + ) + action_scores = layers.fully_connected( + actions_hidden, num_outputs=num_actions, activation_fn=None + ) + action_scores_mean = tf.reduce_mean(action_scores, 1) + action_scores = action_scores - tf.expand_dims(action_scores_mean, 1) + + return state_score + action_scores diff --git a/cleverhans_v3.1.0/examples/RL-attack/train.py b/cleverhans_v3.1.0/examples/RL-attack/train.py new file mode 100644 index 000000000..9342cab0b --- /dev/null +++ b/cleverhans_v3.1.0/examples/RL-attack/train.py @@ -0,0 +1,435 @@ +import argparse +import gym +import numpy as np +import os +import tensorflow as tf +import tempfile +import time +import json +import random + +import rlattack.common.tf_util as U + +from rlattack import logger +from rlattack import deepq +from rlattack.deepq.replay_buffer import ReplayBuffer, PrioritizedReplayBuffer +from rlattack.common.misc_util import ( + boolean_flag, + pickle_load, + pretty_eta, + relatively_safe_pickle_dump, + set_global_seeds, + RunningAvg, + SimpleMonitor, +) +from rlattack.common.schedules import LinearSchedule, PiecewiseSchedule + +# when updating this to non-deprecated ones, it is important to +# copy over LazyFrames +from rlattack.common.atari_wrappers_deprecated import wrap_dqn +from rlattack.common.azure_utils import Container +from model import model, dueling_model +from statistics import statistics + + +def parse_args(): + parser = argparse.ArgumentParser("DQN experiments for Atari games") + # Environment + parser.add_argument("--env", type=str, default="Pong", help="name of the game") + parser.add_argument("--seed", type=int, default=42, help="which seed to use") + # Core DQN parameters + parser.add_argument( + "--replay-buffer-size", type=int, default=int(1e6), help="replay buffer size" + ) + parser.add_argument( + "--lr", type=float, default=1e-4, help="learning rate for Adam optimizer" + ) + parser.add_argument( + "--num-steps", + type=int, + default=int(2e8), + help="total number of steps to \ + run the environment for", + ) + parser.add_argument( + "--batch-size", + type=int, + default=32, + help="number of transitions to optimize \ + at the same time", + ) + parser.add_argument( + "--learning-freq", + type=int, + default=4, + help="number of iterations between \ + every optimization step", + ) + parser.add_argument( + "--target-update-freq", + type=int, + default=40000, + help="number of iterations between \ + every target network update", + ) + # Bells and whistles + boolean_flag(parser, "noisy", default=False, help="whether or not to NoisyNetwork") + boolean_flag( + parser, "double-q", default=True, help="whether or not to use double q learning" + ) + boolean_flag( + parser, "dueling", default=False, help="whether or not to use dueling model" + ) + boolean_flag( + parser, + "prioritized", + default=False, + help="whether or not to use prioritized replay buffer", + ) + parser.add_argument( + "--prioritized-alpha", + type=float, + default=0.6, + help="alpha parameter for prioritized replay buffer", + ) + parser.add_argument( + "--prioritized-beta0", + type=float, + default=0.4, + help="initial value of beta \ + parameters for prioritized replay", + ) + parser.add_argument( + "--prioritized-eps", + type=float, + default=1e-6, + help="eps parameter for prioritized replay buffer", + ) + # Checkpointing + parser.add_argument( + "--save-dir", + type=str, + default=None, + required=True, + help="directory in which \ + training state and model should be saved.", + ) + parser.add_argument( + "--save-azure-container", + type=str, + default=None, + help="It present data will saved/loaded from Azure. \ + Should be in format ACCOUNT_NAME:ACCOUNT_KEY:\ + CONTAINER", + ) + parser.add_argument( + "--save-freq", + type=int, + default=1e6, + help="save model once every time this many \ + iterations are completed", + ) + boolean_flag( + parser, + "load-on-start", + default=True, + help="if true and model was previously saved then training \ + will be resumed", + ) + + # V: Attack Arguments # + parser.add_argument( + "--attack", type=str, default=None, help="Method to attack the model." + ) + parser.add_argument( + "--attack-init", type=int, default=0, help="Iteration no. to begin attacks" + ) + parser.add_argument( + "--attack-prob", + type=float, + default=0.0, + help="Probability of attack at each step, \ + float in range 0 - 1.0", + ) + return parser.parse_args() + + +def make_env(game_name): + env = gym.make(game_name + "NoFrameskip-v4") + monitored_env = SimpleMonitor(env) + env = wrap_dqn(monitored_env) + return env, monitored_env + + +def maybe_save_model(savedir, container, state): + if savedir is None: + return + start_time = time.time() + model_dir = "model-{}".format(state["num_iters"]) + U.save_state(os.path.join(savedir, model_dir, "saved")) + if container is not None: + container.put(os.path.join(savedir, model_dir), model_dir) + relatively_safe_pickle_dump( + state, os.path.join(savedir, "training_state.pkl.zip"), compression=True + ) + if container is not None: + container.put( + os.path.join(savedir, "training_state.pkl.zip"), "training_state.pkl.zip" + ) + relatively_safe_pickle_dump( + state["monitor_state"], os.path.join(savedir, "monitor_state.pkl") + ) + if container is not None: + container.put(os.path.join(savedir, "monitor_state.pkl"), "monitor_state.pkl") + logger.log("Saved model in {} seconds\n".format(time.time() - start_time)) + + +def maybe_load_model(savedir, container): + """Load model if present at the specified path.""" + if savedir is None: + return + + state_path = os.path.join(os.path.join(savedir, "training_state.pkl.zip")) + if container is not None: + logger.log("Attempting to download model from Azure") + found_model = container.get(savedir, "training_state.pkl.zip") + else: + found_model = os.path.exists(state_path) + if found_model: + state = pickle_load(state_path, compression=True) + model_dir = "model-{}".format(state["num_iters"]) + if container is not None: + container.get(savedir, model_dir) + U.load_state(os.path.join(savedir, model_dir, "saved")) + logger.log( + "Loaded models checkpoint at {} iterations".format(state["num_iters"]) + ) + return state + + +if __name__ == "__main__": + args = parse_args() + # Parse savedir and azure container. + savedir = args.save_dir + if args.save_azure_container is not None: + account_name, account_key, container_name = args.save_azure_container.split(":") + container = Container( + account_name=account_name, + account_key=account_key, + container_name=container_name, + maybe_create=True, + ) + if savedir is None: + # Careful! This will not get cleaned up. + savedir = tempfile.TemporaryDirectory().name + else: + container = None + # Create and seed the env. + env, monitored_env = make_env(args.env) + if args.seed > 0: + set_global_seeds(args.seed) + env.unwrapped.seed(args.seed) + + # V: Save arguments, configure log dump path to savedir # + if savedir: + with open(os.path.join(savedir, "args.json"), "w") as f: + json.dump(vars(args), f) + logger.configure(dir=savedir) # log to savedir + + with U.make_session(4) as sess: + # Create training graph and replay buffer + act, train, update_target, debug, craft_adv = deepq.build_train( + make_obs_ph=lambda name: U.Uint8Input( + env.observation_space.shape, name=name + ), + q_func=dueling_model if args.dueling else model, + num_actions=env.action_space.n, + optimizer=tf.train.AdamOptimizer(learning_rate=args.lr, epsilon=1e-4), + gamma=0.99, + grad_norm_clipping=10, + double_q=args.double_q, + noisy=args.noisy, + attack=args.attack, + ) + approximate_num_iters = args.num_steps / 4 + exploration = PiecewiseSchedule( + [ + (0, 1.0), + (approximate_num_iters / 50, 0.1), + (approximate_num_iters / 5, 0.01), + ], + outside_value=0.01, + ) + + if args.prioritized: + replay_buffer = PrioritizedReplayBuffer( + args.replay_buffer_size, args.prioritized_alpha + ) + beta_schedule = LinearSchedule( + approximate_num_iters, initial_p=args.prioritized_beta0, final_p=1.0 + ) + else: + replay_buffer = ReplayBuffer(args.replay_buffer_size) + + U.initialize() + update_target() + num_iters = 0 + + # Load the model + state = maybe_load_model(savedir, container) + if state is not None: + num_iters, replay_buffer = ( + state["num_iters"], + state["replay_buffer"], + ) + monitored_env.set_state(state["monitor_state"]) + + start_time, start_steps = None, None + steps_per_iter = RunningAvg(0.999) + iteration_time_est = RunningAvg(0.999) + obs = env.reset() + # Record the mean of the \sigma + sigma_name_list = [] + sigma_list = [] + for param in tf.trainable_variables(): + # only record the \sigma in the action network + if "sigma" in param.name and "deepq/q_func/action_value" in param.name: + summary_name = ( + param.name.replace("deepq/q_func/action_value/", "") + .replace("/", ".") + .split(":")[0] + ) + sigma_name_list.append(summary_name) + sigma_list.append(tf.reduce_mean(tf.abs(param))) + f_mean_sigma = U.function(inputs=[], outputs=sigma_list) + # Statistics + writer = tf.summary.FileWriter(savedir, sess.graph) + im_stats = statistics( + scalar_keys=["action", "im_reward", "td_errors", "huber_loss"] + + sigma_name_list + ) + ep_stats = statistics(scalar_keys=["ep_reward", "ep_length"]) + # Main trianing loop + ep_length = 0 + while True: + num_iters += 1 + ep_length += 1 + + # V: Perturb observation if we are past the init stage + # and at a designated attack step + # if craft_adv != None and (num_iters >= args.attack_init) + # and ((num_iters - args.attack_init) % args.attack_freq == 0) : + if ( + craft_adv is not None + and (num_iters >= args.attack_init) + and (random.random() <= args.attack_prob) + ): + obs = craft_adv(np.array(obs)[None])[0] + + # Take action and store transition in the replay buffer. + if args.noisy: + # greedily choose + action = act(np.array(obs)[None], stochastic=False)[0] + else: + # epsilon greedy + action = act( + np.array(obs)[None], update_eps=exploration.value(num_iters) + )[0] + new_obs, rew, done, info = env.step(action) + replay_buffer.add(obs, action, rew, new_obs, float(done)) + obs = new_obs + if done: + obs = env.reset() + + if ( + num_iters > max(5 * args.batch_size, args.replay_buffer_size // 20) + and num_iters % args.learning_freq == 0 + ): + # Sample a bunch of transitions from replay buffer + if args.prioritized: + experience = replay_buffer.sample( + args.batch_size, beta=beta_schedule.value(num_iters) + ) + ( + obses_t, + actions, + rewards, + obses_tp1, + dones, + weights, + batch_idxes, + ) = experience + else: + obses_t, actions, rewards, obses_tp1, dones = replay_buffer.sample( + args.batch_size + ) + weights = np.ones_like(rewards) + # Minimize the error in Bellman's and compute TD-error + td_errors, huber_loss = train( + obses_t, actions, rewards, obses_tp1, dones, weights + ) + # Update the priorities in the replay buffer + if args.prioritized: + new_priorities = np.abs(td_errors) + args.prioritized_eps + replay_buffer.update_priorities(batch_idxes, new_priorities) + # Write summary + mean_sigma = f_mean_sigma() + im_stats.add_all_summary( + writer, + [action, rew, np.mean(td_errors), np.mean(huber_loss)] + mean_sigma, + num_iters, + ) + + # Update target network. + if num_iters % args.target_update_freq == 0: + update_target() + + if start_time is not None: + steps_per_iter.update(info["steps"] - start_steps) + iteration_time_est.update(time.time() - start_time) + start_time, start_steps = time.time(), info["steps"] + + # Save the model and training state. + if num_iters > 0 and ( + num_iters % args.save_freq == 0 or info["steps"] > args.num_steps + ): + maybe_save_model( + savedir, + container, + { + "replay_buffer": replay_buffer, + "num_iters": num_iters, + "monitor_state": monitored_env.get_state(), + }, + ) + + if info["steps"] > args.num_steps: + break + + if done: + steps_left = args.num_steps - info["steps"] + completion = np.round(info["steps"] / args.num_steps, 1) + mean_ep_reward = np.mean(info["rewards"][-100:]) + logger.record_tabular("% completion", completion) + logger.record_tabular("steps", info["steps"]) + logger.record_tabular("iters", num_iters) + logger.record_tabular("episodes", len(info["rewards"])) + logger.record_tabular( + "reward (100 epi mean)", np.mean(info["rewards"][-100:]) + ) + if not args.noisy: + logger.record_tabular("exploration", exploration.value(num_iters)) + if args.prioritized: + logger.record_tabular("max priority", replay_buffer._max_priority) + fps_estimate = ( + float(steps_per_iter) / (float(iteration_time_est) + 1e-6) + if steps_per_iter._value is not None + else "calculating:" + ) + logger.dump_tabular() + logger.log() + logger.log("ETA: " + pretty_eta(int(steps_left / fps_estimate))) + logger.log() + # add summary for one episode + ep_stats.add_all_summary(writer, [mean_ep_reward, ep_length], num_iters) + ep_length = 0 diff --git a/examples/adversarial_asr/LibriSpeech/LICENSE.TXT b/cleverhans_v3.1.0/examples/adversarial_asr/LibriSpeech/LICENSE.TXT similarity index 100% rename from examples/adversarial_asr/LibriSpeech/LICENSE.TXT rename to cleverhans_v3.1.0/examples/adversarial_asr/LibriSpeech/LICENSE.TXT diff --git a/examples/adversarial_asr/LibriSpeech/test-clean/2300/131720/2300-131720-0015.wav b/cleverhans_v3.1.0/examples/adversarial_asr/LibriSpeech/test-clean/2300/131720/2300-131720-0015.wav similarity index 100% rename from examples/adversarial_asr/LibriSpeech/test-clean/2300/131720/2300-131720-0015.wav rename to cleverhans_v3.1.0/examples/adversarial_asr/LibriSpeech/test-clean/2300/131720/2300-131720-0015.wav diff --git a/examples/adversarial_asr/LibriSpeech/test-clean/2830/3980/2830-3980-0029.wav b/cleverhans_v3.1.0/examples/adversarial_asr/LibriSpeech/test-clean/2830/3980/2830-3980-0029.wav similarity index 100% rename from examples/adversarial_asr/LibriSpeech/test-clean/2830/3980/2830-3980-0029.wav rename to cleverhans_v3.1.0/examples/adversarial_asr/LibriSpeech/test-clean/2830/3980/2830-3980-0029.wav diff --git a/examples/adversarial_asr/LibriSpeech/test-clean/2961/960/2961-960-0020.wav b/cleverhans_v3.1.0/examples/adversarial_asr/LibriSpeech/test-clean/2961/960/2961-960-0020.wav similarity index 100% rename from examples/adversarial_asr/LibriSpeech/test-clean/2961/960/2961-960-0020.wav rename to cleverhans_v3.1.0/examples/adversarial_asr/LibriSpeech/test-clean/2961/960/2961-960-0020.wav diff --git a/examples/adversarial_asr/LibriSpeech/test-clean/3575/170457/3575-170457-0013.wav b/cleverhans_v3.1.0/examples/adversarial_asr/LibriSpeech/test-clean/3575/170457/3575-170457-0013.wav similarity index 100% rename from examples/adversarial_asr/LibriSpeech/test-clean/3575/170457/3575-170457-0013.wav rename to cleverhans_v3.1.0/examples/adversarial_asr/LibriSpeech/test-clean/3575/170457/3575-170457-0013.wav diff --git a/examples/adversarial_asr/LibriSpeech/test-clean/5105/28241/5105-28241-0006.wav b/cleverhans_v3.1.0/examples/adversarial_asr/LibriSpeech/test-clean/5105/28241/5105-28241-0006.wav similarity index 100% rename from examples/adversarial_asr/LibriSpeech/test-clean/5105/28241/5105-28241-0006.wav rename to cleverhans_v3.1.0/examples/adversarial_asr/LibriSpeech/test-clean/5105/28241/5105-28241-0006.wav diff --git a/examples/adversarial_asr/LibriSpeech/test-clean/5142/36377/5142-36377-0007.wav b/cleverhans_v3.1.0/examples/adversarial_asr/LibriSpeech/test-clean/5142/36377/5142-36377-0007.wav similarity index 100% rename from examples/adversarial_asr/LibriSpeech/test-clean/5142/36377/5142-36377-0007.wav rename to cleverhans_v3.1.0/examples/adversarial_asr/LibriSpeech/test-clean/5142/36377/5142-36377-0007.wav diff --git a/examples/adversarial_asr/LibriSpeech/test-clean/61/70968/61-70968-0011.wav b/cleverhans_v3.1.0/examples/adversarial_asr/LibriSpeech/test-clean/61/70968/61-70968-0011.wav similarity index 100% rename from examples/adversarial_asr/LibriSpeech/test-clean/61/70968/61-70968-0011.wav rename to cleverhans_v3.1.0/examples/adversarial_asr/LibriSpeech/test-clean/61/70968/61-70968-0011.wav diff --git a/examples/adversarial_asr/LibriSpeech/test-clean/61/70968/61-70968-0049.wav b/cleverhans_v3.1.0/examples/adversarial_asr/LibriSpeech/test-clean/61/70968/61-70968-0049.wav similarity index 100% rename from examples/adversarial_asr/LibriSpeech/test-clean/61/70968/61-70968-0049.wav rename to cleverhans_v3.1.0/examples/adversarial_asr/LibriSpeech/test-clean/61/70968/61-70968-0049.wav diff --git a/examples/adversarial_asr/LibriSpeech/test-clean/8224/274381/8224-274381-0007.wav b/cleverhans_v3.1.0/examples/adversarial_asr/LibriSpeech/test-clean/8224/274381/8224-274381-0007.wav similarity index 100% rename from examples/adversarial_asr/LibriSpeech/test-clean/8224/274381/8224-274381-0007.wav rename to cleverhans_v3.1.0/examples/adversarial_asr/LibriSpeech/test-clean/8224/274381/8224-274381-0007.wav diff --git a/examples/adversarial_asr/LibriSpeech/test-clean/8230/279154/8230-279154-0017.wav b/cleverhans_v3.1.0/examples/adversarial_asr/LibriSpeech/test-clean/8230/279154/8230-279154-0017.wav similarity index 100% rename from examples/adversarial_asr/LibriSpeech/test-clean/8230/279154/8230-279154-0017.wav rename to cleverhans_v3.1.0/examples/adversarial_asr/LibriSpeech/test-clean/8230/279154/8230-279154-0017.wav diff --git a/examples/adversarial_asr/README.md b/cleverhans_v3.1.0/examples/adversarial_asr/README.md similarity index 100% rename from examples/adversarial_asr/README.md rename to cleverhans_v3.1.0/examples/adversarial_asr/README.md diff --git a/cleverhans_v3.1.0/examples/adversarial_asr/generate_imperceptible_adv.py b/cleverhans_v3.1.0/examples/adversarial_asr/generate_imperceptible_adv.py new file mode 100644 index 000000000..a54d71c5b --- /dev/null +++ b/cleverhans_v3.1.0/examples/adversarial_asr/generate_imperceptible_adv.py @@ -0,0 +1,627 @@ +import tensorflow as tf +from lingvo import model_imports +from lingvo import model_registry +import numpy as np +import scipy.io.wavfile as wav +import generate_masking_threshold as generate_mask +from tool import Transform, create_features, create_inputs +import time +from lingvo.core import cluster_factory +from absl import flags +from absl import app + +# data directory +flags.DEFINE_string("root_dir", "./", "location of Librispeech") +flags.DEFINE_string( + "input", "read_data.txt", "Input audio .wav file(s), at 16KHz (separated by spaces)" +) + +# data processing +flags.DEFINE_integer("window_size", "2048", "window size in spectrum analysis") +flags.DEFINE_integer( + "max_length_dataset", + "223200", + "the length of the longest audio in the whole dataset", +) +flags.DEFINE_float( + "initial_bound", "2000", "initial l infinity norm for adversarial perturbation" +) + +# training parameters +flags.DEFINE_string("checkpoint", "./model/ckpt-00908156", "location of checkpoint") +flags.DEFINE_integer("batch_size", "5", "batch size") +flags.DEFINE_float("lr_stage1", "100", "learning_rate for stage 1") +flags.DEFINE_float("lr_stage2", "1", "learning_rate for stage 2") +flags.DEFINE_integer("num_iter_stage1", "1000", "number of iterations in stage 1") +flags.DEFINE_integer("num_iter_stage2", "4000", "number of iterations in stage 2") +flags.DEFINE_integer("num_gpu", "0", "which gpu to run") + +FLAGS = flags.FLAGS + + +def ReadFromWav(data, batch_size): + """ + Returns: + audios_np: a numpy array of size (batch_size, max_length) in float + trans: a numpy array includes the targeted transcriptions (batch_size, ) + th_batch: a numpy array of the masking threshold, each of size (?, 1025) + psd_max_batch: a numpy array of the psd_max of the original audio (batch_size) + max_length: the max length of the batch of audios + sample_rate_np: a numpy array + masks: a numpy array of size (batch_size, max_length) + masks_freq: a numpy array of size (batch_size, max_length_freq, 80) + lengths: a list of the length of original audios + """ + audios = [] + lengths = [] + th_batch = [] + psd_max_batch = [] + + # read the .wav file + for i in range(batch_size): + sample_rate_np, audio_temp = wav.read(FLAGS.root_dir + str(data[0, i])) + # read the wav form range from [-32767, 32768] or [-1, 1] + if max(audio_temp) < 1: + audio_np = audio_temp * 32768 + else: + audio_np = audio_temp + + length = len(audio_np) + + audios.append(audio_np) + lengths.append(length) + + max_length = max(lengths) + + # pad the input audio + audios_np = np.zeros([batch_size, max_length]) + masks = np.zeros([batch_size, max_length]) + lengths_freq = (np.array(lengths) // 2 + 1) // 240 * 3 + max_length_freq = max(lengths_freq) + masks_freq = np.zeros([batch_size, max_length_freq, 80]) + for i in range(batch_size): + audio_float = audios[i].astype(float) + audios_np[i, : lengths[i]] = audio_float + masks[i, : lengths[i]] = 1 + masks_freq[i, : lengths_freq[i], :] = 1 + + # compute the masking threshold + th, psd_max = generate_mask.generate_th( + audios_np[i], sample_rate_np, FLAGS.window_size + ) + th_batch.append(th) + psd_max_batch.append(psd_max) + + th_batch = np.array(th_batch) + psd_max_batch = np.array(psd_max_batch) + + # read the transcription + trans = data[2, :] + + return ( + audios_np, + trans, + th_batch, + psd_max_batch, + max_length, + sample_rate_np, + masks, + masks_freq, + lengths, + ) + + +class Attack: + def __init__( + self, + sess, + batch_size=1, + lr_stage1=100, + lr_stage2=0.1, + num_iter_stage1=1000, + num_iter_stage2=4000, + th=None, + psd_max_ori=None, + ): + + self.sess = sess + self.num_iter_stage1 = num_iter_stage1 + self.num_iter_stage2 = num_iter_stage2 + self.batch_size = batch_size + self.lr_stage1 = lr_stage1 + + tf.set_random_seed(1234) + params = model_registry.GetParams("asr.librispeech.Librispeech960Wpm", "Test") + params.random_seed = 1234 + params.is_eval = True + params.cluster.worker.gpus_per_replica = 1 + cluster = cluster_factory.Cluster(params.cluster) + with cluster, tf.device(cluster.GetPlacer()): + model = params.cls(params) + self.delta_large = tf.Variable( + np.zeros((batch_size, FLAGS.max_length_dataset), dtype=np.float32), + name="qq_delta", + ) + + # placeholders + self.input_tf = tf.placeholder( + tf.float32, shape=[batch_size, None], name="qq_input" + ) + self.tgt_tf = tf.placeholder(tf.string) + self.sample_rate_tf = tf.placeholder(tf.int32, name="qq_sample_rate") + self.th = tf.placeholder( + tf.float32, shape=[batch_size, None, None], name="qq_th" + ) + self.psd_max_ori = tf.placeholder( + tf.float32, shape=[batch_size], name="qq_psd" + ) + self.mask = tf.placeholder( + dtype=np.float32, shape=[batch_size, None], name="qq_mask" + ) + self.mask_freq = tf.placeholder( + dtype=np.float32, shape=[batch_size, None, 80] + ) + self.noise = tf.placeholder( + np.float32, shape=[batch_size, None], name="qq_noise" + ) + self.maxlen = tf.placeholder(np.int32) + self.lr_stage2 = tf.placeholder(np.float32) + + # variable + self.rescale = tf.Variable( + np.ones((batch_size, 1), dtype=np.float32), name="qq_rescale" + ) + self.alpha = tf.Variable( + np.ones((batch_size), dtype=np.float32) * 0.05, name="qq_alpha" + ) + + # extract the delta + self.delta = tf.slice( + tf.identity(self.delta_large), [0, 0], [batch_size, self.maxlen] + ) + self.apply_delta = ( + tf.clip_by_value(self.delta, -FLAGS.initial_bound, FLAGS.initial_bound) + * self.rescale + ) + self.new_input = self.apply_delta * self.mask + self.input_tf + self.pass_in = tf.clip_by_value( + self.new_input + self.noise, -(2 ** 15), 2 ** 15 - 1 + ) + + # generate the inputs that are needed for the lingvo model + self.features = create_features( + self.pass_in, self.sample_rate_tf, self.mask_freq + ) + self.inputs = create_inputs( + model, self.features, self.tgt_tf, self.batch_size, self.mask_freq + ) + + task = model.GetTask() + metrics = task.FPropDefaultTheta(self.inputs) + # self.celoss with the shape (batch_size) + self.celoss = tf.get_collection("per_loss")[0] + self.decoded = task.Decode(self.inputs) + + # compute the loss for masking threshold + self.loss_th_list = [] + self.transform = Transform(FLAGS.window_size) + for i in range(self.batch_size): + logits_delta = self.transform( + (self.apply_delta[i, :]), (self.psd_max_ori)[i] + ) + loss_th = tf.reduce_mean(tf.nn.relu(logits_delta - (self.th)[i])) + loss_th = tf.expand_dims(loss_th, dim=0) + self.loss_th_list.append(loss_th) + self.loss_th = tf.concat(self.loss_th_list, axis=0) + + self.optimizer1 = tf.train.AdamOptimizer(self.lr_stage1) + self.optimizer2 = tf.train.AdamOptimizer(self.lr_stage2) + + grad1, var1 = self.optimizer1.compute_gradients( + self.celoss, [self.delta_large] + )[0] + grad21, var21 = self.optimizer2.compute_gradients( + self.celoss, [self.delta_large] + )[0] + grad22, var22 = self.optimizer2.compute_gradients( + self.alpha * self.loss_th, [self.delta_large] + )[0] + + self.train1 = self.optimizer1.apply_gradients([(tf.sign(grad1), var1)]) + self.train21 = self.optimizer2.apply_gradients([(grad21, var21)]) + self.train22 = self.optimizer2.apply_gradients([(grad22, var22)]) + self.train2 = tf.group(self.train21, self.train22) + + def attack_stage1( + self, + audios, + trans, + th_batch, + psd_max_batch, + maxlen, + sample_rate, + masks, + masks_freq, + num_loop, + data, + lr_stage2, + ): + sess = self.sess + # initialize and load the pretrained model + sess.run(tf.initializers.global_variables()) + saver = tf.train.Saver( + [x for x in tf.global_variables() if x.name.startswith("librispeech")] + ) + saver.restore(sess, FLAGS.checkpoint) + + # reassign the variables + sess.run( + tf.assign(self.rescale, np.ones((self.batch_size, 1), dtype=np.float32)) + ) + sess.run( + tf.assign( + self.delta_large, + np.zeros((self.batch_size, FLAGS.max_length_dataset), dtype=np.float32), + ) + ) + + # noise = np.random.normal(scale=2, size=audios.shape) + noise = np.zeros(audios.shape) + feed_dict = { + self.input_tf: audios, + self.tgt_tf: trans, + self.sample_rate_tf: sample_rate, + self.th: th_batch, + self.psd_max_ori: psd_max_batch, + self.mask: masks, + self.mask_freq: masks_freq, + self.noise: noise, + self.maxlen: maxlen, + self.lr_stage2: lr_stage2, + } + losses, predictions = sess.run((self.celoss, self.decoded), feed_dict) + + # show the initial predictions + for i in range(self.batch_size): + print( + "example: {}, loss: {}".format( + num_loop * self.batch_size + i, losses[i] + ) + ) + print("pred:{}".format(predictions["topk_decoded"][i, 0])) + print("targ:{}".format(trans[i].lower())) + print("true: {}".format(data[1, i].lower())) + + # We'll make a bunch of iterations of gradient descent here + now = time.time() + MAX = self.num_iter_stage1 + loss_th = [np.inf] * self.batch_size + final_deltas = [None] * self.batch_size + clock = 0 + + for i in range(MAX): + now = time.time() + + # Actually do the optimization + sess.run(self.train1, feed_dict) + if i % 10 == 0: + d, cl, predictions, new_input = sess.run( + (self.delta, self.celoss, self.decoded, self.new_input), feed_dict + ) + + for ii in range(self.batch_size): + # print out the prediction each 100 iterations + if i % 1000 == 0: + print("pred:{}".format(predictions["topk_decoded"][ii, 0])) + # print("rescale: {}".format(sess.run(self.rescale[ii]))) + if i % 10 == 0: + if i % 100 == 0: + print("example: {}".format(num_loop * self.batch_size + ii)) + print("iteration: {}. loss {}".format(i, cl[ii])) + + if predictions["topk_decoded"][ii, 0] == trans[ii].lower(): + print( + "-------------------------------True--------------------------" + ) + + # update rescale + rescale = sess.run(self.rescale) + if rescale[ii] * FLAGS.initial_bound > np.max(np.abs(d[ii])): + rescale[ii] = np.max(np.abs(d[ii])) / FLAGS.initial_bound + rescale[ii] *= 0.8 + + # save the best adversarial example + final_deltas[ii] = new_input[ii] + + print( + "Iteration i=%d, worked ii=%d celoss=%f bound=%f" + % (i, ii, cl[ii], FLAGS.initial_bound * rescale[ii]) + ) + sess.run(tf.assign(self.rescale, rescale)) + + # in case no final_delta return + if i == MAX - 1 and final_deltas[ii] is None: + final_deltas[ii] = new_input[ii] + + if i % 10 == 0: + print("ten iterations take around {} ".format(clock)) + clock = 0 + + clock += time.time() - now + + return final_deltas + + def attack_stage2( + self, + audios, + trans, + adv, + th_batch, + psd_max_batch, + maxlen, + sample_rate, + masks, + masks_freq, + num_loop, + data, + lr_stage2, + ): + sess = self.sess + # initialize and load the pretrained model + sess.run(tf.initializers.global_variables()) + saver = tf.train.Saver( + [x for x in tf.global_variables() if x.name.startswith("librispeech")] + ) + saver.restore(sess, FLAGS.checkpoint) + + sess.run( + tf.assign(self.rescale, np.ones((self.batch_size, 1), dtype=np.float32)) + ) + sess.run( + tf.assign(self.alpha, np.ones((self.batch_size), dtype=np.float32) * 0.05) + ) + + # reassign the variables + sess.run(tf.assign(self.delta_large, adv)) + + # noise = np.random.normal(scale=2, size=audios.shape) + noise = np.zeros(audios.shape) + feed_dict = { + self.input_tf: audios, + self.tgt_tf: trans, + self.sample_rate_tf: sample_rate, + self.th: th_batch, + self.psd_max_ori: psd_max_batch, + self.mask: masks, + self.mask_freq: masks_freq, + self.noise: noise, + self.maxlen: maxlen, + self.lr_stage2: lr_stage2, + } + losses, predictions = sess.run((self.celoss, self.decoded), feed_dict) + + # show the initial predictions + for i in range(self.batch_size): + print( + "example: {}, loss: {}".format( + num_loop * self.batch_size + i, losses[i] + ) + ) + print("pred:{}".format(predictions["topk_decoded"][i, 0])) + print("targ:{}".format(trans[i].lower())) + print("true: {}".format(data[1, i].lower())) + + # We'll make a bunch of iterations of gradient descent here + now = time.time() + MAX = self.num_iter_stage2 + loss_th = [np.inf] * self.batch_size + final_deltas = [None] * self.batch_size + final_alpha = [None] * self.batch_size + # final_th = [None] * self.batch_size + clock = 0 + min_th = 0.0005 + for i in range(MAX): + now = time.time() + if i == 3000: + # min_th = -np.inf + lr_stage2 = 0.1 + feed_dict = { + self.input_tf: audios, + self.tgt_tf: trans, + self.sample_rate_tf: sample_rate, + self.th: th_batch, + self.psd_max_ori: psd_max_batch, + self.mask: masks, + self.mask_freq: masks_freq, + self.noise: noise, + self.maxlen: maxlen, + self.lr_stage2: lr_stage2, + } + + # Actually do the optimization + sess.run(self.train2, feed_dict) + + if i % 10 == 0: + d, cl, l, predictions, new_input = sess.run( + ( + self.delta, + self.celoss, + self.loss_th, + self.decoded, + self.new_input, + ), + feed_dict, + ) + + for ii in range(self.batch_size): + # print out the prediction each 100 iterations + if i % 1000 == 0: + print("pred:{}".format(predictions["topk_decoded"][ii, 0])) + # print("rescale: {}".format(sess.run(self.rescale[ii]))) + if i % 10 == 0: + # print("example: {}".format(num_loop * self.batch_size + ii)) + + alpha = sess.run(self.alpha) + if i % 100 == 0: + print("example: {}".format(num_loop * self.batch_size + ii)) + print( + "iteration: %d, alpha: %f, loss_ce: %f, loss_th: %f" + % (i, alpha[ii], cl[ii], l[ii]) + ) + + # if the network makes the targeted prediction + if predictions["topk_decoded"][ii, 0] == trans[ii].lower(): + if l[ii] < loss_th[ii]: + final_deltas[ii] = new_input[ii] + loss_th[ii] = l[ii] + final_alpha[ii] = alpha[ii] + print( + "-------------------------------------Succeed---------------------------------" + ) + print( + "save the best example=%d at iteration= %d, alpha = %f" + % (ii, i, alpha[ii]) + ) + + # increase the alpha each 20 iterations + if i % 20 == 0: + alpha[ii] *= 1.2 + sess.run(tf.assign(self.alpha, alpha)) + + # if the network fails to make the targeted prediction, reduce alpha each 50 iterations + if ( + i % 50 == 0 + and predictions["topk_decoded"][ii, 0] != trans[ii].lower() + ): + alpha[ii] *= 0.8 + alpha[ii] = max(alpha[ii], min_th) + sess.run(tf.assign(self.alpha, alpha)) + + # in case no final_delta return + if i == MAX - 1 and final_deltas[ii] is None: + final_deltas[ii] = new_input[ii] + if i % 500 == 0: + print("alpha is {}, loss_th is {}".format(final_alpha, loss_th)) + if i % 10 == 0: + print("ten iterations take around {} ".format(clock)) + clock = 0 + + clock += time.time() - now + + return final_deltas, loss_th, final_alpha + + +def main(argv): + data = np.loadtxt(FLAGS.input, dtype=str, delimiter=",") + data = data[:, FLAGS.num_gpu * 10 : (FLAGS.num_gpu + 1) * 10] + num = len(data[0]) + batch_size = FLAGS.batch_size + num_loops = num / batch_size + assert num % batch_size == 0 + + with tf.device("/gpu:0"): + tfconf = tf.ConfigProto(allow_soft_placement=True) + with tf.Session(config=tfconf) as sess: + # set up the attack class + attack = Attack( + sess, + batch_size=batch_size, + lr_stage1=FLAGS.lr_stage1, + lr_stage2=FLAGS.lr_stage2, + num_iter_stage1=FLAGS.num_iter_stage1, + num_iter_stage2=FLAGS.num_iter_stage2, + ) + + for l in range(num_loops): + data_sub = data[:, l * batch_size : (l + 1) * batch_size] + + # stage 1 + # all the output are numpy arrays + ( + audios, + trans, + th_batch, + psd_max_batch, + maxlen, + sample_rate, + masks, + masks_freq, + lengths, + ) = ReadFromWav(data_sub, batch_size) + adv_example = attack.attack_stage1( + audios, + trans, + th_batch, + psd_max_batch, + maxlen, + sample_rate, + masks, + masks_freq, + l, + data_sub, + FLAGS.lr_stage2, + ) + + # save the adversarial examples in stage 1 + for i in range(batch_size): + print( + "Final distortion for stage 1", + np.max( + np.abs( + adv_example[i][: lengths[i]] - audios[i, : lengths[i]] + ) + ), + ) + name, _ = data_sub[0, i].split(".") + saved_name = FLAGS.root_dir + str(name) + "_stage1.wav" + adv_example_float = adv_example[i] / 32768.0 + wav.write( + saved_name, 16000, np.array(adv_example_float[: lengths[i]]) + ) + print(saved_name) + + # stage 2 + # read the adversarial examples saved in stage 1 + adv = np.zeros([batch_size, FLAGS.max_length_dataset]) + adv[:, :maxlen] = adv_example - audios + + adv_example, loss_th, final_alpha = attack.attack_stage2( + audios, + trans, + adv, + th_batch, + psd_max_batch, + maxlen, + sample_rate, + masks, + masks_freq, + l, + data_sub, + FLAGS.lr_stage2, + ) + + # save the adversarial examples in stage 2 + for i in range(batch_size): + print("example: {}".format(i)) + print( + "Final distortion for stage 2: {}, final alpha is {}, final loss_th is {}".format( + np.max( + np.abs( + adv_example[i][: lengths[i]] + - audios[i, : lengths[i]] + ) + ), + final_alpha[i], + loss_th[i], + ) + ) + + name, _ = data_sub[0, i].split(".") + saved_name = FLAGS.root_dir + str(name) + "_stage2.wav" + adv_example[i] = adv_example[i] / 32768.0 + wav.write(saved_name, 16000, np.array(adv_example[i][: lengths[i]])) + print(saved_name) + + +if __name__ == "__main__": + app.run(main) diff --git a/examples/adversarial_asr/generate_masking_threshold.py b/cleverhans_v3.1.0/examples/adversarial_asr/generate_masking_threshold.py similarity index 67% rename from examples/adversarial_asr/generate_masking_threshold.py rename to cleverhans_v3.1.0/examples/adversarial_asr/generate_masking_threshold.py index a75ca4793..92667a3a0 100644 --- a/examples/adversarial_asr/generate_masking_threshold.py +++ b/cleverhans_v3.1.0/examples/adversarial_asr/generate_masking_threshold.py @@ -6,32 +6,41 @@ import scipy import librosa + def compute_PSD_matrix(audio, window_size): """ - First, perform STFT. - Then, compute the PSD. - Last, normalize PSD. + First, perform STFT. + Then, compute the PSD. + Last, normalize PSD. """ - win = np.sqrt(8.0/3.) * librosa.core.stft(audio, center=False) + win = np.sqrt(8.0 / 3.0) * librosa.core.stft(audio, center=False) z = abs(win / window_size) - psd_max = np.max(z*z) + psd_max = np.max(z * z) psd = 10 * np.log10(z * z + 0.0000000000000000001) PSD = 96 - np.max(psd) + psd - return PSD, psd_max + return PSD, psd_max + def Bark(f): """returns the bark-scale value for input frequency f (in Hz)""" - return 13*np.arctan(0.00076*f) + 3.5*np.arctan(pow(f/7500.0, 2)) + return 13 * np.arctan(0.00076 * f) + 3.5 * np.arctan(pow(f / 7500.0, 2)) + def quiet(f): - """returns threshold in quiet measured in SPL at frequency f with an offset 12(in Hz)""" - thresh = 3.64*pow(f*0.001,-0.8) - 6.5*np.exp(-0.6*pow(0.001*f-3.3,2)) + 0.001*pow(0.001*f,4) - 12 - return thresh + """returns threshold in quiet measured in SPL at frequency f with an offset 12(in Hz)""" + thresh = ( + 3.64 * pow(f * 0.001, -0.8) + - 6.5 * np.exp(-0.6 * pow(0.001 * f - 3.3, 2)) + + 0.001 * pow(0.001 * f, 4) + - 12 + ) + return thresh + def two_slops(bark_psd, delta_TM, bark_maskee): """ - returns the masking threshold for each masker using two slopes as the spread function + returns the masking threshold for each masker using two slopes as the spread function """ Ts = [] for tone_mask in range(bark_psd.shape[0]): @@ -40,20 +49,21 @@ def two_slops(bark_psd, delta_TM, bark_maskee): zero_index = np.argmax(dz > 0) sf = np.zeros(len(dz)) sf[:zero_index] = 27 * dz[:zero_index] - sf[zero_index:] = (-27 + 0.37 * max(bark_psd[tone_mask, 1] - 40, 0)) * dz[zero_index:] + sf[zero_index:] = (-27 + 0.37 * max(bark_psd[tone_mask, 1] - 40, 0)) * dz[ + zero_index: + ] T = bark_psd[tone_mask, 1] + delta_TM[tone_mask] + sf Ts.append(T) return Ts - + + def compute_th(PSD, barks, ATH, freqs): - """ returns the global masking threshold - """ + """returns the global masking threshold""" # Identification of tonal maskers # find the index of maskers that are the local maxima length = len(PSD) masker_index = signal.argrelextrema(PSD, np.greater)[0] - - + # delete the boundary of maskers for smoothing if 0 in masker_index: masker_index = np.delete(0) @@ -62,12 +72,12 @@ def compute_th(PSD, barks, ATH, freqs): num_local_max = len(masker_index) # treat all the maskers as tonal (conservative way) - # smooth the PSD - p_k = pow(10, PSD[masker_index]/10.) - p_k_prev = pow(10, PSD[masker_index - 1]/10.) - p_k_post = pow(10, PSD[masker_index + 1]/10.) + # smooth the PSD + p_k = pow(10, PSD[masker_index] / 10.0) + p_k_prev = pow(10, PSD[masker_index - 1] / 10.0) + p_k_post = pow(10, PSD[masker_index + 1] / 10.0) P_TM = 10 * np.log10(p_k_prev + p_k + p_k_post) - + # bark_psd: the first column bark, the second column: P_TM, the third column: the index of points _BARK = 0 _PSD = 1 @@ -76,59 +86,55 @@ def compute_th(PSD, barks, ATH, freqs): bark_psd[:, _BARK] = barks[masker_index] bark_psd[:, _PSD] = P_TM bark_psd[:, _INDEX] = masker_index - - # delete the masker that doesn't have the highest PSD within 0.5 Bark around its frequency + + # delete the masker that doesn't have the highest PSD within 0.5 Bark around its frequency for i in range(num_local_max): next = i + 1 if next >= bark_psd.shape[0]: break - - while bark_psd[next, _BARK] - bark_psd[i, _BARK] < 0.5: + + while bark_psd[next, _BARK] - bark_psd[i, _BARK] < 0.5: # masker must be higher than quiet threshold if quiet(freqs[int(bark_psd[i, _INDEX])]) > bark_psd[i, _PSD]: bark_psd = np.delete(bark_psd, (i), axis=0) if next == bark_psd.shape[0]: break - + if bark_psd[i, _PSD] < bark_psd[next, _PSD]: bark_psd = np.delete(bark_psd, (i), axis=0) else: bark_psd = np.delete(bark_psd, (next), axis=0) if next == bark_psd.shape[0]: - break - + break + # compute the individual masking threshold - delta_TM = 1 * (-6.025 -0.275 * bark_psd[:, 0]) - Ts = two_slops(bark_psd, delta_TM, barks) + delta_TM = 1 * (-6.025 - 0.275 * bark_psd[:, 0]) + Ts = two_slops(bark_psd, delta_TM, barks) Ts = np.array(Ts) - + # compute the global masking threshold - theta_x = np.sum(pow(10, Ts/10.), axis=0) + pow(10, ATH/10.) - + theta_x = np.sum(pow(10, Ts / 10.0), axis=0) + pow(10, ATH / 10.0) + return theta_x + def generate_th(audio, fs, window_size=2048): """ - returns the masking threshold theta_xs and the max psd of the audio + returns the masking threshold theta_xs and the max psd of the audio """ - PSD, psd_max= compute_PSD_matrix(audio , window_size) + PSD, psd_max = compute_PSD_matrix(audio, window_size) freqs = librosa.core.fft_frequencies(fs, window_size) barks = Bark(freqs) - # compute the quiet threshold + # compute the quiet threshold ATH = np.zeros(len(barks)) - np.inf bark_ind = np.argmax(barks > 1) ATH[bark_ind:] = quiet(freqs[bark_ind:]) - # compute the global masking threshold theta_xs + # compute the global masking threshold theta_xs theta_xs = [] # compute the global masking threshold in each window for i in range(PSD.shape[1]): - theta_xs.append(compute_th(PSD[:,i], barks, ATH, freqs)) + theta_xs.append(compute_th(PSD[:, i], barks, ATH, freqs)) theta_xs = np.array(theta_xs) return theta_xs, psd_max - - - - - diff --git a/cleverhans_v3.1.0/examples/adversarial_asr/generate_robust_adv.py b/cleverhans_v3.1.0/examples/adversarial_asr/generate_robust_adv.py new file mode 100644 index 000000000..32cf1556b --- /dev/null +++ b/cleverhans_v3.1.0/examples/adversarial_asr/generate_robust_adv.py @@ -0,0 +1,692 @@ +import tensorflow as tf +from lingvo import model_imports +from lingvo import model_registry +import numpy as np +import scipy.io.wavfile as wav +import generate_masking_threshold as generate_mask +from tool import create_features, create_inputs, create_speech_rir +import time +from lingvo.core import cluster_factory +from absl import flags +from absl import app +import random + +# data directory +flags.DEFINE_string("root_dir", "./", "location of Librispeech") +flags.DEFINE_string( + "input", + "read_data.txt", + "the text file saved the dir of audios and the corresponding original and targeted transcriptions", +) +flags.DEFINE_string( + "rir_dir", + "LibriSpeech/test-clean/3575/170457/3575-170457-0013", + "directory of generated room reverberations", +) + +# data processing +flags.DEFINE_integer( + "max_length_dataset", + "223200", + "the length of the longest audio in the whole dataset", +) +flags.DEFINE_float( + "initial_bound", "2000.", "initial l infinity norm for adversarial perturbation" +) +flags.DEFINE_integer( + "num_rir", "1000", "number of room reverberations used in training" +) +flags.DEFINE_integer( + "num_counter", "2", "the initial number of required successful rooms" +) +flags.DEFINE_integer("num_rooms", "10", "the initial number of rooms to test") +flags.DEFINE_integer( + "max_delta", "300", "the max delta added to the max l infinity norm" +) + +# training parameters +flags.DEFINE_string("checkpoint", "./model/ckpt-00908156", "location of checkpoint") +flags.DEFINE_integer("batch_size", "5", "batch size") +flags.DEFINE_float("lr_stage1", "50", "learning_rate for stage 1") +flags.DEFINE_float("lr_stage2", "5", "learning_rate for stage 2") +flags.DEFINE_integer("num_iter_stage1", "2000", "number of iterations in stage 1") +flags.DEFINE_integer("num_iter_stage2", "4000", "number of iterations in stage 2") +flags.DEFINE_integer("num_gpu", "0", "which gpu to run") + + +FLAGS = flags.FLAGS + + +def ReadFromWav(data, batch_size): + """ + Returns: + audios_np: a numpy array of size (batch_size, max_length) in float + trans: a numpy array includes the targeted transcriptions (batch_size, ) + max_length: the max length of the batch of audios + sample_rate_np: a numpy array + masks: a numpy array of size (batch_size, max_length) + masks_freq: a numpy array of size (batch_size, max_length_freq, 80) + lengths: a list of the length of original audios + """ + audios = [] + lengths = [] + + # read the .wav file + for i in range(batch_size): + sample_rate_np, audio_temp = wav.read(FLAGS.root_dir + str(data[0, i])) + # read the wav form range from [-32767, 32768] or [-1, 1] + if max(audio_temp) < 1: + audio_np = audio_temp * 32768 + else: + audio_np = audio_temp + + length = len(audio_np) + + audios.append(audio_np) + lengths.append(length) + + max_length = max(lengths) + + # pad the input audio + audios_np = np.zeros([batch_size, max_length]) + masks = np.zeros([batch_size, max_length]) + lengths_freq = (np.array(lengths) // 2 + 1) // 240 * 3 + max_length_freq = max(lengths_freq) + masks_freq = np.zeros([batch_size, max_length_freq, 80]) + for i in range(batch_size): + audio_float = audios[i].astype(float) + audios_np[i, : lengths[i]] = audio_float + masks[i, : lengths[i]] = 1 + masks_freq[i, : lengths_freq[i], :] = 1 + + # read the transcription + trans = data[2, :] + lengths = np.array(lengths).astype(np.int32) + + return audios_np, trans, max_length, sample_rate_np, masks, masks_freq, lengths + + +def Readrir(): + """ + Return: + rir: a numpy array of the room reverberation + + """ + index = random.randint(1, FLAGS.num_rir) + _, rir = wav.read(FLAGS.root_dir + FLAGS.rir_dir + "_rir_" + str(index) + ".wav") + return rir + + +class Attack: + def __init__( + self, + sess, + batch_size=1, + lr_stage1=100, + lr_stage2=0.1, + num_iter_stage1=1000, + num_iter_stage2=4000, + th=None, + psd_max_ori=None, + ): + + self.sess = sess + self.num_iter_stage1 = num_iter_stage1 + self.num_iter_stage2 = num_iter_stage2 + self.batch_size = batch_size + self.lr_stage1 = lr_stage1 + self.lr_stage2 = lr_stage2 + + tf.set_random_seed(1234) + params = model_registry.GetParams("asr.librispeech.Librispeech960Wpm", "Test") + params.random_seed = 1234 + params.is_eval = True + params.cluster.worker.gpus_per_replica = 1 + cluster = cluster_factory.Cluster(params.cluster) + with cluster, tf.device(cluster.GetPlacer()): + model = params.cls(params) + self.delta_large = tf.Variable( + np.zeros((batch_size, FLAGS.max_length_dataset), dtype=np.float32), + name="qq_delta", + ) + + # placeholders + self.input_tf = tf.placeholder( + tf.float32, shape=[batch_size, None], name="qq_input" + ) + self.tgt_tf = tf.placeholder(tf.string) + self.rir = tf.placeholder(tf.float32) + + self.sample_rate_tf = tf.placeholder(tf.int32, name="qq_sample_rate") + self.mask = tf.placeholder( + dtype=np.float32, shape=[batch_size, None], name="qq_mask" + ) + self.mask_freq = tf.placeholder( + dtype=np.float32, shape=[batch_size, None, 80] + ) + self.noise = tf.placeholder( + np.float32, shape=[batch_size, None], name="qq_noise" + ) + self.maxlen = tf.placeholder(np.int32) + self.lr = tf.placeholder(np.float32) + self.lengths = tf.placeholder( + np.int32, + shape=[ + batch_size, + ], + ) + + # variable + self.rescale = tf.Variable( + np.ones((batch_size, 1), dtype=np.float32) * FLAGS.initial_bound, + name="qq_rescale", + ) + + # extract the delta + self.delta = tf.slice( + tf.identity(self.delta_large), [0, 0], [batch_size, self.maxlen] + ) + self.apply_delta = tf.clip_by_value(self.delta, -self.rescale, self.rescale) + self.before_rir = tf.clip_by_value( + self.apply_delta * self.mask + self.input_tf, -(2 ** 15), 2 ** 15 - 1 + ) + self.new_input = ( + create_speech_rir( + self.before_rir, + self.rir, + self.lengths, + self.maxlen, + self.batch_size, + ) + * self.mask + ) + self.pass_in = tf.clip_by_value( + self.new_input + self.noise, -(2 ** 15), 2 ** 15 - 1 + ) + + # generate the inputs that are needed for the lingvo model + self.features = create_features( + self.pass_in, self.sample_rate_tf, self.mask_freq + ) + self.inputs = create_inputs( + model, self.features, self.tgt_tf, self.batch_size, self.mask_freq + ) + + task = model.GetTask() + metrics = task.FPropDefaultTheta(self.inputs) + + # self.celoss with the shape (batch_size) + self.celoss = tf.get_collection("per_loss")[0] + self.decoded = task.Decode(self.inputs) + + self.optimizer1 = tf.train.AdamOptimizer(self.lr) + grad1, var1 = self.optimizer1.compute_gradients( + self.celoss, [self.delta_large] + )[0] + self.train1 = self.optimizer1.apply_gradients([(tf.sign(grad1), var1)]) + + def attack_stage1( + self, + audios, + trans, + maxlen, + sample_rate, + masks, + masks_freq, + num_loop, + data, + lengths, + ): + """ + The first stage saves the adversarial examples that can successfully attack one room + """ + + sess = self.sess + # initialize and load the pretrained model + sess.run(tf.initializers.global_variables()) + saver = tf.train.Saver( + [x for x in tf.global_variables() if x.name.startswith("librispeech")] + ) + saver.restore(sess, FLAGS.checkpoint) + + # reassign the variables + sess.run( + tf.assign( + self.rescale, + np.ones((self.batch_size, 1), dtype=np.float32) * FLAGS.initial_bound, + ) + ) + sess.run( + tf.assign( + self.delta_large, + np.zeros((self.batch_size, FLAGS.max_length_dataset), dtype=np.float32), + ) + ) + + noise = np.zeros(audios.shape) + rir = Readrir() + feed_dict = { + self.input_tf: audios, + self.tgt_tf: trans, + self.sample_rate_tf: sample_rate, + self.mask: masks, + self.mask_freq: masks_freq, + self.noise: noise, + self.maxlen: maxlen, + self.lengths: lengths, + self.rir: rir, + self.lr: self.lr_stage1, + } + losses, predictions = sess.run((self.celoss, self.decoded), feed_dict) + + # show the initial predictions + for i in range(self.batch_size): + print( + "example: {}, loss: {}".format( + num_loop * self.batch_size + i, losses[i] + ) + ) + print("pred:{}".format(predictions["topk_decoded"][i, 0])) + print("targ:{}".format(trans[i].lower())) + print("true: {}".format(data[1, i].lower())) + + # We'll make a bunch of iterations of gradient descent here + now = time.time() + MAX = self.num_iter_stage1 + loss_th = [np.inf] * self.batch_size + final_adv = [None] * self.batch_size + final_perturb = [None] * self.batch_size + clock = 0 + + for i in range(1, MAX + 1): + now = time.time() + + rir = Readrir() + feed_dict = { + self.input_tf: audios, + self.tgt_tf: trans, + self.sample_rate_tf: sample_rate, + self.mask: masks, + self.mask_freq: masks_freq, + self.noise: noise, + self.maxlen: maxlen, + self.lengths: lengths, + self.rir: rir, + self.lr: self.lr_stage1, + } + losses, predictions = sess.run((self.celoss, self.decoded), feed_dict) + + # Actually do the optimization + sess.run(self.train1, feed_dict) + if i % 10 == 0: + d, apply_delta, cl, predictions, new_input = sess.run( + ( + self.delta, + self.apply_delta, + self.celoss, + self.decoded, + self.new_input, + ), + feed_dict, + ) + + for ii in range(self.batch_size): + if i % 100 == 0: + print("example: {}".format(num_loop * self.batch_size + ii)) + print("iteration: {}. loss {}".format(i, cl[ii])) + print("pred:{}".format(predictions["topk_decoded"][ii, 0])) + print("targ:{}".format(trans[ii].lower())) + + if i % 10 == 0: + if predictions["topk_decoded"][ii, 0] == trans[ii].lower(): + print( + "-------------------------------True--------------------------" + ) + rescale = sess.run(self.rescale) + # update rescale + if i % 10 == 0: + if rescale[ii] > np.max(np.abs(d[ii])): + rescale[ii] = np.max(np.abs(d[ii])) + rescale[ii] *= 0.8 + print( + "Iteration i=%d, worked ii=%d celoss=%f bound=%f" + % (i, ii, cl[ii], rescale[ii]) + ) + sess.run(tf.assign(self.rescale, rescale)) + + # save the best adversarial example + final_adv[ii] = new_input[ii] + final_perturb[ii] = apply_delta[ii] + print( + "Stage 1: save the example at iteration i=%d example ii=%d celoss=%f bound=%f" + % (i, ii, cl[ii], rescale[ii]) + ) + + # in case no final_delta return + if i == MAX - 1 and final_adv[ii] is None: + final_adv[ii] = new_input[ii] + final_perturb[ii] = apply_delta[ii] + + if i % 10 == 0: + print("ten iterations take around {} ".format(clock)) + clock = 0 + + clock += time.time() - now + + return final_adv, final_perturb + + def attack_stage2( + self, + audios, + trans, + adv, + rescales, + maxlen, + sample_rate, + masks, + masks_freq, + num_loop, + data, + lengths, + ): + sess = self.sess + # initialize and load the pretrained model + sess.run(tf.initializers.global_variables()) + saver = tf.train.Saver( + [x for x in tf.global_variables() if x.name.startswith("librispeech")] + ) + saver.restore(sess, FLAGS.checkpoint) + + # reassign the variables + sess.run(tf.assign(self.delta_large, adv)) + sess.run(tf.assign(self.rescale, rescales)) + + noise = np.zeros(audios.shape) + rir = Readrir() + feed_dict = { + self.input_tf: audios, + self.tgt_tf: trans, + self.sample_rate_tf: sample_rate, + self.mask: masks, + self.mask_freq: masks_freq, + self.noise: noise, + self.maxlen: maxlen, + self.lengths: lengths, + self.rir: rir, + self.lr: self.lr_stage2, + } + losses, predictions = sess.run((self.celoss, self.decoded), feed_dict) + + # show the initial predictions + for i in range(self.batch_size): + print( + "example: {}, loss: {}".format( + num_loop * self.batch_size + i, losses[i] + ) + ) + print("pred:{}".format(predictions["topk_decoded"][i, 0])) + print("targ:{}".format(trans[i].lower())) + print("true: {}".format(data[1, i].lower())) + + # We'll make a bunch of iterations of gradient descent here + now = time.time() + MAX = self.num_iter_stage2 + loss_th = [np.inf] * self.batch_size + final_adv = [None] * self.batch_size + final_perturb = [None] * self.batch_size + num_counters = [FLAGS.num_counter] * self.batch_size + num_rooms = [FLAGS.num_rooms] * self.batch_size + clock = 0 + + for i in range(1, MAX + 1): + now = time.time() + + rir = Readrir() + feed_dict = { + self.input_tf: audios, + self.tgt_tf: trans, + self.sample_rate_tf: sample_rate, + self.mask: masks, + self.mask_freq: masks_freq, + self.noise: noise, + self.maxlen: maxlen, + self.lengths: lengths, + self.rir: rir, + self.lr: self.lr_stage2, + } + losses, predictions = sess.run((self.celoss, self.decoded), feed_dict) + + # Actually do the optimization + sess.run(self.train1, feed_dict) + if i % 10 == 0: + d, apply_delta, cl, predictions, new_input = sess.run( + ( + self.delta, + self.apply_delta, + self.celoss, + self.decoded, + self.new_input, + ), + feed_dict, + ) + + for ii in range(self.batch_size): + if i % 100 == 0: + print("example: {}".format(num_loop * self.batch_size + ii)) + print("iteration: {}. loss {}".format(i, cl[ii])) + print("pred:{}".format(predictions["topk_decoded"][ii, 0])) + print("targ:{}".format(trans[ii].lower())) + + sum_counter = 0 + if i % 10 == 0: + for counter in range(num_rooms[ii]): + if predictions["topk_decoded"][ii, 0] == trans[ii].lower(): + sum_counter += 1 + print("succeed %d times for example %d" % (sum_counter, ii)) + rir = Readrir() + feed_dict = { + self.input_tf: audios, + self.tgt_tf: trans, + self.sample_rate_tf: sample_rate, + self.mask: masks, + self.mask_freq: masks_freq, + self.noise: noise, + self.maxlen: maxlen, + self.lengths: lengths, + self.rir: rir, + self.lr: self.lr_stage2, + } + predictions = sess.run(self.decoded, feed_dict) + + if sum_counter == num_counters[ii]: + print( + "-------------------------------True--------------------------" + ) + print( + " The num_counter is %d for example %d" + % (num_counters[ii], ii) + ) + num_counters[ii] += 1 + if num_counters[ii] > num_rooms[ii]: + num_rooms[ii] += 1 + # save the best adversarial example + final_adv[ii] = new_input[ii] + final_perturb[ii] = apply_delta[ii] + print( + "Stage 2: save the example at iteration i=%d example ii=%d celoss=%f" + % (i, ii, cl[ii]) + ) + + # in case no final_delta return + if i == MAX - 1 and final_adv[ii] is None: + final_adv[ii] = new_input[ii] + final_perturb[ii] = apply_delta[ii] + + if i % 10 == 0: + print("ten iterations take around {} ".format(clock)) + clock = 0 + clock += time.time() - now + + return final_adv, final_perturb + + +def main(argv): + data = np.loadtxt(FLAGS.input, dtype=str, delimiter=",") + data = data[:, FLAGS.num_gpu * 10 : (FLAGS.num_gpu + 1) * 10] + num = len(data[0]) + batch_size = FLAGS.batch_size + num_loops = num / batch_size + assert num % batch_size == 0 + + with tf.device("/gpu:0"): + tfconf = tf.ConfigProto(allow_soft_placement=True) + with tf.Session(config=tfconf) as sess: + # set up the attack class + attack = Attack( + sess, + batch_size=batch_size, + lr_stage1=FLAGS.lr_stage1, + lr_stage2=FLAGS.lr_stage2, + num_iter_stage1=FLAGS.num_iter_stage1, + num_iter_stage2=FLAGS.num_iter_stage2, + ) + + for l in range(num_loops): + data_sub = data[:, l * batch_size : (l + 1) * batch_size] + + # stage 1 + # all the output are numpy arrays + ( + audios, + trans, + maxlen, + sample_rate, + masks, + masks_freq, + lengths, + ) = ReadFromWav(data_sub, batch_size) + adv_example, perturb = attack.attack_stage1( + audios, + trans, + maxlen, + sample_rate, + masks, + masks_freq, + l, + data_sub, + lengths, + ) + + # save the adversarial examples in stage 1 that can only successfully attack one simulated room + for i in range(batch_size): + print( + "Final distortion for stage 1", + np.max( + np.abs( + adv_example[i][: lengths[i]] - audios[i, : lengths[i]] + ) + ), + ) + name, _ = data_sub[0, i].split(".") + saved_name = ( + FLAGS.root_dir + str(name) + "_robust_speechrir_stage1.wav" + ) + adv_example_float = adv_example[i] / 32768.0 + wav.write( + saved_name, + 16000, + np.array( + np.clip( + adv_example_float[: lengths[i]], -(2 ** 15), 2 ** 15 - 1 + ) + ), + ) + + saved_name = ( + FLAGS.root_dir + str(name) + "_robust_perturb_stage1.wav" + ) + perturb_float = perturb[i] / 32768.0 + wav.write( + saved_name, + 16000, + np.array( + np.clip( + perturb_float[: lengths[i]], -(2 ** 15), 2 ** 15 - 1 + ) + ), + ) + print(saved_name) + + # stage 2 + # read the adversarial examples saved in stage 1 + adv = np.zeros([batch_size, FLAGS.max_length_dataset]) + adv[:, :maxlen] = adv_example - audios + rescales = np.max(np.abs(adv), axis=1) + FLAGS.max_delta + rescales = np.expand_dims(rescales, axis=1) + + ( + audios, + trans, + maxlen, + sample_rate, + masks, + masks_freq, + lengths, + ) = ReadFromWav(data_sub, batch_size) + adv_example, perturb = attack.attack_stage2( + audios, + trans, + adv, + rescales, + maxlen, + sample_rate, + masks, + masks_freq, + l, + data_sub, + lengths, + ) + + # save the adversarial examples in stage 2 that can successfully attack a set of simulated rooms + for i in range(batch_size): + print( + "Final distortion for stage 2", + np.max( + np.abs( + adv_example[i][: lengths[i]] - audios[i, : lengths[i]] + ) + ), + ) + name, _ = data_sub[0, i].split(".") + saved_name = ( + FLAGS.root_dir + str(name) + "_robust_speechrir_stage2.wav" + ) + adv_example_float = adv_example[i] / 32768.0 + wav.write( + saved_name, + 16000, + np.array( + np.clip( + adv_example_float[: lengths[i]], -(2 ** 15), 2 ** 15 - 1 + ) + ), + ) + + saved_name = ( + FLAGS.root_dir + str(name) + "_robust_perturb_stage2.wav" + ) + perturb_float = perturb[i] / 32768.0 + wav.write( + saved_name, + 16000, + np.array( + np.clip( + perturb_float[: lengths[i]], -(2 ** 15), 2 ** 15 - 1 + ) + ), + ) + print(saved_name) + + +if __name__ == "__main__": + app.run(main) diff --git a/examples/adversarial_asr/model/checkpoint b/cleverhans_v3.1.0/examples/adversarial_asr/model/checkpoint similarity index 100% rename from examples/adversarial_asr/model/checkpoint rename to cleverhans_v3.1.0/examples/adversarial_asr/model/checkpoint diff --git a/examples/adversarial_asr/model/ckpt-00908156.index b/cleverhans_v3.1.0/examples/adversarial_asr/model/ckpt-00908156.index similarity index 100% rename from examples/adversarial_asr/model/ckpt-00908156.index rename to cleverhans_v3.1.0/examples/adversarial_asr/model/ckpt-00908156.index diff --git a/examples/adversarial_asr/model/ckpt-00908156.meta b/cleverhans_v3.1.0/examples/adversarial_asr/model/ckpt-00908156.meta similarity index 100% rename from examples/adversarial_asr/model/ckpt-00908156.meta rename to cleverhans_v3.1.0/examples/adversarial_asr/model/ckpt-00908156.meta diff --git a/examples/adversarial_asr/model/events.out.tfevents.1543301105.e33cfcb49883 b/cleverhans_v3.1.0/examples/adversarial_asr/model/events.out.tfevents.1543301105.e33cfcb49883 similarity index 100% rename from examples/adversarial_asr/model/events.out.tfevents.1543301105.e33cfcb49883 rename to cleverhans_v3.1.0/examples/adversarial_asr/model/events.out.tfevents.1543301105.e33cfcb49883 diff --git a/examples/adversarial_asr/read_data.txt b/cleverhans_v3.1.0/examples/adversarial_asr/read_data.txt similarity index 100% rename from examples/adversarial_asr/read_data.txt rename to cleverhans_v3.1.0/examples/adversarial_asr/read_data.txt diff --git a/cleverhans_v3.1.0/examples/adversarial_asr/room_simulator.py b/cleverhans_v3.1.0/examples/adversarial_asr/room_simulator.py new file mode 100644 index 000000000..a4fb5b242 --- /dev/null +++ b/cleverhans_v3.1.0/examples/adversarial_asr/room_simulator.py @@ -0,0 +1,106 @@ +import numpy as np +import matplotlib.pyplot as plt +from scipy.io import wavfile +from scipy.signal import fftconvolve +import pyroomacoustics as pra +import random +import pickle + + +def clip(signal, high, low): + """ + Clip a signal from above at high and from below at low. + """ + s = signal.copy() + + s[np.where(s > high)] = high + s[np.where(s < low)] = low + + return s + + +def normalize(signal, bits=None): + """ + normalize to be in a given range. + """ + + s = signal.copy() + s /= np.abs(s).max() + + # if one wants to scale for bits allocated + if bits is not None: + s *= 2 ** (bits - 1) - 1 + s = clip(s, 2 ** (bits - 1) - 1, -(2 ** (bits - 1))) + + return s + + +# name of the rir +data = np.loadtxt("./read_data.txt", dtype=str, delimiter=",") +name = data[0, 0] +name_sub, _ = name.split(".") + +# read one audio +fs, signal = wavfile.read(name) + +temp = 0 +room_settings = [] +# set the number of room reverberations that you want to create +num_rooms = 1500 + +for i in range(num_rooms): + print("Saved room reverberation: " + str(i)) + width = random.randint(3, 5) + length = random.randint(4, 6) + height = random.randint(2, 4) + + room_dim = [width, length, height] + x_source = random.randint(0, width * 10) / 10.0 + y_source = random.randint(0, length * 10) / 10.0 + z_source = random.randint(0, height * 10) / 10.0 + + x_mic = random.randint(0, width * 10) / 10.0 + y_mic = random.randint(0, length * 10) / 10.0 + z_mic = random.randint(0, height * 10) / 10.0 + + source = [x_source, y_source, z_source] + microphone = np.array([[x_mic], [y_mic], [z_mic]]) + + room_setting = [ + width, + length, + height, + x_source, + y_source, + z_source, + x_mic, + y_mic, + z_mic, + ] + + if room_setting not in room_settings: + temp += 1 + + room_settings.append(room_setting) + max_order = 100 + + # set max_order to a low value for a quick (but less accurate) RIR + room = pra.ShoeBox(room_dim, fs=fs, max_order=max_order, absorption=0.2) + + # add source and set the signal to WAV file content + room.add_source(source, signal=signal) + + # add two-microphone array + room.add_microphone_array(pra.MicrophoneArray(microphone, room.fs)) + + # compute image sources + room.image_source_model(use_libroom=True) + + room.compute_rir() + rir = room.rir[0][0] + + # save the room reverberations + wavfile.write(name_sub + "_rir_" + str(temp) + ".wav", 16000, rir) + +with open("room_setting.data", "wb") as f: + pickle.dump(room_settings, f) diff --git a/examples/adversarial_asr/test_imperceptible_adv.py b/cleverhans_v3.1.0/examples/adversarial_asr/test_imperceptible_adv.py similarity index 58% rename from examples/adversarial_asr/test_imperceptible_adv.py rename to cleverhans_v3.1.0/examples/adversarial_asr/test_imperceptible_adv.py index 5025da019..486bd1f19 100644 --- a/examples/adversarial_asr/test_imperceptible_adv.py +++ b/cleverhans_v3.1.0/examples/adversarial_asr/test_imperceptible_adv.py @@ -10,81 +10,86 @@ from absl import flags from absl import app -flags.DEFINE_string('input', 'read_data.txt', - 'the text file saved the dir of audios and the corresponding original and targeted transcriptions') -flags.DEFINE_integer('batch_size', '5', - 'batch_size to do the testing') -flags.DEFINE_string('checkpoint', "./model/ckpt-00908156", - 'location of checkpoint') -flags.DEFINE_string('stage', "stage2", 'which stage to test') -flags.DEFINE_boolean('adv', 'True', 'to test adversarial examples or clean examples') +flags.DEFINE_string( + "input", + "read_data.txt", + "the text file saved the dir of audios and the corresponding original and targeted transcriptions", +) +flags.DEFINE_integer("batch_size", "5", "batch_size to do the testing") +flags.DEFINE_string("checkpoint", "./model/ckpt-00908156", "location of checkpoint") +flags.DEFINE_string("stage", "stage2", "which stage to test") +flags.DEFINE_boolean("adv", "True", "to test adversarial examples or clean examples") FLAGS = flags.FLAGS + def Read_input(data, batch_size): """ - Returns: + Returns: audios_np: a numpy array of size (batch_size, max_length) in float - sample_rate: a numpy array + sample_rate: a numpy array trans: an array includes the targeted transcriptions (batch_size,) - masks_freq: a numpy array to mask out the padding features in frequency domain + masks_freq: a numpy array to mask out the padding features in frequency domain """ audios = [] lengths = [] - + for i in range(batch_size): - name, _ = data[0,i].split(".") + name, _ = data[0, i].split(".") if FLAGS.adv: - sample_rate_np, audio_temp = wav.read("./" + str(name) + "_" + FLAGS.stage + ".wav") + sample_rate_np, audio_temp = wav.read( + "./" + str(name) + "_" + FLAGS.stage + ".wav" + ) else: sample_rate_np, audio_temp = wav.read("./" + str(name) + ".wav") # read the wav form range from [-32767, 32768] or [-1, 1] if max(audio_temp) < 1: audio_np = audio_temp * 32768 - + else: audio_np = audio_temp length = len(audio_np) - + audios.append(audio_np) lengths.append(length) - - max_length = max(lengths) + max_length = max(lengths) lengths_freq = (np.array(lengths) // 2 + 1) // 240 * 3 max_length_freq = max(lengths_freq) masks_freq = np.zeros([batch_size, max_length_freq, 80]) - + # combine the audios into one array - audios_np = np.zeros([batch_size, max_length]) - + audios_np = np.zeros([batch_size, max_length]) + for i in range(batch_size): - audios_np[i, :lengths[i]] = audios[i] - masks_freq[i, :lengths_freq[i], :] = 1 - + audios_np[i, : lengths[i]] = audios[i] + masks_freq[i, : lengths_freq[i], :] = 1 + audios_np = audios_np.astype(float) if FLAGS.adv: trans = data[2, :] else: trans = data[1, :] - + return audios_np, sample_rate_np, trans, masks_freq + def main(argv): data = np.loadtxt(FLAGS.input, dtype=str, delimiter=",") # calculate the number of loops to run the test num = len(data[0]) batch_size = FLAGS.batch_size - num_loops = num / batch_size + num_loops = num / batch_size assert num % batch_size == 0 - - + with tf.device("/gpu:0"): tf.set_random_seed(1234) tfconf = tf.ConfigProto(allow_soft_placement=True) - with tf.Session(config=tfconf) as sess: - params = model_registry.GetParams('asr.librispeech.Librispeech960Wpm', 'Test') + with tf.Session(config=tfconf) as sess: + params = model_registry.GetParams( + "asr.librispeech.Librispeech960Wpm", "Test" + ) params.cluster.worker.gpus_per_replica = 1 cluster = cluster_factory.Cluster(params.cluster) with cluster, tf.device(cluster.GetPlacer()): @@ -95,55 +100,59 @@ def main(argv): task = model.GetTask() saver = tf.train.Saver() saver.restore(sess, FLAGS.checkpoint) - + # define the placeholders input_tf = tf.placeholder(tf.float32, shape=[batch_size, None]) tgt_tf = tf.placeholder(tf.string) - sample_rate_tf = tf.placeholder(tf.int32) + sample_rate_tf = tf.placeholder(tf.int32) mask_tf = tf.placeholder(tf.float32, shape=[batch_size, None, 80]) - + # generate the features and inputs features = create_features(input_tf, sample_rate_tf, mask_tf) shape = tf.shape(features) inputs = create_inputs(model, features, tgt_tf, batch_size, mask_tf) - + # loss - metrics = task.FPropDefaultTheta(inputs) - loss = tf.get_collection("per_loss")[0] - + metrics = task.FPropDefaultTheta(inputs) + loss = tf.get_collection("per_loss")[0] + # prediction decoded_outputs = task.Decode(inputs) - dec_metrics_dict = task.CreateDecoderMetrics() - - correct = 0 - for l in range(num_loops): - data_sub = data[:, l * batch_size:(l + 1) * batch_size] - audios_np, sample_rate, tgt_np, mask_freq = Read_input(data_sub, batch_size) - feed_dict={input_tf: audios_np, - sample_rate_tf: sample_rate, - tgt_tf: tgt_np, - mask_tf: mask_freq} - - losses = sess.run(loss, feed_dict) + dec_metrics_dict = task.CreateDecoderMetrics() + + correct = 0 + for l in range(num_loops): + data_sub = data[:, l * batch_size : (l + 1) * batch_size] + audios_np, sample_rate, tgt_np, mask_freq = Read_input( + data_sub, batch_size + ) + feed_dict = { + input_tf: audios_np, + sample_rate_tf: sample_rate, + tgt_tf: tgt_np, + mask_tf: mask_freq, + } + + losses = sess.run(loss, feed_dict) predictions = sess.run(decoded_outputs, feed_dict) - + task.PostProcessDecodeOut(predictions, dec_metrics_dict) - wer_value = dec_metrics_dict['wer'].value * 100. - - for i in range(batch_size): - print("pred:{}".format(predictions['topk_decoded'][i, 0])) + wer_value = dec_metrics_dict["wer"].value * 100.0 + + for i in range(batch_size): + print("pred:{}".format(predictions["topk_decoded"][i, 0])) print("targ:{}".format(tgt_np[i].lower())) print("true: {}".format(data_sub[1, i].lower())) - if predictions['topk_decoded'][i,0] == tgt_np[i].lower(): + if predictions["topk_decoded"][i, 0] == tgt_np[i].lower(): correct += 1 print("------------------------------") print("example {} succeeds".format(i)) - print("Now, the WER is: {0:.2f}%".format(wer_value)) + print("Now, the WER is: {0:.2f}%".format(wer_value)) print("num of examples succeed: {}".format(correct)) print("success rate: {}%".format(correct / float(num) * 100)) - -if __name__ == '__main__': + +if __name__ == "__main__": app.run(main) diff --git a/cleverhans_v3.1.0/examples/adversarial_asr/test_robust_adv.py b/cleverhans_v3.1.0/examples/adversarial_asr/test_robust_adv.py new file mode 100644 index 000000000..0dc898d6e --- /dev/null +++ b/cleverhans_v3.1.0/examples/adversarial_asr/test_robust_adv.py @@ -0,0 +1,226 @@ +import tensorflow as tf +from lingvo import model_imports +from lingvo import model_registry +import numpy as np +import scipy.io.wavfile as wav +import generate_masking_threshold as generate_mask +from tool import create_features, create_inputs, create_speech_rir +import time +from lingvo.core import cluster_factory +from absl import flags +from absl import app + +flags.DEFINE_string("root_dir", "./", "location of Librispeech") +flags.DEFINE_string( + "input", + "read_data.txt", + "the text file saved the dir of audios and the corresponding original and targeted transcriptions", +) +flags.DEFINE_string( + "rir_dir", + "LibriSpeech/test-clean/3575/170457/3575-170457-0013", + "directory of generated room reverberations", +) +flags.DEFINE_string("checkpoint", "./model/ckpt-00908156", "location of checkpoint") + +flags.DEFINE_integer("batch_size", "5", "batch_size to do the testing") +flags.DEFINE_string("stage", "stage2", "which step to test") +flags.DEFINE_boolean("adv", "True", "to test adversarial examples or clean examples") +flags.DEFINE_integer("num_test_rooms", "100", "batch_size to do the testing") +flags.DEFINE_integer("num_train_rooms", "1000", "batch_size to do the testing") + +FLAGS = flags.FLAGS + + +def Read_input(data, batch_size): + """ + Returns: + audios_np: a numpy array of size (batch_size, max_length) in float + sample_rate: a numpy array + trans: an array includes the targeted transcriptions (batch_size,) + """ + audios = [] + lengths = [] + + for i in range(batch_size): + name, _ = data[0, i].split(".") + + if FLAGS.adv: + sample_rate_np, delta = wav.read( + "./" + str(name) + "_robust_perturb_" + FLAGS.stage + ".wav" + ) + _, audio_orig = wav.read("./" + str(name) + ".wav") + if max(delta) < 1: + delta = delta * 32768 + audio_np = audio_orig + delta + else: + sample_rate_np, audio_np = wav.read("./" + str(name) + ".wav") + + length = len(audio_np) + + audios.append(audio_np) + lengths.append(length) + + max_length = max(lengths) + masks = np.zeros([batch_size, max_length]) + lengths_freq = (np.array(lengths) // 2 + 1) // 240 * 3 + max_length_freq = max(lengths_freq) + masks_freq = np.zeros([batch_size, max_length_freq, 80]) + + # combine the audios into one array + audios_np = np.zeros([batch_size, max_length]) + + for i in range(batch_size): + audios_np[i, : lengths[i]] = audios[i] + masks[i, : lengths[i]] = 1 + masks_freq[i, : lengths_freq[i], :] = 1 + + audios_np = audios_np.astype(float) + + if FLAGS.adv: + trans = data[2, :] + else: + trans = data[1, :] + + lengths = np.array(lengths).astype(np.int32) + + return audios_np, sample_rate_np, trans, masks_freq, lengths, max_length, masks + + +def Readrir(num_room): + """ + Return: + rir: a numpy array of the room reverberation + (make sure the test rooms are different from training rooms) + + """ + index = num_room + FLAGS.num_train_rooms + 1 + _, rir = wav.read(FLAGS.root_dir + FLAGS.rir_dir + "_rir_" + str(index) + ".wav") + return rir + + +def main(argv): + data = np.loadtxt(FLAGS.input, dtype=str, delimiter=",") + # calculate the number of loops to run the test + num = len(data[0]) + batch_size = FLAGS.batch_size + num_loops = num / batch_size + assert num % batch_size == 0 + + with tf.device("/gpu:0"): + tf.set_random_seed(1234) + tfconf = tf.ConfigProto(allow_soft_placement=True) + with tf.Session(config=tfconf) as sess: + params = model_registry.GetParams( + "asr.librispeech.Librispeech960Wpm", "Test" + ) + params.cluster.worker.gpus_per_replica = 1 + cluster = cluster_factory.Cluster(params.cluster) + with cluster, tf.device(cluster.GetPlacer()): + params.vn.global_vn = False + params.random_seed = 1234 + params.is_eval = True + model = params.cls(params) + task = model.GetTask() + saver = tf.train.Saver() + saver.restore(sess, FLAGS.checkpoint) + + # define the placeholders + input_tf = tf.placeholder(tf.float32, shape=[batch_size, None]) + tgt_tf = tf.placeholder(tf.string) + sample_rate_tf = tf.placeholder(tf.int32) + mask_tf = tf.placeholder(tf.float32, shape=[batch_size, None, 80]) + rir_tf = tf.placeholder(tf.float32) + lengths = tf.placeholder( + np.int32, + shape=[ + batch_size, + ], + ) + maxlen = tf.placeholder(np.int32) + mask = tf.placeholder(dtype=np.float32, shape=[batch_size, None]) + + # generate the features and inputs + new_input = ( + create_speech_rir(input_tf, rir_tf, lengths, maxlen, batch_size) + * mask + ) + features = create_features(new_input, sample_rate_tf, mask_tf) + shape = tf.shape(features) + inputs = create_inputs(model, features, tgt_tf, batch_size, mask_tf) + + # loss + metrics = task.FPropDefaultTheta(inputs) + loss = tf.get_collection("per_loss")[0] + + # prediction + decoded_outputs = task.Decode(inputs) + dec_metrics_dict = task.CreateDecoderMetrics() + + success_rates = [] + for num_room in range(FLAGS.num_test_rooms): + correct = 0 + rir = Readrir(num_room) + + for l in range(num_loops): + data_sub = data[:, l * batch_size : (l + 1) * batch_size] + ( + audios_np, + sample_rate, + tgt_np, + mask_freq, + lengths_np, + max_len, + masks, + ) = Read_input(data_sub, batch_size) + + feed_dict = { + input_tf: audios_np, + sample_rate_tf: sample_rate, + tgt_tf: tgt_np, + mask_tf: mask_freq, + rir_tf: rir, + lengths: lengths_np, + maxlen: max_len, + mask: masks, + } + + losses = sess.run(loss, feed_dict) + predictions = sess.run(decoded_outputs, feed_dict) + + task.PostProcessDecodeOut(predictions, dec_metrics_dict) + wer_value = dec_metrics_dict["wer"].value * 100.0 + + for i in range(batch_size): + print( + "example: {}, loss_ce: {}".format( + l * batch_size + i, losses[i] + ) + ) + print("pred:{}".format(predictions["topk_decoded"][i, 0])) + print("targ:{}".format(tgt_np[i].lower())) + print("true: {}".format(data_sub[1, i].lower())) + + if predictions["topk_decoded"][i, 0] == tgt_np[i].lower(): + correct += 1 + + print("--------------------------------") + print("Now, the WER is: {0:.2f}%".format(wer_value)) + + print( + "num of examples succeed for room {}: {}".format( + num_room, correct + ) + ) + success_rate = correct / float(num) * 100 + print( + "success rate for room {}: {}%".format(num_room, success_rate) + ) + + success_rates.append(success_rate) + success_ave = float(sum(success_rates)) / len(success_rates) + print("success rate overall: {}%".format(success_ave)) + + +if __name__ == "__main__": + app.run(main) diff --git a/examples/adversarial_asr/tool.py b/cleverhans_v3.1.0/examples/adversarial_asr/tool.py similarity index 51% rename from examples/adversarial_asr/tool.py rename to cleverhans_v3.1.0/examples/adversarial_asr/tool.py index 053836223..94c8786e7 100644 --- a/examples/adversarial_asr/tool.py +++ b/cleverhans_v3.1.0/examples/adversarial_asr/tool.py @@ -4,55 +4,62 @@ from lingvo.core import asr_frontend from lingvo.core import py_utils -def _MakeLogMel(audio, sample_rate): - audio = tf.expand_dims(audio, axis=0) - static_sample_rate = 16000 - mel_frontend = _CreateAsrFrontend() - with tf.control_dependencies( - [tf.assert_equal(sample_rate, static_sample_rate)]): - log_mel, _ = mel_frontend.FPropDefaultTheta(audio) - return log_mel + +def _MakeLogMel(audio, sample_rate): + audio = tf.expand_dims(audio, axis=0) + static_sample_rate = 16000 + mel_frontend = _CreateAsrFrontend() + with tf.control_dependencies([tf.assert_equal(sample_rate, static_sample_rate)]): + log_mel, _ = mel_frontend.FPropDefaultTheta(audio) + return log_mel + def _CreateAsrFrontend(): - p = asr_frontend.MelFrontend.Params() - p.sample_rate = 16000. - p.frame_size_ms = 25. - p.frame_step_ms = 10. - p.num_bins = 80 - p.lower_edge_hertz = 125. - p.upper_edge_hertz = 7600. - p.preemph = 0.97 - p.noise_scale = 0. - p.pad_end = False - # Stack 3 frames and sub-sample by a factor of 3. - p.left_context = 2 - p.output_stride = 3 - return p.cls(p) + p = asr_frontend.MelFrontend.Params() + p.sample_rate = 16000.0 + p.frame_size_ms = 25.0 + p.frame_step_ms = 10.0 + p.num_bins = 80 + p.lower_edge_hertz = 125.0 + p.upper_edge_hertz = 7600.0 + p.preemph = 0.97 + p.noise_scale = 0.0 + p.pad_end = False + # Stack 3 frames and sub-sample by a factor of 3. + p.left_context = 2 + p.output_stride = 3 + return p.cls(p) + def create_features(input_tf, sample_rate_tf, mask_freq): """ Return: A tensor of features with size (batch_size, max_time_steps, 80) """ - - features_list = [] - # unstact the features with placeholder + + features_list = [] + # unstact the features with placeholder input_unpack = tf.unstack(input_tf, axis=0) for i in range(len(input_unpack)): features = _MakeLogMel(input_unpack[i], sample_rate_tf) - features = tf.reshape(features, shape=[-1, 80]) - features = tf.expand_dims(features, dim=0) + features = tf.reshape(features, shape=[-1, 80]) + features = tf.expand_dims(features, dim=0) features_list.append(features) - features_tf = tf.concat(features_list, axis=0) + features_tf = tf.concat(features_list, axis=0) features_tf = features_tf * mask_freq - return features_tf - -def create_inputs(model, features, tgt, batch_size, mask_freq): - tgt_ids, tgt_labels, tgt_paddings = model.GetTask().input_generator.StringsToIds(tgt) - + return features_tf + + +def create_inputs(model, features, tgt, batch_size, mask_freq): + tgt_ids, tgt_labels, tgt_paddings = model.GetTask().input_generator.StringsToIds( + tgt + ) + # we expect src_inputs to be of shape [batch_size, num_frames, feature_dim, channels] - src_paddings = tf.zeros([tf.shape(features)[0], tf.shape(features)[1]], dtype=tf.float32) - src_paddings = 1. - mask_freq[:,:,0] + src_paddings = tf.zeros( + [tf.shape(features)[0], tf.shape(features)[1]], dtype=tf.float32 + ) + src_paddings = 1.0 - mask_freq[:, :, 0] src_frames = tf.expand_dims(features, dim=-1) inputs = py_utils.NestedMap() @@ -60,52 +67,68 @@ def create_inputs(model, features, tgt, batch_size, mask_freq): ids=tgt_ids, labels=tgt_labels, paddings=tgt_paddings, - weights=1.0 - tgt_paddings) + weights=1.0 - tgt_paddings, + ) inputs.src = py_utils.NestedMap(src_inputs=src_frames, paddings=src_paddings) inputs.sample_ids = tf.zeros([batch_size]) return inputs - + + def create_speech_rir(audios, rir, lengths_audios, max_len, batch_size): """ Returns: - A tensor of speech with reverberations (Convolve the audio with the rir) + A tensor of speech with reverberations (Convolve the audio with the rir) """ speech_rir = [] for i in range(batch_size): s1 = lengths_audios[i] s2 = tf.convert_to_tensor(tf.shape(rir)) - shape = s1 + s2 - 1 - + shape = s1 + s2 - 1 + # Compute convolution in fourier space sp1 = tf.spectral.rfft(rir, shape) - sp2 = tf.spectral.rfft(tf.slice(tf.reshape(audios[i], [-1,]), [0], [lengths_audios[i]]), shape) + sp2 = tf.spectral.rfft( + tf.slice( + tf.reshape( + audios[i], + [ + -1, + ], + ), + [0], + [lengths_audios[i]], + ), + shape, + ) ret = tf.spectral.irfft(sp1 * sp2, shape) # normalization ret /= tf.reduce_max(tf.abs(ret)) - ret *= 2 ** (16 - 1) -1 - ret = tf.clip_by_value(ret, -2 **(16 - 1), 2**(16-1) - 1) + ret *= 2 ** (16 - 1) - 1 + ret = tf.clip_by_value(ret, -(2 ** (16 - 1)), 2 ** (16 - 1) - 1) ret = tf.pad(ret, tf.constant([[0, 100000]])) ret = ret[:max_len] - + speech_rir.append(tf.expand_dims(ret, axis=0)) speech_rirs = tf.concat(speech_rir, axis=0) return speech_rirs + class Transform(object): - ''' + """ Return: PSD - ''' + """ + def __init__(self, window_size): - self.scale = 8. / 3. + self.scale = 8.0 / 3.0 self.frame_length = int(window_size) - self.frame_step = int(window_size//4) + self.frame_step = int(window_size // 4) self.window_size = window_size - + def __call__(self, x, psd_max_ori): win = tf.contrib.signal.stft(x, self.frame_length, self.frame_step) - z = self.scale *tf.abs(win / self.window_size) + z = self.scale * tf.abs(win / self.window_size) psd = tf.square(z) - PSD = tf.pow(10., 9.6) / tf.reshape(psd_max_ori, [-1, 1, 1]) * psd - return PSD \ No newline at end of file + PSD = tf.pow(10.0, 9.6) / tf.reshape(psd_max_ori, [-1, 1, 1]) * psd + return PSD diff --git a/examples/adversarial_asr/util/convert_name_format.sh b/cleverhans_v3.1.0/examples/adversarial_asr/util/convert_name_format.sh similarity index 100% rename from examples/adversarial_asr/util/convert_name_format.sh rename to cleverhans_v3.1.0/examples/adversarial_asr/util/convert_name_format.sh diff --git a/examples/adversarial_asr/util/read_data_full.txt b/cleverhans_v3.1.0/examples/adversarial_asr/util/read_data_full.txt similarity index 100% rename from examples/adversarial_asr/util/read_data_full.txt rename to cleverhans_v3.1.0/examples/adversarial_asr/util/read_data_full.txt diff --git a/examples/adversarial_patch/AdversarialPatch.ipynb b/cleverhans_v3.1.0/examples/adversarial_patch/AdversarialPatch.ipynb similarity index 100% rename from examples/adversarial_patch/AdversarialPatch.ipynb rename to cleverhans_v3.1.0/examples/adversarial_patch/AdversarialPatch.ipynb diff --git a/examples/adversarial_patch/README.md b/cleverhans_v3.1.0/examples/adversarial_patch/README.md similarity index 100% rename from examples/adversarial_patch/README.md rename to cleverhans_v3.1.0/examples/adversarial_patch/README.md diff --git a/examples/facenet_adversarial_faces/README.md b/cleverhans_v3.1.0/examples/facenet_adversarial_faces/README.md similarity index 100% rename from examples/facenet_adversarial_faces/README.md rename to cleverhans_v3.1.0/examples/facenet_adversarial_faces/README.md diff --git a/cleverhans_v3.1.0/examples/facenet_adversarial_faces/facenet_fgsm.py b/cleverhans_v3.1.0/examples/facenet_adversarial_faces/facenet_fgsm.py new file mode 100644 index 000000000..2b8f904ed --- /dev/null +++ b/cleverhans_v3.1.0/examples/facenet_adversarial_faces/facenet_fgsm.py @@ -0,0 +1,141 @@ +import facenet + +import tensorflow as tf +import numpy as np +from cleverhans.model import Model +from cleverhans.attacks import FastGradientMethod + +import set_loader + + +class InceptionResnetV1Model(Model): + model_path = "models/facenet/20180402-114759/20180402-114759.pb" + + def __init__(self): + super(InceptionResnetV1Model, self).__init__(scope="model") + + # Load Facenet CNN + facenet.load_model(self.model_path) + # Save input and output tensors references + graph = tf.get_default_graph() + self.face_input = graph.get_tensor_by_name("input:0") + self.embedding_output = graph.get_tensor_by_name("embeddings:0") + + def convert_to_classifier(self): + # Create victim_embedding placeholder + self.victim_embedding_input = tf.placeholder(tf.float32, shape=(None, 512)) + + # Squared Euclidean Distance between embeddings + distance = tf.reduce_sum( + tf.square(self.embedding_output - self.victim_embedding_input), axis=1 + ) + + # Convert distance to a softmax vector + # 0.99 out of 4 is the distance threshold for the Facenet CNN + threshold = 0.99 + score = tf.where( + distance > threshold, + 0.5 + ((distance - threshold) * 0.5) / (4.0 - threshold), + 0.5 * distance / threshold, + ) + reverse_score = 1.0 - score + self.softmax_output = tf.transpose(tf.stack([reverse_score, score])) + + # Save softmax layer + self.layer_names = [] + self.layers = [] + self.layers.append(self.softmax_output) + self.layer_names.append("logits") + + def fprop(self, x, set_ref=False): + return dict(zip(self.layer_names, self.layers)) + + +with tf.Graph().as_default(): + with tf.Session() as sess: + # Load model + model = InceptionResnetV1Model() + # Convert to classifier + model.convert_to_classifier() + + # Load pairs of faces and their labels in one-hot encoding + size = 100 + faces1, faces2, labels = set_loader.load_testset(size) + + # Create victims' embeddings using Facenet itself + graph = tf.get_default_graph() + phase_train_placeholder = graph.get_tensor_by_name("phase_train:0") + feed_dict = {model.face_input: faces2, phase_train_placeholder: False} + victims_embeddings = sess.run(model.embedding_output, feed_dict=feed_dict) + + # Define FGSM for the model + steps = 1 + eps = 0.01 + alpha = eps / steps + fgsm = FastGradientMethod(model) + fgsm_params = {"eps": alpha, "clip_min": 0.0, "clip_max": 1.0} + adv_x = fgsm.generate(model.face_input, **fgsm_params) + + # Run FGSM + adv = faces1 + for i in range(steps): + print("FGSM step " + str(i + 1)) + feed_dict = { + model.face_input: adv, + model.victim_embedding_input: victims_embeddings, + phase_train_placeholder: False, + } + adv = sess.run(adv_x, feed_dict=feed_dict) + + # Test accuracy of the model + batch_size = graph.get_tensor_by_name("batch_size:0") + + feed_dict = { + model.face_input: faces1, + model.victim_embedding_input: victims_embeddings, + phase_train_placeholder: False, + batch_size: 64, + } + real_labels = sess.run(model.softmax_output, feed_dict=feed_dict) + + accuracy = np.mean( + (np.argmax(labels, axis=-1)) == (np.argmax(real_labels, axis=-1)) + ) + print("Accuracy: " + str(accuracy * 100) + "%") + + # Test accuracy against adversarial examples + feed_dict = { + model.face_input: adv, + model.victim_embedding_input: victims_embeddings, + phase_train_placeholder: False, + batch_size: 64, + } + adversarial_labels = sess.run(model.softmax_output, feed_dict=feed_dict) + + same_faces_index = np.where((np.argmax(labels, axis=-1) == 0)) + different_faces_index = np.where((np.argmax(labels, axis=-1) == 1)) + + accuracy = np.mean( + (np.argmax(labels[same_faces_index], axis=-1)) + == (np.argmax(adversarial_labels[same_faces_index], axis=-1)) + ) + print( + "Accuracy against adversarial examples for " + + "same person faces (dodging): " + + str(accuracy * 100) + + "%" + ) + + accuracy = np.mean( + (np.argmax(labels[different_faces_index], axis=-1)) + == (np.argmax(adversarial_labels[different_faces_index], axis=-1)) + ) + print( + "Accuracy against adversarial examples for " + + "different people faces (impersonation): " + + str(accuracy * 100) + + "%" + ) + + # Save images to folder + set_loader.save_images(adv, faces1, faces2, size) diff --git a/cleverhans_v3.1.0/examples/facenet_adversarial_faces/set_loader.py b/cleverhans_v3.1.0/examples/facenet_adversarial_faces/set_loader.py new file mode 100644 index 000000000..85b470892 --- /dev/null +++ b/cleverhans_v3.1.0/examples/facenet_adversarial_faces/set_loader.py @@ -0,0 +1,73 @@ +import shutil +import os + +import lfw +import facenet + +import numpy as np + +from PIL import Image + + +pairs_path = "datasets/lfw/pairs.txt" +testset_path = "datasets/lfw/lfw_mtcnnpy_160" +image_size = 160 + + +def save_images(adv, faces1, faces2, size): + save_images_to_folder(adv, size, "images/adversarial/") + save_images_to_folder(0.5 + (adv - faces1), size, "images/noise/") + save_images_to_folder(faces1, size, "images/faces1/") + save_images_to_folder(faces2, size, "images/faces2/") + + +def save_images_to_folder(images, size, path): + if os.path.isdir(path): + shutil.rmtree(path) + os.makedirs(path) + + for index in range(images.shape[0]): + if index < size: + image_array = (np.reshape(images[index], (160, 160, 3)) * 255).astype( + np.uint8 + ) + Image.fromarray(image_array, "RGB").save(path + str(index) + ".png") + + +def load_testset(size): + # Load images paths and labels + pairs = lfw.read_pairs(pairs_path) + paths, labels = lfw.get_paths(testset_path, pairs) + + # Random choice + permutation = np.random.choice(len(labels), size, replace=False) + paths_batch_1 = [] + paths_batch_2 = [] + + for index in permutation: + paths_batch_1.append(paths[index * 2]) + paths_batch_2.append(paths[index * 2 + 1]) + + labels = np.asarray(labels)[permutation] + paths_batch_1 = np.asarray(paths_batch_1) + paths_batch_2 = np.asarray(paths_batch_2) + + # Load images + faces1 = facenet.load_data(paths_batch_1, False, False, image_size) + faces2 = facenet.load_data(paths_batch_2, False, False, image_size) + + # Change pixel values to 0 to 1 values + min_pixel = min(np.min(faces1), np.min(faces2)) + max_pixel = max(np.max(faces1), np.max(faces2)) + faces1 = (faces1 - min_pixel) / (max_pixel - min_pixel) + faces2 = (faces2 - min_pixel) / (max_pixel - min_pixel) + + # Convert labels to one-hot vectors + onehot_labels = [] + for index in range(len(labels)): + if labels[index]: + onehot_labels.append([1, 0]) + else: + onehot_labels.append([0, 1]) + + return faces1, faces2, np.array(onehot_labels) diff --git a/cleverhans_v3.1.0/examples/imagenet_featadvs/model.py b/cleverhans_v3.1.0/examples/imagenet_featadvs/model.py new file mode 100644 index 000000000..379b2b6db --- /dev/null +++ b/cleverhans_v3.1.0/examples/imagenet_featadvs/model.py @@ -0,0 +1,39 @@ +# pylint: disable=missing-docstring +import functools +import tensorflow as tf + +from cleverhans.initializers import HeReLuNormalInitializer +from cleverhans.model import Model + + +class ModelImageNetCNN(Model): + def __init__(self, scope, nb_classes=1000, **kwargs): + del kwargs + Model.__init__(self, scope, nb_classes, locals()) + + def fprop(self, x, **kwargs): + del kwargs + my_conv = functools.partial( + tf.layers.conv2d, + kernel_size=3, + strides=2, + padding="valid", + activation=tf.nn.relu, + kernel_initializer=HeReLuNormalInitializer, + ) + my_dense = functools.partial( + tf.layers.dense, kernel_initializer=HeReLuNormalInitializer + ) + + with tf.variable_scope(self.scope, reuse=tf.AUTO_REUSE): + for depth in [96, 256, 384, 384, 256]: + x = my_conv(x, depth) + y = tf.layers.flatten(x) + y = my_dense(y, 4096, tf.nn.relu) + y = fc7 = my_dense(y, 4096, tf.nn.relu) + y = my_dense(y, 1000) + return {"fc7": fc7, self.O_LOGITS: y, self.O_PROBS: tf.nn.softmax(logits=y)} + + +def make_imagenet_cnn(input_shape=(None, 224, 224, 3)): + return ModelImageNetCNN("imagenet") diff --git a/examples/madry_lab_challenges/LICENSE b/cleverhans_v3.1.0/examples/madry_lab_challenges/LICENSE similarity index 100% rename from examples/madry_lab_challenges/LICENSE rename to cleverhans_v3.1.0/examples/madry_lab_challenges/LICENSE diff --git a/examples/madry_lab_challenges/README b/cleverhans_v3.1.0/examples/madry_lab_challenges/README similarity index 100% rename from examples/madry_lab_challenges/README rename to cleverhans_v3.1.0/examples/madry_lab_challenges/README diff --git a/cleverhans_v3.1.0/examples/madry_lab_challenges/cifar10/attack_model.py b/cleverhans_v3.1.0/examples/madry_lab_challenges/cifar10/attack_model.py new file mode 100644 index 000000000..1fc8d489f --- /dev/null +++ b/cleverhans_v3.1.0/examples/madry_lab_challenges/cifar10/attack_model.py @@ -0,0 +1,163 @@ +"""Runs CleverHans attacks on the Madry Lab CIFAR-10 challenge model + +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import os +import sys +import time +import numpy as np +import logging + +import tensorflow as tf +from tensorflow.python.platform import app, flags +from cleverhans.utils import set_log_level, to_categorical +from cleverhans.utils_tf import model_eval +import cifar10_input + + +FLAGS = flags.FLAGS + + +def main(argv): + + model_file = tf.train.latest_checkpoint(FLAGS.checkpoint_dir) + + if model_file is None: + print("No model found") + sys.exit() + + cifar = cifar10_input.CIFAR10Data(FLAGS.dataset_dir) + + nb_classes = 10 + X_test = cifar.eval_data.xs + Y_test = to_categorical(cifar.eval_data.ys, nb_classes) + assert Y_test.shape[1] == 10.0 + + set_log_level(logging.DEBUG) + + with tf.Session() as sess: + + x = tf.placeholder(tf.float32, shape=(None, 32, 32, 3)) + y = tf.placeholder(tf.float32, shape=(None, 10)) + + from cleverhans.model_zoo.madry_lab_challenges.cifar10_model import make_wresnet + + model = make_wresnet() + + saver = tf.train.Saver() + + # Restore the checkpoint + saver.restore(sess, model_file) + + nb_samples = FLAGS.nb_samples + + attack_params = { + "batch_size": FLAGS.batch_size, + "clip_min": 0.0, + "clip_max": 255.0, + } + + if FLAGS.attack_type == "cwl2": + from cleverhans.attacks import CarliniWagnerL2 + + attacker = CarliniWagnerL2(model, sess=sess) + attack_params.update( + { + "binary_search_steps": 1, + "max_iterations": 100, + "learning_rate": 0.1, + "initial_const": 10, + "batch_size": 10, + } + ) + + else: # eps and eps_iter in range 0-255 + attack_params.update({"eps": 8, "ord": np.inf}) + if FLAGS.attack_type == "fgsm": + from cleverhans.attacks import FastGradientMethod + + attacker = FastGradientMethod(model, sess=sess) + + elif FLAGS.attack_type == "pgd": + attack_params.update({"eps_iter": 2, "nb_iter": 20}) + from cleverhans.attacks import MadryEtAl + + attacker = MadryEtAl(model, sess=sess) + + eval_par = {"batch_size": FLAGS.batch_size} + + if FLAGS.sweep: + max_eps = 16 + epsilons = np.linspace(1, max_eps, max_eps) + for e in epsilons: + t1 = time.time() + attack_params.update({"eps": e}) + x_adv = attacker.generate(x, **attack_params) + preds_adv = model.get_probs(x_adv) + acc = model_eval( + sess, + x, + y, + preds_adv, + X_test[:nb_samples], + Y_test[:nb_samples], + args=eval_par, + ) + print( + "Epsilon %.2f, accuracy on adversarial" % e, + "examples %0.4f\n" % acc, + ) + t2 = time.time() + else: + t1 = time.time() + x_adv = attacker.generate(x, **attack_params) + preds_adv = model.get_probs(x_adv) + acc = model_eval( + sess, + x, + y, + preds_adv, + X_test[:nb_samples], + Y_test[:nb_samples], + args=eval_par, + ) + t2 = time.time() + print("Test accuracy on adversarial examples %0.4f\n" % acc) + print("Took", t2 - t1, "seconds") + + +if __name__ == "__main__": + + if "CIFAR10_CHALLENGE_DIR" in os.environ: + cifar10_root = os.environ["CIFAR10_CHALLENGE_DIR"] + default_ckpt_dir = os.path.join(cifar10_root, "models/adv_trained") + default_data_dir = os.path.join(cifar10_root, "cifar10_data") + + flags.DEFINE_integer("batch_size", 100, "Batch size") + + flags.DEFINE_integer("nb_samples", 1000, "Number of samples to test") + + flags.DEFINE_string( + "attack_type", + "fgsm", + ( + "Attack type: 'fgsm'->'fast " + "gradient sign method', " + "'pgd'->'projected " + "gradient descent', 'cwl2'->" + "'Carlini & Wagner L2'" + ), + ) + flags.DEFINE_string( + "checkpoint_dir", default_ckpt_dir, "Checkpoint directory to load" + ) + + flags.DEFINE_string("dataset_dir", default_data_dir, "Dataset directory") + + flags.DEFINE_bool("sweep", False, "Sweep epsilon or single epsilon?") + + app.run(main) diff --git a/cleverhans_v3.1.0/examples/madry_lab_challenges/mnist/attack_model.py b/cleverhans_v3.1.0/examples/madry_lab_challenges/mnist/attack_model.py new file mode 100644 index 000000000..e8a3ccfd3 --- /dev/null +++ b/cleverhans_v3.1.0/examples/madry_lab_challenges/mnist/attack_model.py @@ -0,0 +1,111 @@ +"""Runs CleverHans attacks on the Madry Lab MNIST challenge model + +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import os +import time + +import tensorflow as tf +from tensorflow.examples.tutorials.mnist import input_data +from tensorflow.python.platform import app +from tensorflow.python.platform import flags +from madry_mnist_model import MadryMNIST +from cleverhans.utils_tf import model_eval +from cleverhans.attacks import FastGradientMethod +from cleverhans.attacks import BasicIterativeMethod +from cleverhans.utils_mnist import data_mnist + + +FLAGS = flags.FLAGS + + +def main(argv): + checkpoint = tf.train.latest_checkpoint(FLAGS.checkpoint_dir) + + if checkpoint is None: + raise ValueError("Couldn't find latest checkpoint in " + FLAGS.checkpoint_dir) + + train_start = 0 + train_end = 60000 + test_start = 0 + test_end = 10000 + X_train, Y_train, X_test, Y_test = data_mnist( + train_start=train_start, + train_end=train_end, + test_start=test_start, + test_end=test_end, + ) + + assert Y_train.shape[1] == 10 + + # NOTE: for compatibility with Madry Lab downloadable checkpoints, + # we cannot enclose this in a scope or do anything else that would + # change the automatic naming of the variables. + model = MadryMNIST() + + x_input = tf.placeholder(tf.float32, shape=[None, 784]) + x_image = tf.placeholder(tf.float32, shape=[None, 28, 28, 1]) + y = tf.placeholder(tf.float32, shape=[None, 10]) + + if FLAGS.attack_type == "fgsm": + fgsm = FastGradientMethod(model) + fgsm_params = {"eps": 0.3, "clip_min": 0.0, "clip_max": 1.0} + adv_x = fgsm.generate(x_image, **fgsm_params) + elif FLAGS.attack_type == "bim": + bim = BasicIterativeMethod(model) + bim_params = { + "eps": 0.3, + "clip_min": 0.0, + "clip_max": 1.0, + "nb_iter": 50, + "eps_iter": 0.01, + } + adv_x = bim.generate(x_image, **bim_params) + else: + raise ValueError(FLAGS.attack_type) + preds_adv = model.get_probs(adv_x) + + saver = tf.train.Saver() + + with tf.Session() as sess: + # Restore the checkpoint + saver.restore(sess, checkpoint) + + # Evaluate the accuracy of the MNIST model on adversarial examples + eval_par = {"batch_size": FLAGS.batch_size} + t1 = time.time() + acc = model_eval(sess, x_image, y, preds_adv, X_test, Y_test, args=eval_par) + t2 = time.time() + print("Took", t2 - t1, "seconds") + print("Test accuracy on adversarial examples: %0.4f\n" % acc) + + +if __name__ == "__main__": + + dirs = ["models", "adv_trained"] + if "MNIST_CHALLENGE_DIR" in os.environ: + dirs.insert(0, os.environ["MNIST_CHALLENGE_DIR"]) + default_checkpoint_dir = os.path.join(*dirs) + + flags.DEFINE_integer("batch_size", 128, "batch size") + flags.DEFINE_float( + "label_smooth", + 0.1, + ("Amount to subtract from correct label " "and distribute among other labels"), + ) + flags.DEFINE_string( + "attack_type", + "fgsm", + ( + "Attack type: 'fgsm'->fast gradient sign" + "method, 'bim'->'basic iterative method'" + ), + ) + flags.DEFINE_string( + "checkpoint_dir", default_checkpoint_dir, "Checkpoint directory to load" + ) + app.run(main) diff --git a/cleverhans_v3.1.0/examples/madry_lab_challenges/mnist/madry_mnist_model.py b/cleverhans_v3.1.0/examples/madry_lab_challenges/mnist/madry_mnist_model.py new file mode 100644 index 000000000..7f87cb883 --- /dev/null +++ b/cleverhans_v3.1.0/examples/madry_lab_challenges/mnist/madry_mnist_model.py @@ -0,0 +1,89 @@ +"""cleverhans.model.Model implementation of mnist_challenge.model.Model + +This re-implementation factors variable creation apart from forward +propagation so it is possible to run forward propagation more than once +in the same model. +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from collections import OrderedDict +import tensorflow as tf +from cleverhans.model import Model +from cleverhans.utils import deterministic_dict +from cleverhans.dataset import Factory, MNIST + + +class MadryMNIST(Model): + def __init__(self, nb_classes=10): + # NOTE: for compatibility with Madry Lab downloadable checkpoints, + # we cannot use scopes, give these variables names, etc. + self.W_conv1 = self._weight_variable([5, 5, 1, 32]) + self.b_conv1 = self._bias_variable([32]) + self.W_conv2 = self._weight_variable([5, 5, 32, 64]) + self.b_conv2 = self._bias_variable([64]) + self.W_fc1 = self._weight_variable([7 * 7 * 64, 1024]) + self.b_fc1 = self._bias_variable([1024]) + self.W_fc2 = self._weight_variable([1024, nb_classes]) + self.b_fc2 = self._bias_variable([nb_classes]) + Model.__init__(self, "", nb_classes, {}) + self.dataset_factory = Factory(MNIST, {"center": False}) + + def get_params(self): + return [ + self.W_conv1, + self.b_conv1, + self.W_conv2, + self.b_conv2, + self.W_fc1, + self.b_fc1, + self.W_fc2, + self.b_fc2, + ] + + def fprop(self, x): + + output = OrderedDict() + # first convolutional layer + h_conv1 = tf.nn.relu(self._conv2d(x, self.W_conv1) + self.b_conv1) + h_pool1 = self._max_pool_2x2(h_conv1) + + # second convolutional layer + h_conv2 = tf.nn.relu(self._conv2d(h_pool1, self.W_conv2) + self.b_conv2) + h_pool2 = self._max_pool_2x2(h_conv2) + + # first fully connected layer + + h_pool2_flat = tf.reshape(h_pool2, [-1, 7 * 7 * 64]) + h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, self.W_fc1) + self.b_fc1) + + # output layer + logits = tf.matmul(h_fc1, self.W_fc2) + self.b_fc2 + + output = deterministic_dict(locals()) + del output["self"] + output[self.O_PROBS] = tf.nn.softmax(logits=logits) + + return output + + @staticmethod + def _weight_variable(shape): + initial = tf.truncated_normal(shape, stddev=0.1) + return tf.Variable(initial) + + @staticmethod + def _bias_variable(shape): + initial = tf.constant(0.1, shape=shape) + return tf.Variable(initial) + + @staticmethod + def _conv2d(x, W): + return tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding="SAME") + + @staticmethod + def _max_pool_2x2(x): + return tf.nn.max_pool( + x, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding="SAME" + ) diff --git a/examples/multigpu_advtrain/README.md b/cleverhans_v3.1.0/examples/multigpu_advtrain/README.md similarity index 100% rename from examples/multigpu_advtrain/README.md rename to cleverhans_v3.1.0/examples/multigpu_advtrain/README.md diff --git a/cleverhans_v3.1.0/examples/multigpu_advtrain/attacks_multigpu.py b/cleverhans_v3.1.0/examples/multigpu_advtrain/attacks_multigpu.py new file mode 100644 index 000000000..dd56b0f3f --- /dev/null +++ b/cleverhans_v3.1.0/examples/multigpu_advtrain/attacks_multigpu.py @@ -0,0 +1,151 @@ +# pylint: disable=missing-docstring +from collections import OrderedDict + +import tensorflow as tf + +from cleverhans.attacks import MadryEtAl +from cleverhans.utils_tf import clip_eta + +from model import clone_variable + + +class MadryEtAlMultiGPU(MadryEtAl): + + """ + A multi-GPU version of the Projected Gradient Descent Attack + (Madry et al. 2017). + Paper link: https://arxiv.org/pdf/1706.06083.pdf + + This attack is designed to run on multiple GPUs for generating adversarial + examples. + Comparing to data parallelism, using this parallelization we can get + very close to optimal n times speed up using n GPUs. The current + implementation gets close to 6x speed up on 8 GPUs. + """ + + def __init__(self, *args, **kwargs): + """ + Create a MadryEtAlMultiGPU instance. + """ + super(MadryEtAlMultiGPU, self).__init__(*args, **kwargs) + self.structural_kwargs += ["ngpu"] + + def get_or_guess_labels(self, x, kwargs): + device_name = "/gpu:0" + self.model.set_device(device_name) + with tf.device(device_name): + with tf.variable_scope("model_pred"): + ret = super(MadryEtAlMultiGPU, self).get_or_guess_labels(x, kwargs) + return ret + + def attack(self, x, y_p, **kwargs): + """ + This method creates a symoblic graph of the MadryEtAl attack on + multiple GPUs. The graph is created on the first n GPUs. + + Stop gradient is needed to get the speed-up. This prevents us from + being able to back-prop through the attack. + + :param x: A tensor with the input image. + :param y_p: Ground truth label or predicted label. + :return: Two lists containing the input and output tensors of each GPU. + """ + inputs = [] + outputs = [] + + # Create the initial random perturbation + device_name = "/gpu:0" + self.model.set_device(device_name) + with tf.device(device_name): + with tf.variable_scope("init_rand"): + if self.rand_init: + eta = tf.random_uniform(tf.shape(x), -self.eps, self.eps) + eta = clip_eta(eta, self.ord, self.eps) + eta = tf.stop_gradient(eta) + else: + eta = tf.zeros_like(x) + + # TODO: Break the graph only nGPU times instead of nb_iter times. + # The current implementation by the time an adversarial example is + # used for training, the weights of the model have changed nb_iter + # times. This can cause slower convergence compared to the single GPU + # adversarial training. + for i in range(self.nb_iter): + # Create the graph for i'th step of attack + inputs += [OrderedDict()] + outputs += [OrderedDict()] + device_name = x.device + self.model.set_device(device_name) + with tf.device(device_name): + with tf.variable_scope("step%d" % i): + if i > 0: + # Clone the variables to separate the graph of 2 GPUs + x = clone_variable("x", x) + y_p = clone_variable("y_p", y_p) + eta = clone_variable("eta", eta) + + inputs[i]["x"] = x + inputs[i]["y_p"] = y_p + outputs[i]["x"] = x + outputs[i]["y_p"] = y_p + inputs[i]["eta"] = eta + + eta = self.attack_single_step(x, eta, y_p) + + if i < self.nb_iter - 1: + outputs[i]["eta"] = eta + else: + # adv_x, not eta is the output of the last step + adv_x = x + eta + if self.clip_min is not None and self.clip_max is not None: + adv_x = tf.clip_by_value( + adv_x, self.clip_min, self.clip_max + ) + adv_x = tf.stop_gradient(adv_x, name="adv_x") + outputs[i]["adv_x"] = adv_x + + return inputs, outputs + + def generate_np(self, x_val, **kwargs): + """ + Facilitates testing this attack. + """ + _, feedable, _feedable_types, hash_key = self.construct_variables(kwargs) + + if hash_key not in self.graphs: + with tf.variable_scope(None, "attack_%d" % len(self.graphs)): + # x is a special placeholder we always want to have + with tf.device("/gpu:0"): + x = tf.placeholder(tf.float32, shape=x_val.shape, name="x") + + inputs, outputs = self.generate(x, **kwargs) + + from runner import RunnerMultiGPU + + runner = RunnerMultiGPU(inputs, outputs, sess=self.sess) + self.graphs[hash_key] = runner + + runner = self.graphs[hash_key] + feed_dict = {"x": x_val} + for name in feedable: + feed_dict[name] = feedable[name] + fvals = runner.run(feed_dict) + while not runner.is_finished(): + fvals = runner.run() + + return fvals["adv_x"] + + def parse_params(self, ngpu=1, **kwargs): + """ + Take in a dictionary of parameters and applies attack-specific checks + before saving them as attributes. + + Attack-specific parameters: + :param ngpu: (required int) the number of GPUs available. + :param kwargs: A dictionary of parameters for MadryEtAl attack. + """ + + return_status = super(MadryEtAlMultiGPU, self).parse_params(**kwargs) + self.ngpu = ngpu + + return return_status diff --git a/cleverhans_v3.1.0/examples/multigpu_advtrain/evaluator.py b/cleverhans_v3.1.0/examples/multigpu_advtrain/evaluator.py new file mode 100644 index 000000000..82cc31e5f --- /dev/null +++ b/cleverhans_v3.1.0/examples/multigpu_advtrain/evaluator.py @@ -0,0 +1,246 @@ +""" +Simplifying the evaluation of a model. Multiple attacks are initialized and +run against a model at every evaluation step. +""" +import logging + +import tensorflow as tf + +from cleverhans.utils_tf import model_eval +from cleverhans.attacks import FastGradientMethod +from cleverhans.attacks import MadryEtAl + +from attacks_multigpu import MadryEtAlMultiGPU + + +def create_adv_by_name(model, x, attack_type, sess, dataset, y=None, **kwargs): + """ + Creates the symbolic graph of an adversarial example given the name of + an attack. Simplifies creating the symbolic graph of an attack by defining + dataset-specific parameters. + Dataset-specific default parameters are used unless a different value is + given in kwargs. + + :param model: an object of Model class + :param x: Symbolic input to the attack. + :param attack_type: A string that is the name of an attack. + :param sess: Tensorflow session. + :param dataset: The name of the dataset as a string to use for default + params. + :param y: (optional) a symbolic variable for the labels. + :param kwargs: (optional) additional parameters to be passed to the attack. + """ + # TODO: black box attacks + attack_names = { + "FGSM": FastGradientMethod, + "MadryEtAl": MadryEtAl, + "MadryEtAl_y": MadryEtAl, + "MadryEtAl_multigpu": MadryEtAlMultiGPU, + "MadryEtAl_y_multigpu": MadryEtAlMultiGPU, + } + + if attack_type not in attack_names: + raise Exception("Attack %s not defined." % attack_type) + + attack_params_shared = { + "mnist": { + "eps": 0.3, + "eps_iter": 0.01, + "clip_min": 0.0, + "clip_max": 1.0, + "nb_iter": 40, + }, + "cifar10": { + "eps": 8.0 / 255, + "eps_iter": 0.01, + "clip_min": 0.0, + "clip_max": 1.0, + "nb_iter": 20, + }, + } + + with tf.variable_scope(attack_type): + attack_class = attack_names[attack_type] + attack = attack_class(model, sess=sess) + + # Extract feedable and structural keyword arguments from kwargs + fd_kwargs = attack.feedable_kwargs.keys() + attack.structural_kwargs + params = attack_params_shared[dataset].copy() + params.update({k: v for k, v in kwargs.items() if v is not None}) + params = {k: v for k, v in params.items() if k in fd_kwargs} + + if "_y" in attack_type: + params["y"] = y + logging.info(params) + adv_x = attack.generate(x, **params) + + return adv_x + + +class Evaluator(object): + """ + This class evaluates a model against multiple attacks. + """ + + def __init__( + self, sess, model, batch_size, x_pre, x, y, data, writer, hparams=None + ): + """ + :param sess: Tensorflow session. + :param model: an object of Model class + :param batch_size: batch_size for evaluation. + :param x_pre: placeholder for input before preprocessing. + :param x: symbolic input to model. + :param y: symbolic variable for the label. + :param data: a tuple with training and test data in the form + (X_train, Y_train, X_test, Y_test). + :param writer: Tensorflow summary writer. + :param hparams: Flags to control the evaluation. + """ + if hparams is None: + hparams = {} + model.set_training(False) + self.preds = model.get_probs(x) + self.sess = sess + self.batch_size = batch_size + self.x_pre = x_pre + self.x = x + self.y = y + self.X_train, self.Y_train, self.X_test, self.Y_test = data + self.writer = writer + self.hparams = hparams + + # Evaluate on a fixed subsampled set of the train data + self.eval_params = {"batch_size": batch_size} + + self.epoch = 0 + + self.attack_type_train = hparams.attack_type_train + self.attack_type_test = [] + for att_type in hparams.attack_type_test.split(","): + if att_type == "": + continue + self.attack_type_test += [att_type] + self.attacks = {} + + # Initialize the attack object and graph + for att_type in self.attack_type_test: + logging.info("Intializing attack %s" % att_type) + adv_x = create_adv_by_name( + model, x, att_type, sess, dataset=hparams.dataset, y=y + ) + + model.set_training(False) + preds_adv = model.get_probs(adv_x) + self.attacks[att_type] = (adv_x, preds_adv) + # visualize adversarial image + tf.summary.image(att_type, adv_x, max_outputs=10) + self.sum_op = tf.summary.merge_all() + + def log_value(self, tag, val, desc=""): + """ + Log values to standard output and Tensorflow summary. + + :param tag: summary tag. + :param val: (required float or numpy array) value to be logged. + :param desc: (optional) additional description to be printed. + """ + logging.info("%s (%s): %.4f" % (desc, tag, val)) + self.summary.value.add(tag=tag, simple_value=val) + + def eval_advs(self, x, y, preds_adv, X_test, Y_test, att_type): + """ + Evaluate the accuracy of the model on adversarial examples + + :param x: symbolic input to model. + :param y: symbolic variable for the label. + :param preds_adv: symbolic variable for the prediction on an + adversarial example. + :param X_test: NumPy array of test set inputs. + :param Y_test: NumPy array of test set labels. + :param att_type: name of the attack. + """ + end = (len(X_test) // self.batch_size) * self.batch_size + + if self.hparams.fast_tests: + end = 10 * self.batch_size + + acc = model_eval( + self.sess, + x, + y, + preds_adv, + X_test[:end], + Y_test[:end], + args=self.eval_params, + ) + self.log_value( + "test_accuracy_%s" % att_type, acc, "Test accuracy on adversarial examples" + ) + return acc + + def eval_multi(self, inc_epoch=True): + """ + Run the evaluation on multiple attacks. + """ + sess = self.sess + preds = self.preds + x = self.x_pre + y = self.y + X_train = self.X_train + Y_train = self.Y_train + X_test = self.X_test + Y_test = self.Y_test + writer = self.writer + + self.summary = tf.Summary() + report = {} + + # Evaluate on train set + subsample_factor = 100 + X_train_subsampled = X_train[::subsample_factor] + Y_train_subsampled = Y_train[::subsample_factor] + acc_train = model_eval( + sess, + x, + y, + preds, + X_train_subsampled, + Y_train_subsampled, + args=self.eval_params, + ) + self.log_value( + "train_accuracy_subsampled", acc_train, "Clean accuracy, subsampled train" + ) + report["train"] = acc_train + + # Evaluate on the test set + acc = model_eval(sess, x, y, preds, X_test, Y_test, args=self.eval_params) + self.log_value("test_accuracy_natural", acc, "Clean accuracy, natural test") + report["test"] = acc + + # Evaluate against adversarial attacks + if self.epoch % self.hparams.eval_iters == 0: + for att_type in self.attack_type_test: + _, preds_adv = self.attacks[att_type] + acc = self.eval_advs(x, y, preds_adv, X_test, Y_test, att_type) + report[att_type] = acc + + if self.writer: + writer.add_summary(self.summary, self.epoch) + + # Add examples of adversarial examples to the summary + if self.writer and self.epoch % 20 == 0 and self.sum_op is not None: + sm_val = self.sess.run( + self.sum_op, + feed_dict={x: X_test[: self.batch_size], y: Y_test[: self.batch_size]}, + ) + if self.writer: + writer.add_summary(sm_val) + + self.epoch += 1 if inc_epoch else 0 + + return report + + def __call__(self, **kwargs): + return self.eval_multi(**kwargs) diff --git a/cleverhans_v3.1.0/examples/multigpu_advtrain/make_model.py b/cleverhans_v3.1.0/examples/multigpu_advtrain/make_model.py new file mode 100644 index 000000000..c947ddbd8 --- /dev/null +++ b/cleverhans_v3.1.0/examples/multigpu_advtrain/make_model.py @@ -0,0 +1,69 @@ +# pylint: disable=missing-docstring +from model import Conv2D, ReLU, Flatten, Linear, Softmax, MLP +from model import MLPnGPU +from model import Conv2DnGPU +from model import LinearnGPU +from model import MaxPool + +from resnet_tf import ResNetTF + + +def make_basic_cnn(nb_filters=64, nb_classes=10, input_shape=(None, 28, 28, 1)): + layers = [ + Conv2D(nb_filters, (8, 8), (2, 2), "SAME"), + ReLU(), + Conv2D(nb_filters * 2, (6, 6), (2, 2), "VALID"), + ReLU(), + Conv2D(nb_filters * 2, (5, 5), (1, 1), "VALID"), + ReLU(), + Flatten(), + Linear(nb_classes), + Softmax(), + ] + + model = MLP(nb_classes, layers, input_shape) + return model + + +def make_basic_ngpu(nb_classes=10, input_shape=(None, 28, 28, 1), **kwargs): + """ + Create a multi-GPU model similar to the basic cnn in the tutorials. + """ + model = make_basic_cnn() + layers = model.layers + + model = MLPnGPU(nb_classes, layers, input_shape) + return model + + +def make_madry_ngpu(nb_classes=10, input_shape=(None, 28, 28, 1), **kwargs): + """ + Create a multi-GPU model similar to Madry et al. (arXiv:1706.06083). + """ + layers = [ + Conv2DnGPU(32, (5, 5), (1, 1), "SAME"), + ReLU(), + MaxPool((2, 2), (2, 2), "SAME"), + Conv2DnGPU(64, (5, 5), (1, 1), "SAME"), + ReLU(), + MaxPool((2, 2), (2, 2), "SAME"), + Flatten(), + LinearnGPU(1024), + ReLU(), + LinearnGPU(nb_classes), + Softmax(), + ] + + model = MLPnGPU(nb_classes, layers, input_shape) + return model + + +def make_model(model_type="madry", **kwargs): + if model_type == "basic": + return make_basic_ngpu(**kwargs) + elif model_type == "madry": + return make_madry_ngpu(**kwargs) + elif model_type == "resnet_tf": + return ResNetTF(**kwargs) + else: + raise Exception("model type not defined.") diff --git a/cleverhans_v3.1.0/examples/multigpu_advtrain/model.py b/cleverhans_v3.1.0/examples/multigpu_advtrain/model.py new file mode 100644 index 000000000..399ec0535 --- /dev/null +++ b/cleverhans_v3.1.0/examples/multigpu_advtrain/model.py @@ -0,0 +1,431 @@ +""" +MultiGPU model similar to the one used in model tutorials. The model keeps +one copy of the weights on each device and handles syncing the parameters +across devices. +""" +# pylint: disable=missing-docstring +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import numpy as np +import tensorflow as tf + +from cleverhans.model import Model + + +def clone_variable(name, x, trainable=False): + return tf.get_variable(name, shape=x.shape, dtype=x.dtype, trainable=trainable) + + +def unify_device_name(dname): + """Converts TensorFlow device names in the format /Device:GPU0 to /gpu:0.""" + if dname is None: + return None + return dname.lower().replace("device:", "") + + +class MLP(Model): + """ + An example of a bare bones multilayer perceptron (MLP) class. + """ + + def __init__(self, nb_classes, layers, input_shape): + super(MLP, self).__init__("MLP", nb_classes=nb_classes, hparams={}) + + self.layer_names = [] + self.layers = layers + self.input_shape = input_shape + if isinstance(layers[-1], Softmax): + layers[-1].name = "probs" + layers[-2].name = "logits" + else: + layers[-1].name = "logits" + for i, layer in enumerate(self.layers): + if hasattr(layer, "name"): + name = layer.name + else: + name = layer.__class__.__name__ + str(i) + layer.name = name + self.layer_names.append(name) + + layer.set_input_shape(input_shape) + input_shape = layer.get_output_shape() + + def fprop(self, x, set_ref=False): + states = [] + for layer in self.layers: + if set_ref: + layer.ref = x + x = layer.fprop(x) + assert x is not None + states.append(x) + states = dict(zip(self.layer_names, states)) + return states + + def get_params(self): + out = [] + for layer in self.layers: + for param in layer.get_params(): + if param not in out: + out.append(param) + return out + + +class Layer(object): + def get_output_shape(self): + return self.output_shape + + +class Linear(Layer): + def __init__(self, num_hid): + self.num_hid = num_hid + + def set_input_shape(self, input_shape): + batch_size, dim = input_shape + self.input_shape = [batch_size, dim] + self.output_shape = [batch_size, self.num_hid] + init = tf.random_normal([dim, self.num_hid], dtype=tf.float32) + init = init / tf.sqrt( + 1e-7 + tf.reduce_sum(tf.square(init), axis=0, keep_dims=True) + ) + self.W = tf.Variable(init) + self.b = tf.Variable(np.zeros((self.num_hid,)).astype("float32")) + + def fprop(self, x): + return tf.matmul(x, self.W) + self.b + + def get_params(self): + return [self.W, self.b] + + +class Conv2D(Layer): + def __init__(self, output_channels, kernel_shape, strides, padding): + self.__dict__.update(locals()) + del self.self + + def set_input_shape(self, input_shape): + batch_size, _, __, input_channels = input_shape + kernel_shape = tuple(self.kernel_shape) + (input_channels, self.output_channels) + assert len(kernel_shape) == 4 + assert all(isinstance(e, int) for e in kernel_shape), kernel_shape + init = tf.random_normal(kernel_shape, dtype=tf.float32) + init = init / tf.sqrt(1e-7 + tf.reduce_sum(tf.square(init), axis=(0, 1, 2))) + self.kernels = tf.Variable(init) + self.b = tf.Variable(np.zeros((self.output_channels,)).astype("float32")) + input_shape = list(input_shape) + input_shape[0] = 1 + dummy_batch = tf.zeros(input_shape) + dummy_output = self.fprop(dummy_batch) + output_shape = [int(e) for e in dummy_output.get_shape()] + output_shape[0] = batch_size + self.output_shape = tuple(output_shape) + + def fprop(self, x): + return ( + tf.nn.conv2d( + x, self.kernels, (1,) + tuple(self.strides) + (1,), self.padding + ) + + self.b + ) + + def get_params(self): + return [self.kernels, self.b] + + +class ReLU(Layer): + def __init__(self): + pass + + def set_input_shape(self, shape): + self.input_shape = shape + self.output_shape = shape + + def fprop(self, x): + return tf.nn.relu(x) + + def get_params(self): + return [] + + +class Softmax(Layer): + def __init__(self): + pass + + def set_input_shape(self, shape): + self.input_shape = shape + self.output_shape = shape + + def fprop(self, x): + return tf.nn.softmax(x) + + def get_params(self): + return [] + + +class Flatten(Layer): + def __init__(self): + pass + + def set_input_shape(self, shape): + self.input_shape = shape + output_width = 1 + for factor in shape[1:]: + output_width *= factor + self.output_width = output_width + self.output_shape = [shape[0], output_width] + + def fprop(self, x): + return tf.reshape(x, [-1, self.output_width]) + + def get_params(self): + return [] + + +class MLPnGPU(MLP): + """ + A multi layer perceptron that can be copied over multiple GPUs. Only one + copy of the weights is created on each device. + """ + + def __init__(self, nb_classes, layers, input_shape): + super(MLPnGPU, self).__init__(nb_classes, layers, input_shape) + self.scope = "MLPnGPU" + + def fprop(self, x): + with tf.variable_scope(self.scope): + states = super(MLPnGPU, self).fprop(x) + return states + + def set_device(self, device_name): + """ + Set the device before the next fprop to create a new graph on the + specified device. + """ + device_name = unify_device_name(device_name) + self.device_name = device_name + for layer in self.layers: + layer.device_name = device_name + + def create_sync_ops(self, host_device): + """ + Return a list of assignment operations that syncs the parameters + of all model copies with the one on host_device. + :param host_device: (required str) the name of the device with latest + parameters + """ + host_device = unify_device_name(host_device) + sync_ops = [] + for layer in self.layers: + if isinstance(layer, LayernGPU): + sync_ops += layer.create_sync_ops(host_device) + return sync_ops + + def set_training(self, training=False): + for layer in self.layers: + if isinstance(layer, LayernGPU): + layer.set_training(training) + + +class LayernGPU(Layer): + """ + A layer that has separate copies of model parameters on each GPU. + """ + + def __init__(self): + """ + :param input_shape: a tuple or list as the input shape to layer + """ + self.input_shape = None + self.params_device = {} + self.params_names = None + self.device_name = "/gpu:0" + self.training = True + + def set_training(self, training=False): + self.training = training + + def get_variable(self, name, initializer): + """ + Create and initialize a variable using a numpy array and set trainable. + :param name: (required str) name of the variable + :param initializer: a numpy array or a tensor + """ + v = tf.get_variable( + name, + shape=initializer.shape, + initializer=(lambda shape, dtype, partition_info: initializer), + trainable=self.training, + ) + return v + + def set_input_shape_ngpu(self, new_input_shape): + """ + Create and initialize layer parameters on the device previously set + in self.device_name. + + :param new_input_shape: a list or tuple for the shape of the input. + """ + assert self.device_name, "Device name has not been set." + + device_name = self.device_name + if self.input_shape is None: + # First time setting the input shape + self.input_shape = [None] + [int(d) for d in list(new_input_shape)] + + if device_name in self.params_device: + # There is a copy of weights on this device + self.__dict__.update(self.params_device[device_name]) + return + + # Stop recursion + self.params_device[device_name] = {} + + # Initialize weights on this device + with tf.device(device_name): + self.set_input_shape(self.input_shape) + keys_after = self.__dict__.keys() + if self.params_names is None: + # Prevent overriding training + self.params_names = [ + k for k in keys_after if isinstance(self.__dict__[k], tf.Variable) + ] + params = {k: self.__dict__[k] for k in self.params_names} + self.params_device[device_name] = params + + def create_sync_ops(self, host_device): + """Create an assignment operation for each weight on all devices. The + weight is assigned the value of the copy on the `host_device'. + """ + sync_ops = [] + host_params = self.params_device[host_device] + for device, params in (self.params_device).iteritems(): + if device == host_device: + continue + for k in self.params_names: + if isinstance(params[k], tf.Variable): + sync_ops += [tf.assign(params[k], host_params[k])] + return sync_ops + + def fprop(self, x): + if self.name is None: + self.set_input_shape_ngpu(x.shape[1:]) + return self.fprop_noscope(x) + else: + with tf.variable_scope(self.name): + self.set_input_shape_ngpu(x.shape[1:]) + return self.fprop_noscope(x) + + +class LinearnGPU(LayernGPU): + def __init__(self, num_hid, w_name="W"): + super(LinearnGPU, self).__init__() + self.num_hid = num_hid + self.w_name = w_name + + def set_input_shape(self, input_shape): + batch_size, dim = input_shape + self.input_shape = [batch_size, dim] + self.output_shape = [batch_size, self.num_hid] + shape = [dim, self.num_hid] + with tf.variable_scope(self.name): + init = tf.truncated_normal(shape, stddev=0.1) + self.W = self.get_variable(self.w_name, init) + self.b = self.get_variable( + "b", 0.1 + np.zeros((self.num_hid,)).astype("float32") + ) + + def fprop_noscope(self, x): + return tf.matmul(x, self.W) + self.b + + +class Conv2DnGPU(LayernGPU): + def __init__( + self, output_channels, kernel_shape, strides, padding, w_name="kernels" + ): + super(Conv2DnGPU, self).__init__() + self.__dict__.update(locals()) + del self.self + self.w_name = w_name + + def set_input_shape(self, input_shape): + assert len(input_shape) == 4 + input_channels = input_shape[3] + kernel_shape = tuple(self.kernel_shape) + (input_channels, self.output_channels) + assert len(kernel_shape) == 4 + assert all(isinstance(e, int) for e in kernel_shape), kernel_shape + with tf.variable_scope(self.name): + init = tf.truncated_normal(kernel_shape, stddev=0.1) + self.kernels = self.get_variable(self.w_name, init) + self.b = self.get_variable( + "b", 0.1 + np.zeros((self.output_channels,)).astype("float32") + ) + input_shape = list(input_shape) + self.input_shape = input_shape + input_shape[0] = 1 + dummy_batch = tf.zeros(input_shape) + dummy_output = self.fprop(dummy_batch) + output_shape = [int(e) for e in dummy_output.get_shape()] + output_shape[0] = 1 + self.output_shape = tuple(output_shape) + + def fprop_noscope(self, x): + return ( + tf.nn.conv2d( + x, self.kernels, (1,) + tuple(self.strides) + (1,), self.padding + ) + + self.b + ) + + +class MaxPool(LayernGPU): + def __init__(self, ksize, strides, padding): + super(MaxPool, self).__init__() + self.__dict__.update(locals()) + del self.self + + def set_input_shape(self, input_shape): + input_shape = list(input_shape) + input_shape[0] = 1 + dummy_batch = tf.zeros(input_shape) + dummy_output = self.fprop(dummy_batch) + output_shape = [int(e) for e in dummy_output.get_shape()] + output_shape[0] = 1 + self.output_shape = tuple(output_shape) + + def fprop_noscope(self, x): + return tf.nn.max_pool( + x, + ksize=(1,) + tuple(self.ksize) + (1,), + strides=(1,) + tuple(self.strides) + (1,), + padding=self.padding, + ) + + +class LayerNorm(LayernGPU): + def set_input_shape(self, input_shape): + self.input_shape = list(input_shape) + params_shape = [input_shape[-1]] + self.params_shape = params_shape + + self.beta = tf.get_variable( + "beta", + params_shape, + tf.float32, + initializer=tf.constant_initializer(0.0, tf.float32), + trainable=self.training, + ) + self.gamma = tf.get_variable( + "gamma", + params_shape, + tf.float32, + initializer=tf.constant_initializer(1.0, tf.float32), + trainable=self.training, + ) + + def fprop_noscope(self, x): + mean = tf.reduce_mean(x, (1, 2), keep_dims=True) + x = x - mean + std = tf.sqrt(1e-7 + tf.reduce_mean(tf.square(x), (1, 2), keep_dims=True)) + x = x / std + return x * self.gamma + self.beta diff --git a/cleverhans_v3.1.0/examples/multigpu_advtrain/resnet_tf.py b/cleverhans_v3.1.0/examples/multigpu_advtrain/resnet_tf.py new file mode 100644 index 000000000..a1ac146db --- /dev/null +++ b/cleverhans_v3.1.0/examples/multigpu_advtrain/resnet_tf.py @@ -0,0 +1,345 @@ +# https://github.com/tensorflow/models/blob/master/research/resnet/resnet_model.py +# +# Copyright 2016 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== + +"""ResNet model. + +Related papers: +https://arxiv.org/pdf/1603.05027v2.pdf +https://arxiv.org/pdf/1512.03385v1.pdf +https://arxiv.org/pdf/1605.07146v1.pdf +""" +from collections import namedtuple + +import tensorflow as tf +import six + +from model import MLPnGPU +from model import Conv2DnGPU +from model import LinearnGPU +from model import LayerNorm + +HParams = namedtuple( + "HParams", + "batch_size, nb_classes, min_lrn_rate, lrn_rate, " + "num_residual_units, use_bottleneck, weight_decay_rate, " + "relu_leakiness, momentum", +) + + +class ResNetTF(MLPnGPU): + """ResNet model.""" + + def __init__(self, batch_size=None, name=None, **kwargs): + NB_CLASSES = 10 + super(ResNetTF, self).__init__( + nb_classes=NB_CLASSES, layers=[], input_shape=None + ) + self.global_step = tf.contrib.framework.get_or_create_global_step() + self.hps = HParams( + batch_size=batch_size, + nb_classes=NB_CLASSES, + min_lrn_rate=0.0001, + lrn_rate=0.1, + num_residual_units=5, + use_bottleneck=False, + weight_decay_rate=0.0002, + relu_leakiness=0.1, + momentum=0.9, + ) + self.layers = [] + self.layer_idx = 0 + self.init_layers = True + self.decay_cost = None + self.training = None + self.device_name = None + + def set_training(self, training=False): + super(ResNetTF, self).set_training(training) + self.training = training + + def fprop(self, x): + self.layer_idx = 0 + with tf.variable_scope("Resnet"): + logits, probs = self._build_model(x) + self.init_layers = False + states = {"logits": logits, "probs": probs} + return states + + def _stride_arr(self, stride): + """Map a stride scalar to the stride array for tf.nn.conv2d.""" + return [1, stride, stride, 1] + + def _build_model(self, x): + """Build the core model within the graph.""" + with tf.variable_scope("init"): + x = self._conv("init_conv", x, 3, x.shape[3], 16, self._stride_arr(1)) + + strides = [1, 2, 2] + activate_before_residual = [True, False, False] + if self.hps.use_bottleneck: + res_func = self._bottleneck_residual + filters = [16, 64, 128, 256] + else: + res_func = self._residual + filters = [16, 16, 32, 64] + # Uncomment the following codes to use w28-10 wide residual + # network. + # It is more memory efficient than very deep residual network and + # has + # comparably good performance. + # https://arxiv.org/pdf/1605.07146v1.pdf + # filters = [16, 160, 320, 640] + # Update hps.num_residual_units to 4 + + with tf.variable_scope("unit_1_0"): + x = res_func( + x, + filters[0], + filters[1], + self._stride_arr(strides[0]), + activate_before_residual[0], + ) + for i in six.moves.range(1, self.hps.num_residual_units): + with tf.variable_scope("unit_1_%d" % i): + x = res_func(x, filters[1], filters[1], self._stride_arr(1), False) + + with tf.variable_scope("unit_2_0"): + x = res_func( + x, + filters[1], + filters[2], + self._stride_arr(strides[1]), + activate_before_residual[1], + ) + for i in six.moves.range(1, self.hps.num_residual_units): + with tf.variable_scope("unit_2_%d" % i): + x = res_func(x, filters[2], filters[2], self._stride_arr(1), False) + + with tf.variable_scope("unit_3_0"): + x = res_func( + x, + filters[2], + filters[3], + self._stride_arr(strides[2]), + activate_before_residual[2], + ) + for i in six.moves.range(1, self.hps.num_residual_units): + with tf.variable_scope("unit_3_%d" % i): + x = res_func(x, filters[3], filters[3], self._stride_arr(1), False) + + with tf.variable_scope("unit_last"): + x = self._layer_norm("final_bn", x) + x = self._relu(x, self.hps.relu_leakiness) + x = self._global_avg_pool(x) + + with tf.variable_scope("logit"): + logits = self._fully_connected(x, self.hps.nb_classes) + predictions = tf.nn.softmax(logits) + + return logits, predictions + + def build_cost(self, labels, logits): + """ + Build the graph for cost from the logits if logits are provided. + If predictions are provided, logits are extracted from the operation. + """ + op = logits.op + if "softmax" in str(op).lower(): + (logits,) = op.inputs + + with tf.variable_scope("costs"): + xent = tf.nn.softmax_cross_entropy_with_logits(logits=logits, labels=labels) + cost = tf.reduce_mean(xent, name="xent") + cost += self._decay() + cost = cost + + return cost + + def build_train_op_from_cost(self, cost): + """Build training specific ops for the graph.""" + self.lrn_rate = tf.constant(self.hps.lrn_rate, tf.float32, name="learning_rate") + self.momentum = tf.constant(self.hps.momentum, tf.float32, name="momentum") + + trainable_variables = tf.trainable_variables() + grads = tf.gradients(cost, trainable_variables) + devs = {v.device for v in trainable_variables} + assert len(devs) == 1, ( + "There should be no trainable variables" + " on any device other than the last GPU." + ) + + optimizer = tf.train.MomentumOptimizer(self.lrn_rate, self.momentum) + + gv_pairs = zip(grads, trainable_variables) + gv_pairs = [gv for gv in gv_pairs if gv[0] is not None] + devs = {gv[1].device for gv in gv_pairs} + assert len(devs) == 1, "There should be no gradients wrt" " vars on other GPUs." + + apply_op = optimizer.apply_gradients( + gv_pairs, global_step=self.global_step, name="train_step" + ) + + train_ops = [apply_op] + train_op = tf.group(*train_ops) + return train_op + + def _layer_norm(self, name, x): + """Layer normalization.""" + if self.init_layers: + bn = LayerNorm() + bn.name = name + self.layers += [bn] + else: + bn = self.layers[self.layer_idx] + self.layer_idx += 1 + bn.device_name = self.device_name + bn.set_training(self.training) + x = bn.fprop(x) + return x + + def _residual( + self, x, in_filter, out_filter, stride, activate_before_residual=False + ): + """Residual unit with 2 sub layers.""" + if activate_before_residual: + with tf.variable_scope("shared_activation"): + x = self._layer_norm("init_bn", x) + x = self._relu(x, self.hps.relu_leakiness) + orig_x = x + else: + with tf.variable_scope("residual_only_activation"): + orig_x = x + x = self._layer_norm("init_bn", x) + x = self._relu(x, self.hps.relu_leakiness) + + with tf.variable_scope("sub1"): + x = self._conv("conv1", x, 3, in_filter, out_filter, stride) + + with tf.variable_scope("sub2"): + x = self._layer_norm("bn2", x) + x = self._relu(x, self.hps.relu_leakiness) + x = self._conv("conv2", x, 3, out_filter, out_filter, [1, 1, 1, 1]) + + with tf.variable_scope("sub_add"): + if in_filter != out_filter: + orig_x = tf.nn.avg_pool(orig_x, stride, stride, "VALID") + orig_x = tf.pad( + orig_x, + [ + [0, 0], + [0, 0], + [0, 0], + [(out_filter - in_filter) // 2, (out_filter - in_filter) // 2], + ], + ) + x += orig_x + + return x + + def _bottleneck_residual( + self, x, in_filter, out_filter, stride, activate_before_residual=False + ): + """Bottleneck residual unit with 3 sub layers.""" + if activate_before_residual: + with tf.variable_scope("common_bn_relu"): + x = self._layer_norm("init_bn", x) + x = self._relu(x, self.hps.relu_leakiness) + orig_x = x + else: + with tf.variable_scope("residual_bn_relu"): + orig_x = x + x = self._layer_norm("init_bn", x) + x = self._relu(x, self.hps.relu_leakiness) + + with tf.variable_scope("sub1"): + x = self._conv("conv1", x, 1, in_filter, out_filter / 4, stride) + + with tf.variable_scope("sub2"): + x = self._layer_norm("bn2", x) + x = self._relu(x, self.hps.relu_leakiness) + x = self._conv("conv2", x, 3, out_filter / 4, out_filter / 4, [1, 1, 1, 1]) + + with tf.variable_scope("sub3"): + x = self._layer_norm("bn3", x) + x = self._relu(x, self.hps.relu_leakiness) + x = self._conv("conv3", x, 1, out_filter / 4, out_filter, [1, 1, 1, 1]) + + with tf.variable_scope("sub_add"): + if in_filter != out_filter: + orig_x = self._conv("project", orig_x, 1, in_filter, out_filter, stride) + x += orig_x + + return x + + def _decay(self): + """L2 weight decay loss.""" + if self.decay_cost is not None: + return self.decay_cost + + costs = [] + if self.device_name is None: + for var in tf.trainable_variables(): + if var.op.name.find(r"DW") > 0: + costs.append(tf.nn.l2_loss(var)) + else: + for layer in self.layers: + for var in layer.params_device[self.device_name].values(): + if isinstance(var, tf.Variable) and var.op.name.find(r"DW") > 0: + costs.append(tf.nn.l2_loss(var)) + + self.decay_cost = tf.multiply(self.hps.weight_decay_rate, tf.add_n(costs)) + return self.decay_cost + + def _conv(self, name, x, filter_size, in_filters, out_filters, strides): + """Convolution.""" + if self.init_layers: + conv = Conv2DnGPU( + out_filters, + (filter_size, filter_size), + strides[1:3], + "SAME", + w_name="DW", + ) + conv.name = name + self.layers += [conv] + else: + conv = self.layers[self.layer_idx] + self.layer_idx += 1 + conv.device_name = self.device_name + conv.set_training(self.training) + return conv.fprop(x) + + def _relu(self, x, leakiness=0.0): + """Relu, with optional leaky support.""" + return tf.where(tf.less(x, 0.0), leakiness * x, x, name="leaky_relu") + + def _fully_connected(self, x, out_dim): + """FullyConnected layer for final output.""" + if self.init_layers: + fc = LinearnGPU(out_dim, w_name="DW") + fc.name = "logits" + self.layers += [fc] + else: + fc = self.layers[self.layer_idx] + self.layer_idx += 1 + fc.device_name = self.device_name + fc.set_training(self.training) + return fc.fprop(x) + + def _global_avg_pool(self, x): + assert x.get_shape().ndims == 4 + return tf.reduce_mean(x, [1, 2]) diff --git a/cleverhans_v3.1.0/examples/multigpu_advtrain/run_multigpu.py b/cleverhans_v3.1.0/examples/multigpu_advtrain/run_multigpu.py new file mode 100644 index 000000000..c7c7991cf --- /dev/null +++ b/cleverhans_v3.1.0/examples/multigpu_advtrain/run_multigpu.py @@ -0,0 +1,85 @@ +""" +This script adversarially trains a model using iterative attacks on multiple +GPUs. +""" +# pylint: disable=missing-docstring +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import logging +from collections import namedtuple + +from cleverhans.compat import app, flags + +from trainer import TrainerMultiGPU +from trainer import TrainerSingleGPU + + +def run_trainer(hparams): + logging.basicConfig(format="%(asctime)s %(message)s", level=logging.INFO) + + if "multigpu" in hparams.attack_type_train: + logging.info("Multi GPU Trainer.") + trainer = TrainerMultiGPU(hparams) + else: + logging.info("Single GPU Trainer.") + trainer = TrainerSingleGPU(hparams) + trainer.model_train() + trainer.eval(inc_epoch=False) + + return trainer.finish() + + +def main(argv=None): + f = {x: flags.FLAGS[x].value for x in dir(flags.FLAGS)} + HParams = namedtuple("HParams", f.keys()) + hparams = HParams(**f) + run_trainer(hparams) + + +if __name__ == "__main__": + flags.DEFINE_integer("train_start", 0, "Index of first training set example.") + flags.DEFINE_integer("train_end", 60000, "Index of last training set example.") + flags.DEFINE_integer("test_start", 0, "Index of first test set example.") + flags.DEFINE_integer("test_end", 10000, "Index of last test set example.") + flags.DEFINE_integer("nb_epochs", 6, "Number of epochs to train model.") + flags.DEFINE_integer("batch_size", 128, "Size of training batches.") + flags.DEFINE_boolean("adv_train", False, "Whether to do adversarial training.") + flags.DEFINE_boolean("save", True, "Whether to save from a checkpoint.") + flags.DEFINE_string("save_dir", "runs/X", "Location to store logs/model.") + flags.DEFINE_string("model_type", "madry", "Model type: basic|madry|resnet_tf.") + flags.DEFINE_string( + "attack_type_train", + "MadryEtAl_y_multigpu", + "Attack type for adversarial training:\ + FGSM|MadryEtAl{,_y}{,_multigpu}.", + ) + flags.DEFINE_string( + "attack_type_test", "FGSM", "Attack type for test: FGSM|MadryEtAl{,_y}." + ) + flags.DEFINE_string("dataset", "mnist", "Dataset mnist|cifar10.") + flags.DEFINE_boolean( + "only_adv_train", False, "Do not train with clean examples when adv training." + ) + flags.DEFINE_integer("save_steps", 50, "Save model per X steps.") + flags.DEFINE_integer( + "attack_nb_iter_train", None, "Number of iterations of training attack." + ) + flags.DEFINE_integer("eval_iters", 1, "Evaluate every X steps.") + flags.DEFINE_integer( + "lrn_step", 30000, "Step to decrease learning rate" "for ResNet." + ) + flags.DEFINE_float("adam_lrn", 0.001, "Learning rate for Adam Optimizer.") + flags.DEFINE_float("mom_lrn", 0.1, "Learning rate for Momentum Optimizer.") + flags.DEFINE_integer("ngpu", 1, "Number of gpus.") + flags.DEFINE_integer("sync_step", 1, "Sync params frequency.") + flags.DEFINE_boolean("fast_tests", False, "Fast tests against attacks.") + flags.DEFINE_string( + "data_path", + "./datasets/", + "Path to datasets." "Each dataset should be in a subdirectory.", + ) + + app.run() diff --git a/cleverhans_v3.1.0/examples/multigpu_advtrain/runner.py b/cleverhans_v3.1.0/examples/multigpu_advtrain/runner.py new file mode 100644 index 000000000..853290e6a --- /dev/null +++ b/cleverhans_v3.1.0/examples/multigpu_advtrain/runner.py @@ -0,0 +1,165 @@ +"""Wrappers to TensorFlow Session.run(). +""" +# pylint: disable=missing-docstring +from collections import OrderedDict + + +class Runner(object): + """ + Wrap TensorFlow Session.run() by adding preprocessing and postprocessing + steps. + """ + + def __init__(self, inputs, outputs, sess=None): + self.sess = sess + self.inputs = inputs + self.outputs = outputs + self.feed_dict = {} + + def run(self, X_batch=None): + fetches, feed_dict = self.set_input(X_batch) + fvals = self.sess.run(fetches, feed_dict=feed_dict) + return self.proc_fvals(fvals) + + def set_input(self, X_batch=None): + raise NotImplementedError("set_input not implemented.") + + def proc_fvals(self, fvals): + raise NotImplementedError("proc_fvals not implemented.") + + def is_finished(self): + raise NotImplementedError("is_finished not implemented.") + + +class RunnerMultiGPU(Runner): + """ + Runs a graph with sub-graphs that need to run sequentially. Each sub-graph + takes its inputs from the outputs of the previous sub-graph. + """ + + def __init__(self, *args, **kwargs): + super(RunnerMultiGPU, self).__init__(*args, **kwargs) + self.assert_inputs_outputs() + self.next_vals = [None] * len(self.inputs) + + def assert_inputs_outputs(self): + inputs = self.inputs + outputs = self.outputs + assert len(inputs) == len(outputs), "Inputs and Outputs should match in length." + for i in range(len(inputs)): + device = inputs[i].values()[0].device + for _k, v in inputs[i].iteritems(): + assert v.device == device, "Inputs should be on the same device." + for _k, v in outputs[i].iteritems(): + assert v.device == device, "Outputs should be on the same device." + if i > 0: + ikeys = inputs[i].keys() + okeys = outputs[i - 1].keys() + # The actual requirement is looser, only the last output keys + # should always be returned in the same order. + assert all( + ikeys[j] == okeys[j] for j in range(len(ikeys)) + ), "Inputs and outputs keys should be in the same order." + + def set_input(self, X_batch=None): + """ + Preprocessing the inputs before calling session.run() + + :param X_batch: A dictionary of inputs to the first sub-graph + :return: A tuple, `(fetches, fd)`, with `fetches` being a list of + Tensors to be fetches and `fd` the feed dictionary. + """ + inputs = self.inputs + outputs = self.outputs + + # data for first gpu + fd = {} + if X_batch is not None: + self.next_vals[0] = OrderedDict() + for i, vname in enumerate(self.inputs[0]): + if vname in X_batch: + self.next_vals[0][vname] = X_batch[vname] + else: + self.next_vals[0][vname] = None + else: + self.next_vals[0] = None + + # Set `feed_dict` for each GPU. If there is something to run for that + # GPU, collect outputs to be fetched. + fetches = [] + self.active_gpus = [] + for i in range(len(outputs)): + if self.next_vals[i] is None: + self.active_gpus += [False] + continue + self.active_gpus += [True] + for k in inputs[i]: + if self.next_vals[i][k] is not None: + fd[inputs[i][k]] = self.next_vals[i][k] + for k, v in outputs[i].iteritems(): + fetches += [v] + + fd.update(self.feed_dict) + + return fetches, fd + + def proc_fvals(self, fvals): + """ + Postprocess the outputs of the Session.run(). Move the outputs of + sub-graphs to next ones and return the output of the last sub-graph. + + :param fvals: A list of fetched values returned by Session.run() + :return: A dictionary of fetched values returned by the last sub-graph. + """ + inputs = self.inputs + outputs = self.outputs + + # Move data to the next sub-graph for the next step + cur = 0 + for i in range(len(inputs) - 1): + if not self.active_gpus[i]: + self.next_vals[i + 1] = None + continue + self.next_vals[i + 1] = OrderedDict() + for k in outputs[i]: + self.next_vals[i + 1][k] = fvals[cur] + cur += 1 + if i == 0: + self.next_vals[0] = None + + # Return the output of the last sub-graph + last_fvals = OrderedDict() + if self.active_gpus[-1]: + assert cur + len(outputs[-1]) == len(fvals) + for k in outputs[-1]: + last_fvals[k] = fvals[cur] + cur += 1 + return last_fvals + + def is_finished(self): + return all(v is None for v in self.next_vals) + + +class RunnerSingleGPU(Runner): + def __init__(self, *args, **kwargs): + super(RunnerSingleGPU, self).__init__(*args, **kwargs) + + def set_input(self, X_batch=None): + fd = {} + for vname, v in self.inputs[0].iteritems(): + if vname in X_batch: + fd[v] = X_batch[vname] + fetches = self.outputs + return fetches, fd + + def proc_fvals(self, fvals): + """ + Nothing to post-process on single GPU. + """ + return True + + def is_finished(self): + """ + Single GPU trainer has no cache. + """ + return True diff --git a/cleverhans_v3.1.0/examples/multigpu_advtrain/test_attack_multigpu.py b/cleverhans_v3.1.0/examples/multigpu_advtrain/test_attack_multigpu.py new file mode 100644 index 000000000..86c14296f --- /dev/null +++ b/cleverhans_v3.1.0/examples/multigpu_advtrain/test_attack_multigpu.py @@ -0,0 +1,88 @@ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import unittest +import numpy as np +import tensorflow as tf + +from attacks_multigpu import MadryEtAlMultiGPU +from model import MLPnGPU +from model import LayernGPU + + +import sys +import os + +sys.path.insert(0, os.path.abspath("../../tests_tf/")) +from test_attacks import TestMadryEtAl # NOQA + + +class TestMadryEtAlMultiGPU(TestMadryEtAl): + """ + By inherting from `TestMadryEtAl`, the attack `MadryEtAlMultiGPU` can be + tested against all tests of the base attack. + """ + + def setUp(self): + super(TestMadryEtAlMultiGPU, self).setUp() + + class SimpleLayer(LayernGPU): + def set_input_shape(self, input_shape): + self.input_shape = input_shape + self.output_shape = input_shape + self.W1 = tf.constant([[1.5, 0.3], [-2, 0.3]], dtype=tf.float32) + self.W2 = tf.constant([[-2.4, 1.2], [0.5, -2.3]], dtype=tf.float32) + + def fprop_noscope(self, x): + h1 = tf.nn.sigmoid(tf.matmul(x, self.W1)) + res = tf.matmul(h1, self.W2) + return res + + input_shape = (None, 2) + self.model_ngpu = MLPnGPU([SimpleLayer()], input_shape) + + self.attack_single_gpu = self.attack + self.attack_multi_gpu = MadryEtAlMultiGPU(self.model_ngpu, sess=self.sess) + self.attack = self.attack_multi_gpu + + def test_single_vs_multi_gpu(self): + """ + Compare the strength of the single GPU and multi-GPU implementations. + """ + x_val = np.random.rand(100, 2) + x_val = np.array(x_val, dtype=np.float32) + + def multi_attack(attack): + flags = { + "ngpu": 1, + "eps": 1.0, + "eps_iter": 0.01, + "clip_min": 0.5, + "clip_max": 0.7, + "nb_iter": 2, + "rand_init": True, + } + + orig_labs = np.argmax(self.sess.run(self.model(x_val)), axis=1) + new_labs_multi = orig_labs.copy() + # Generate multiple adversarial examples + for i in range(40): + x_adv = attack.generate_np(x_val, **flags) + new_labs = np.argmax(self.sess.run(self.model(x_adv)), axis=1) + + # Examples for which we have not found adversarial examples + indices = orig_labs == new_labs_multi + new_labs_multi[indices] = new_labs[indices] + + return np.mean(orig_labs == new_labs_multi) + + acc_s = multi_attack(self.attack_single_gpu) + acc_m = multi_attack(self.attack_multi_gpu) + + self.assertClose(acc_s, acc_m, atol=1e-2) + + +if __name__ == "__main__": + unittest.main() diff --git a/cleverhans_v3.1.0/examples/multigpu_advtrain/test_run_multigpu.py b/cleverhans_v3.1.0/examples/multigpu_advtrain/test_run_multigpu.py new file mode 100644 index 000000000..9a3d96470 --- /dev/null +++ b/cleverhans_v3.1.0/examples/multigpu_advtrain/test_run_multigpu.py @@ -0,0 +1,190 @@ +# pylint: disable=missing-docstring +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +from collections import namedtuple +import unittest + +import numpy as np +import tensorflow as tf + +from cleverhans.utils import AccuracyReport +from cleverhans.devtools.checks import CleverHansTest +from run_multigpu import run_trainer + + +class TestRunMultiGPU(CleverHansTest): + def helper_run_multi_gpu_madryetal(self, extra_flags=None): + """ + Compare the single GPU performance to multiGPU performance. + """ + # Run the trainers on a dataset of reduced size + flags = { + "train_start": 0, + "train_end": 5000, + "test_start": 0, + "test_end": 333, + "nb_epochs": 5, + "testing": True, + } + + # Run the multi-gpu trainer for adversarial training + flags.update( + { + "batch_size": 128, + "adam_lrn": 0.001, + "dataset": "mnist", + "only_adv_train": False, + "eval_iters": 1, + "fast_tests": True, + "save_dir": None, + "save_steps": 10000, + "attack_nb_iter_train": 10, + "sync_step": None, + "adv_train": True, + "save": False, + "model_type": "basic", + "attack_type_test": "MadryEtAl_y", + } + ) + if extra_flags is not None: + flags.update(extra_flags) + + # Run the multi-gpu trainer for adversarial training using 2 gpus + # trainer_multigpu by default sets `allow_soft_placement=True` + flags.update( + {"ngpu": 2, "attack_type_train": "MadryEtAl_y_multigpu", "sync_step": 1} + ) + HParams = namedtuple("HParams", flags.keys()) + + hparams = HParams(**flags) + np.random.seed(42) + tf.set_random_seed(42) + with tf.variable_scope(None, "runner"): + report_dict = run_trainer(hparams) + report_m = AccuracyReport() + report_m.train_adv_train_clean_eval = report_dict["train"] + report_m.adv_train_clean_eval = report_dict["test"] + report_m.adv_train_adv_eval = report_dict["MadryEtAl_y"] + + flags.update({"ngpu": 1, "attack_type_train": "MadryEtAl_y"}) + hparams = HParams(**flags) + np.random.seed(42) + tf.set_random_seed(42) + with tf.variable_scope(None, "runner"): + report_dict = run_trainer(hparams) + report_s = AccuracyReport() + report_s.train_adv_train_clean_eval = report_dict["train"] + report_s.adv_train_clean_eval = report_dict["test"] + report_s.adv_train_adv_eval = report_dict["MadryEtAl_y"] + + self.assertClose( + report_s.train_adv_train_clean_eval, + report_m.train_adv_train_clean_eval, + atol=5e-2, + ) + self.assertClose( + report_s.adv_train_clean_eval, report_m.adv_train_clean_eval, atol=2e-2 + ) + self.assertClose( + report_s.adv_train_adv_eval, report_m.adv_train_adv_eval, atol=5e-2 + ) + + def test_run_single_gpu_fgsm(self): + """ + Test the basic single GPU performance by comparing to the FGSM + tutorial. + """ + from cleverhans_tutorials import mnist_tutorial_tf + + # Run the MNIST tutorial on a dataset of reduced size + flags = { + "train_start": 0, + "train_end": 5000, + "test_start": 0, + "test_end": 333, + "nb_epochs": 5, + "testing": True, + } + report = mnist_tutorial_tf.mnist_tutorial(**flags) + + # Run the multi-gpu trainer for clean training + flags.update( + { + "batch_size": 128, + "adam_lrn": 0.001, + "dataset": "mnist", + "only_adv_train": False, + "eval_iters": 1, + "ngpu": 1, + "fast_tests": False, + "attack_type_train": "", + "save_dir": None, + "save_steps": 10000, + "attack_nb_iter_train": None, + "save": False, + "model_type": "basic", + "attack_type_test": "FGSM", + } + ) + + flags.update({"adv_train": False}) + HParams = namedtuple("HParams", flags.keys()) + + hparams = HParams(**flags) + np.random.seed(42) + tf.set_random_seed(42) + with tf.variable_scope(None, "runner"): + report_dict = run_trainer(hparams) + report_2 = AccuracyReport() + report_2.train_clean_train_clean_eval = report_dict["train"] + report_2.clean_train_clean_eval = report_dict["test"] + report_2.clean_train_adv_eval = report_dict["FGSM"] + + # Run the multi-gpu trainer for adversarial training + flags.update({"adv_train": True, "attack_type_train": "FGSM"}) + HParams = namedtuple("HParams", flags.keys()) + + hparams = HParams(**flags) + np.random.seed(42) + tf.set_random_seed(42) + with tf.variable_scope(None, "runner"): + report_dict = run_trainer(hparams) + report_2.train_adv_train_clean_eval = report_dict["train"] + report_2.adv_train_clean_eval = report_dict["test"] + report_2.adv_train_adv_eval = report_dict["FGSM"] + + self.assertClose( + report.train_clean_train_clean_eval, + report_2.train_clean_train_clean_eval, + atol=5e-2, + ) + self.assertClose( + report.clean_train_clean_eval, report_2.clean_train_clean_eval, atol=2e-2 + ) + self.assertClose( + report.clean_train_adv_eval, report_2.clean_train_adv_eval, atol=5e-2 + ) + self.assertClose( + report.train_adv_train_clean_eval, + report_2.train_adv_train_clean_eval, + atol=1e-1, + ) + self.assertClose( + report.adv_train_clean_eval, report_2.adv_train_clean_eval, atol=2e-2 + ) + self.assertClose( + report.adv_train_adv_eval, report_2.adv_train_adv_eval, atol=1e-1 + ) + + def test_run_multi_gpu_madryetal(self): + self.helper_run_multi_gpu_madryetal() + + def test_run_multi_gpu_naive(self): + self.helper_run_multi_gpu_madryetal({"adv_train": False}) + + +if __name__ == "__main__": + unittest.main() diff --git a/cleverhans_v3.1.0/examples/multigpu_advtrain/test_runner.py b/cleverhans_v3.1.0/examples/multigpu_advtrain/test_runner.py new file mode 100644 index 000000000..83c6acdc8 --- /dev/null +++ b/cleverhans_v3.1.0/examples/multigpu_advtrain/test_runner.py @@ -0,0 +1,73 @@ +# pylint: disable=missing-docstring +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import unittest +import numpy as np +import tensorflow as tf + +from cleverhans.devtools.checks import CleverHansTest + +from runner import RunnerMultiGPU + + +class TestRunnerMultiGPU(CleverHansTest): + def setUp(self): + super(TestRunnerMultiGPU, self).setUp() + self.sess = tf.Session() + + inputs = [] + outputs = [] + self.niter = 10 + niter = self.niter + # A Simple graph with `niter` sub-graphs. + with tf.variable_scope(None, "runner"): + for i in range(niter): + v = tf.get_variable("v%d" % i, shape=(100, 10)) + w = tf.get_variable("w%d" % i, shape=(100, 1)) + + inputs += [{"v": v, "w": w}] + outputs += [{"v": v, "w": w}] + + self.runner = RunnerMultiGPU(inputs, outputs, sess=self.sess) + + def help_test_runner(self, ninputs, niter): + """ + Tests the MultiGPU runner by feeding in random Tensors for `ninputs` + steps. Then validating the output after `niter-1` steps. + """ + v_val = [] + w_val = [] + for i in range(ninputs): + v_val += [np.random.rand(100, 10)] + w_val += [np.random.rand(100, 1)] + fvals = self.runner.run({"v": v_val[i], "w": w_val[i]}) + self.assertTrue(len(fvals) == 0) + self.assertFalse(self.runner.is_finished()) + + for i in range(niter - ninputs - 1): + self.assertFalse(self.runner.is_finished()) + fvals = self.runner.run() + self.assertTrue(len(fvals) == 0) + self.assertFalse(self.runner.is_finished()) + + for i in range(ninputs): + self.assertFalse(self.runner.is_finished()) + fvals = self.runner.run() + self.assertTrue("v" in fvals and "w" in fvals) + self.assertTrue(np.allclose(fvals["v"], v_val[i])) + self.assertTrue(np.allclose(fvals["w"], w_val[i])) + + self.assertTrue(self.runner.is_finished()) + + def test_queue_full(self): + self.help_test_runner(self.niter - 1, self.niter) + + def test_queue_half(self): + self.help_test_runner(self.niter // 2, self.niter) + + +if __name__ == "__main__": + unittest.main() diff --git a/cleverhans_v3.1.0/examples/multigpu_advtrain/trainer.py b/cleverhans_v3.1.0/examples/multigpu_advtrain/trainer.py new file mode 100644 index 000000000..3d8bf8570 --- /dev/null +++ b/cleverhans_v3.1.0/examples/multigpu_advtrain/trainer.py @@ -0,0 +1,497 @@ +""" +This module provides Trainer classes that given a set of flags, create, +initialize and train a model. These classes use Runner objects to handle +multigpu/singlegpu training. +""" +# pylint: disable=missing-docstring +from collections import OrderedDict +import logging +import math +import time +import os + +import numpy as np +import six +import tensorflow as tf + +from cleverhans.utils_tf import batch_indices +from cleverhans.utils_mnist import data_mnist +import utils_cifar as cifar_input +import utils_svhn as svhn_input +from utils import preprocess_batch + +from make_model import make_model +from evaluator import Evaluator +from evaluator import create_adv_by_name +from model import clone_variable + + +class TrainManager(object): + """ + The base trainer class. Given an object of `hparams`, a trainer + creates and initializes a model. After initialization, the method + `model_train` can be used to train the model. + """ + + def __init__(self, hparams): + """ + :param hparams: An instance of collections.namedtuple specifying the + model type and training configs. The parameters are + documented in `run_multigpu.py`. + """ + self.hparams = hparams + self.batch_size = hparams.batch_size + self.evaluate = None + self.step_num = 0 + self.report = None + self._init_session() + self._init_data() + self._init_inputs() + self._init_model() + self._create_train_graph() + self._init_eval() + self.runner = None + + def _init_session(self): + # Set TF random seed to improve reproducibility + self.rng = np.random.RandomState([2017, 8, 30]) + tf.set_random_seed(1234) + + # Create TF session + self.sess = tf.Session(config=tf.ConfigProto(allow_soft_placement=True)) + + # Object used to keep track of (and return) key accuracies + if self.hparams.save: + self.writer = tf.summary.FileWriter(self.hparams.save_dir, flush_secs=10) + else: + self.writer = None + + def _init_data(self): + hparams = self.hparams + batch_size = hparams.batch_size + if hparams.dataset == "mnist": + # Get MNIST test data + X_train, Y_train, X_test, Y_test = data_mnist( + train_start=hparams.train_start, + train_end=hparams.train_end, + test_start=hparams.test_start, + test_end=hparams.test_end, + ) + input_shape = (batch_size, 28, 28, 1) + preproc_func = None + elif hparams.dataset == "cifar10": + X_train, Y_train, X_test, Y_test = cifar_input.read_CIFAR10( + os.path.join(hparams.data_path, hparams.dataset) + ) + input_shape = (batch_size, 32, 32, 3) + preproc_func = cifar_input.cifar_tf_preprocess + elif hparams.dataset == "svhn": + X_train, Y_train, X_test, Y_test = svhn_input.read_SVHN( + os.path.join(hparams.data_path, hparams.dataset) + ) + input_shape = (batch_size, 32, 32, 3) + preproc_func = svhn_input.svhn_tf_preprocess + + # Use label smoothing + assert Y_train.shape[1] == 10.0 + label_smooth = 0.1 + Y_train = Y_train.clip(label_smooth / 9.0, 1.0 - label_smooth) + + self.X_train = X_train + self.Y_train = Y_train + self.X_test = X_test + self.Y_test = Y_test + self.data = (X_train, Y_train, X_test, Y_test) + self.input_shape = input_shape + self.preproc_func = preproc_func + + def _init_inputs(self): + preproc_func = self.preproc_func + input_shape = self.input_shape + # Define input TF placeholder + with tf.device("/gpu:0"): + x_pre = tf.placeholder(tf.float32, shape=input_shape, name="x") + x = preprocess_batch(x_pre, preproc_func) + y = tf.placeholder(tf.float32, shape=(self.batch_size, 10), name="y") + + self.g0_inputs = {"x_pre": x_pre, "x": x, "y": y} + + def _init_model(self): + flags = self.hparams.__dict__ + # Define TF model graph + model = make_model(input_shape=self.input_shape, **flags) + model.set_device(None) + self.model = model + + def _init_eval(self): + logging.info("Init eval") + x_pre, x, y = [self.g0_inputs[k] for k in ["x_pre", "x", "y"]] + self.model.set_device("/gpu:0") + self.evaluate = Evaluator( + self.sess, + self.model, + self.batch_size, + x_pre, + x, + y, + self.data, + self.writer, + self.hparams, + ) + + def eval(self, **kwargs): + if self.evaluate is not None: + self.report = self.evaluate.eval_multi() + + def finish(self): + if self.writer: + self.writer.close() + return self.report + + def _update_learning_params(self): + model = self.model + hparams = self.hparams + fd = self.runner.feed_dict + step_num = self.step_num + + if hparams.model_type == "resnet_tf": + if step_num < hparams.lrn_step: + lrn_rate = hparams.mom_lrn + elif step_num < 30000: + lrn_rate = hparams.mom_lrn / 10 + elif step_num < 35000: + lrn_rate = hparams.mom_lrn / 100 + else: + lrn_rate = hparams.mom_lrn / 1000 + + fd[model.lrn_rate] = lrn_rate + + def _build_train_op(self, predictions, y, predictions_adv): + model = self.model + hparams = self.hparams + if hparams.model_type == "resnet_tf": + build_train_op = model.build_cost + else: + raise NotImplementedError( + "this configuration of this example is no longer maintained" + ) + + # Define loss + with tf.variable_scope("train_loss"): + if predictions_adv is not None: + if hparams.only_adv_train: + loss = build_train_op(y, predictions_adv) + else: + loss = build_train_op(y, predictions) + adv_loss = build_train_op(y, predictions_adv) + loss = (loss + adv_loss) / 2 + else: + loss = build_train_op(y, predictions) + + if hparams.model_type == "resnet_tf": + train_step = model.build_train_op_from_cost(loss) + else: + optim = tf.train.AdamOptimizer(learning_rate=hparams.adam_lrn) + train_step = optim.minimize(loss) + + return train_step + + def model_train(self): + """ + Train a TF graph + :param sess: TF session to use when training the graph + :param x: input placeholder + :param y: output placeholder (for labels) + :param predictions: model output predictions + :param X_train: numpy array with training inputs + :param Y_train: numpy array with training outputs + :param hparams.save: boolean controlling the save operation + :param predictions_adv: if set with the adversarial example tensor, + will run adversarial training + :param evaluate: function that is run after each training iteration + (typically to display the test/validation accuracy). + """ + + assert ( + self.runner is not None + ), """Runner is not initialized. TrainerSingleGPU or TrainerMultiGPU + instantiate a Runner object at initialization time.""" + hparams = self.hparams + batch_size = hparams.batch_size + nb_epochs = hparams.nb_epochs + train_dir = hparams.save_dir + filename = "model.ckpt" + X_train = self.X_train + Y_train = self.Y_train + + sess = self.sess + + with sess.as_default(): + X_batch = X_train[:batch_size] + Y_batch = Y_train[:batch_size] + self._init_tf(X_batch, Y_batch) + + for epoch in six.moves.xrange(nb_epochs): + logging.info("Epoch " + str(epoch)) + + # Compute number of batches + nb_batches = int(math.ceil(float(len(X_train)) / batch_size)) + assert nb_batches * batch_size >= len(X_train) + + # Indices to shuffle training set + index_shuf = list(range(len(X_train))) + self.rng.shuffle(index_shuf) + + prev = time.time() + for batch in range(nb_batches): + # Compute batch start and end indices + start, end = batch_indices(batch, len(X_train), batch_size) + + # Perform one training step + self._update_learning_params() + + # Train step + X_batch = X_train[index_shuf[start:end]] + Y_batch = Y_train[index_shuf[start:end]] + + self._run({"x_pre": X_batch, "y": Y_batch}) + self._sync_params() + + # Clean up the queue + while not self.runner.is_finished(): + self._run() + + self._sync_params(forced=True) + + assert end >= len(X_train), "Not all training examples are used." + cur = time.time() + logging.info("\tEpoch took " + str(cur - prev) + " seconds") + prev = cur + + self.eval() + + # Save model + cond = (epoch + 1) % hparams.save_steps == 0 or epoch == nb_epochs + if hparams.save and cond: + save_path = os.path.join(train_dir, filename) + saver = tf.train.Saver() + saver.save(sess, save_path) + logging.info("Model saved at: " + str(save_path)) + logging.info("Completed model training.") + + def _init_tf(self, X_batch, Y_batch): + x_pre = self.g0_inputs["x_pre"] + y = self.g0_inputs["y"] + fd = {x_pre: X_batch, y: Y_batch} + init_op = tf.global_variables_initializer() + self.sess.run(init_op, feed_dict=fd) + + def _run(self, X_batch=None): + last_fvals = self.runner.run(X_batch) + self.step_num += 1 + return last_fvals + + def _sync_params(self, forced=False): + raise NotImplementedError("sync_params should be implemented.") + + def _create_train_graph(self): + """ + The evaluation graph must be initialized after the train graph is + fully initialized, otherwise, some of the variables will be created + untrainable. + """ + assert ( + self.evaluate is None + ), """Evaluation graph should be initialzed + after the train graph""" + + +class TrainerMultiGPU(TrainManager): + """ + This class uses a `RunnerMultiGPU` object to train a model on multiple + GPUs. It mainly overrides the `_create_train_graph` to create a graph + for adversarial training on multiple GPUs. + """ + + def __init__(self, *args, **kwargs): + super(TrainerMultiGPU, self).__init__(*args, **kwargs) + from runner import RunnerMultiGPU + + self.runner = RunnerMultiGPU(self.inputs, self.outputs, sess=self.sess) + + def clone_g0_inputs_on_ngpus(self, inputs, outputs, g0_inputs): + """ + Clone variables unused by the attack on all GPUs. Specifically, the + ground-truth label, y, has to be preserved until the training step. + + :param inputs: A list of dictionaries as the inputs to each step. + :param outputs: A list of dictionaries as the outputs of each step. + :param g0_inputs: Initial variables to be cloned. + :return: Updated inputs and outputs. + """ + assert len(inputs) == len( + outputs + ), "Inputs and outputs should have the same number of elements." + + inputs[0].update(g0_inputs) + outputs[0].update(g0_inputs) + + # Copy g0_inputs forward + for i in range(1, len(inputs)): + # Create the graph for i'th step of attack + device_name = inputs[i]["x"].device + with tf.device(device_name): + with tf.variable_scope("step%d" % i): + for k, v in g0_inputs.iteritems(): + if k not in inputs[i]: + v_copy = clone_variable(k, v) + inputs[i][k] = v_copy + outputs[i][k] = v_copy + + return inputs, outputs + + def _create_train_graph(self): + super(TrainerMultiGPU, self)._create_train_graph() + assert "_multigpu" in self.hparams.attack_type_train + + hparams = self.hparams + model = self.model + sess = self.sess + + # Create trainable variables on last gpu. + # Variables are set to trainable or non-trainable first time they are + # created. This caused a bug when the last gpu is used both for attack + # generation and training. With this bug the result of naive training + # was affected by the length of the unused adversarial generation + # graph. + device_name = "/gpu:%d" % (hparams.ngpu - 1) + model.set_device(device_name) + with tf.device(device_name): + x = clone_variable("x", self.g0_inputs["x"]) + model.set_training(training=True) + preds = model.get_probs(x) + + # Generates steps on gpus + model.set_training(training=False) + logging.info("Initializing train attack %s" % hparams.attack_type_train) + inputs, outputs = create_adv_by_name( + model, + self.g0_inputs["x"], + hparams.attack_type_train, + sess, + y=self.g0_inputs["y"], + nb_iter=hparams.attack_nb_iter_train, + dataset=hparams.dataset, + ngpu=hparams.ngpu, + ) + + inputs, outputs = self.clone_g0_inputs_on_ngpus(inputs, outputs, self.g0_inputs) + + # Train step on last gpu + device_name = "/gpu:%d" % (hparams.ngpu - 1) + model.set_device(device_name) + with tf.device(device_name): + with tf.variable_scope("last"): + inputs += [OrderedDict()] + for k, v in outputs[-1].iteritems(): + v_copy = clone_variable(k, v) + inputs[-1][k] = v_copy + x = inputs[-1]["x"] + adv_x = inputs[-1]["adv_x"] + y = inputs[-1]["y"] + if not hparams.adv_train: + model.set_training(training=True) + preds = model.get_probs(x) + preds_adv = None + elif not hparams.only_adv_train: + model.set_training(training=True) + preds = model.get_probs(x) + model.set_training(training=True) + preds_adv = model.get_probs(adv_x) + else: + preds = None + model.set_training(training=True) + preds_adv = model.get_probs(adv_x) + train_fetches = self._build_train_op(preds, y, preds_adv) + + outputs += [{"fetches": train_fetches}] + + # Create the sync operation + device_name = "/gpu:%d" % (hparams.ngpu - 1) + model.set_device(device_name) + with tf.device(device_name): + sync_ops = model.create_sync_ops(host_device=device_name) + + self.inputs = inputs + self.outputs = outputs + self.sync_ops = sync_ops + + def _sync_params(self, forced=False): + if forced or (self.step_num % self.hparams.sync_step == 0): + self.sess.run(self.sync_ops) + + +class TrainerSingleGPU(TrainManager): + """ + This class uses a `RunnerSingleGPU` object to train a model on a single + GPU. + """ + + def __init__(self, *args, **kwargs): + super(TrainerSingleGPU, self).__init__(*args, **kwargs) + from runner import RunnerSingleGPU + + self.runner = RunnerSingleGPU(self.inputs, self.outputs, sess=self.sess) + + def _create_train_graph(self): + super(TrainerSingleGPU, self)._create_train_graph() + self.model.set_device("/gpu:0") + hparams = self.hparams + model = self.model + x = self.g0_inputs["x"] + y = self.g0_inputs["y"] + sess = self.sess + + # Create trainable variables. + model.set_training(training=True) + preds = model.get_probs(x) + + if not hparams.adv_train: + logging.info("Naive training") + + model.set_training(training=True) + preds = model.get_probs(x) + preds_adv = None + else: + logging.info("Adversarial training") + logging.info("Initializing train attack %s" % hparams.attack_type_train) + + model.set_training(training=False) + adv_x = create_adv_by_name( + model, + x, + hparams.attack_type_train, + sess, + y=y, + nb_iter=hparams.attack_nb_iter_train, + dataset=hparams.dataset, + ) + if hparams.only_adv_train: + preds = None + model.set_training(training=True) + preds_adv = model.get_probs(adv_x) + else: + model.set_training(training=True) + preds = model.get_probs(x) + model.set_training(training=True) + preds_adv = model.get_probs(adv_x) + train_fetches = self._build_train_op(preds, y, preds_adv) + + self.inputs = [self.g0_inputs] + self.outputs = [train_fetches] + + def _sync_params(self, forced=False): + """ + Nothing to sync on single GPU. + """ + return True diff --git a/cleverhans_v3.1.0/examples/multigpu_advtrain/utils.py b/cleverhans_v3.1.0/examples/multigpu_advtrain/utils.py new file mode 100644 index 000000000..058597bb6 --- /dev/null +++ b/cleverhans_v3.1.0/examples/multigpu_advtrain/utils.py @@ -0,0 +1,25 @@ +# pylint: disable=missing-docstring +import tensorflow as tf + + +def preprocess_batch(images_batch, preproc_func=None): + """ + Creates a preprocessing graph for a batch given a function that processes + a single image. + + :param images_batch: A tensor for an image batch. + :param preproc_func: (optional function) A function that takes in a + tensor and returns a preprocessed input. + """ + if preproc_func is None: + return images_batch + + with tf.variable_scope("preprocess"): + images_list = tf.split(images_batch, int(images_batch.shape[0])) + result_list = [] + for img in images_list: + reshaped_img = tf.reshape(img, img.shape[1:]) + processed_img = preproc_func(reshaped_img) + result_list.append(tf.expand_dims(processed_img, axis=0)) + result_images = tf.concat(result_list, axis=0) + return result_images diff --git a/cleverhans_v3.1.0/examples/multigpu_advtrain/utils_cifar.py b/cleverhans_v3.1.0/examples/multigpu_advtrain/utils_cifar.py new file mode 100644 index 000000000..5e8e19ae4 --- /dev/null +++ b/cleverhans_v3.1.0/examples/multigpu_advtrain/utils_cifar.py @@ -0,0 +1,183 @@ +""" +https://github.com/renmengye/revnet-public/blob/master/resnet/data/cifar_input.py + +MIT License + +Copyright (c) 2017 Mengye Ren + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" +# pylint: disable=missing-docstring +from __future__ import absolute_import, division, print_function, unicode_literals + +import os +import cPickle as pkl + +import numpy as np +from six.moves import range +import tensorflow as tf + +# Global constants describing the CIFAR-10 data set. +IMAGE_HEIGHT = 32 +IMAGE_WIDTH = 32 +NUM_CLASSES = 10 +NUM_CHANNEL = 3 +NUM_TRAIN_IMG = 50000 +NUM_TEST_IMG = 10000 + + +def unpickle(file): + fo = open(file, "rb") + dict = pkl.load(fo) + fo.close() + return dict + + +def read_CIFAR10(data_folder): + """ Reads and parses examples from CIFAR10 data files """ + + train_img = [] + train_label = [] + test_img = [] + test_label = [] + + train_file_list = [ + "data_batch_1", + "data_batch_2", + "data_batch_3", + "data_batch_4", + "data_batch_5", + ] + test_file_list = ["test_batch"] + + if "CIFAR10_PATH" in os.environ: + cifar10_path = os.environ["CIFAR10_PATH"] + else: + cifar10_path = "cifar-10-batches-py" + + for i in range(len(train_file_list)): + tmp_dict = unpickle(os.path.join(data_folder, cifar10_path, train_file_list[i])) + + train_img.append(tmp_dict["data"]) + train_label.append(tmp_dict["labels"]) + + tmp_dict = unpickle(os.path.join(data_folder, cifar10_path, test_file_list[0])) + test_img.append(tmp_dict["data"]) + test_label.append(tmp_dict["labels"]) + + train_img = np.concatenate(train_img) + train_label = np.concatenate(train_label) + test_img = np.concatenate(test_img) + test_label = np.concatenate(test_label) + + train_img = np.reshape( + train_img, [NUM_TRAIN_IMG, NUM_CHANNEL, IMAGE_HEIGHT, IMAGE_WIDTH] + ) + test_img = np.reshape( + test_img, [NUM_TEST_IMG, NUM_CHANNEL, IMAGE_HEIGHT, IMAGE_WIDTH] + ) + + # change format from [B, C, H, W] to [B, H, W, C] for feeding to Tensorflow + train_img = np.transpose(train_img, [0, 2, 3, 1]) + test_img = np.transpose(test_img, [0, 2, 3, 1]) + + mean_img = np.mean(np.concatenate([train_img, test_img]), axis=0) + + CIFAR10_data = {} + CIFAR10_data["train_img"] = train_img - mean_img + CIFAR10_data["test_img"] = test_img - mean_img + CIFAR10_data["train_label"] = train_label + CIFAR10_data["test_label"] = test_label + + train_img = train_img - mean_img + test_img = test_img - mean_img + train_label = train_label + test_label = test_label + + train_label = np.eye(10)[train_label] + test_label = np.eye(10)[test_label] + + return train_img, train_label, test_img, test_label + + +def read_CIFAR100(data_folder): + """ Reads and parses examples from CIFAR100 python data files """ + + train_img = [] + train_label = [] + test_img = [] + test_label = [] + + train_file_list = ["cifar-100-python/train"] + test_file_list = ["cifar-100-python/test"] + + tmp_dict = unpickle(os.path.join(data_folder, train_file_list[0])) + train_img.append(tmp_dict["data"]) + train_label.append(tmp_dict["fine_labels"]) + + tmp_dict = unpickle(os.path.join(data_folder, test_file_list[0])) + test_img.append(tmp_dict["data"]) + test_label.append(tmp_dict["fine_labels"]) + + train_img = np.concatenate(train_img) + train_label = np.concatenate(train_label) + test_img = np.concatenate(test_img) + test_label = np.concatenate(test_label) + + train_img = np.reshape( + train_img, [NUM_TRAIN_IMG, NUM_CHANNEL, IMAGE_HEIGHT, IMAGE_WIDTH] + ) + test_img = np.reshape( + test_img, [NUM_TEST_IMG, NUM_CHANNEL, IMAGE_HEIGHT, IMAGE_WIDTH] + ) + + # change format from [B, C, H, W] to [B, H, W, C] for feeding to Tensorflow + train_img = np.transpose(train_img, [0, 2, 3, 1]) + test_img = np.transpose(test_img, [0, 2, 3, 1]) + mean_img = np.mean(np.concatenate([train_img, test_img]), axis=0) + + CIFAR100_data = {} + CIFAR100_data["train_img"] = train_img - mean_img + CIFAR100_data["test_img"] = test_img - mean_img + CIFAR100_data["train_label"] = train_label + CIFAR100_data["test_label"] = test_label + + return CIFAR100_data + + +def cifar_tf_preprocess( + inp, random_crop=True, random_flip=True, whiten=True, br_sat_con=False +): + image_size = 32 + image = inp + if random_crop: + image = tf.image.resize_image_with_crop_or_pad( + inp, image_size + 4, image_size + 4 + ) + image = tf.random_crop(image, [image_size, image_size, 3]) + if random_flip: + image = tf.image.random_flip_left_right(image) + # Brightness/saturation/constrast provides small gains .2%~.5% on cifar. + if br_sat_con: + image = tf.image.random_brightness(image, max_delta=63.0 / 255.0) + image = tf.image.random_saturation(image, lower=0.5, upper=1.5) + image = tf.image.random_contrast(image, lower=0.2, upper=1.8) + if whiten: + image = tf.image.per_image_standardization(image) + return image diff --git a/cleverhans_v3.1.0/examples/multigpu_advtrain/utils_svhn.py b/cleverhans_v3.1.0/examples/multigpu_advtrain/utils_svhn.py new file mode 100644 index 000000000..9081a4fd9 --- /dev/null +++ b/cleverhans_v3.1.0/examples/multigpu_advtrain/utils_svhn.py @@ -0,0 +1,98 @@ +""" +Reading the SVHN dataset. It is derived from CIFAR10 scripts in RevNets code. + +https://github.com/renmengye/revnet-public/blob/master/resnet/data/cifar_input.py + +MIT License + +Copyright (c) 2017 Mengye Ren + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" +# pylint: disable=missing-docstring +from __future__ import absolute_import, division, print_function, unicode_literals + +import os + +import numpy as np +from six.moves import range +import tensorflow as tf +import scipy.io as sio + +# Global constants describing the SVHN data set. +IMAGE_HEIGHT = 32 +IMAGE_WIDTH = 32 +NUM_CLASSES = 10 +NUM_CHANNEL = 3 +NUM_TRAIN_IMG = 73257 + 531131 +NUM_TEST_IMG = 26032 + + +def read_SVHN(data_folder): + """ Reads and parses examples from SVHN data files """ + + train_img = [] + train_label = [] + test_img = [] + test_label = [] + + train_file_list = ["train_32x32.mat", "extra_32x32.mat"] + test_file_list = ["test_32x32.mat"] + + for i in range(len(train_file_list)): + tmp_dict = sio.loadmat(os.path.join(data_folder, train_file_list[i])) + train_img.append(tmp_dict["X"]) + train_label.append(tmp_dict["y"]) + + tmp_dict = sio.loadmat(os.path.join(data_folder, test_file_list[0])) + test_img.append(tmp_dict["X"]) + test_label.append(tmp_dict["y"]) + + train_img = np.concatenate(train_img, axis=-1) + train_label = np.concatenate(train_label).flatten() + test_img = np.concatenate(test_img, axis=-1) + test_label = np.concatenate(test_label).flatten() + + # change format from [H, W, C, B] to [B, H, W, C] for feeding to Tensorflow + train_img = np.transpose(train_img, [3, 0, 1, 2]) + test_img = np.transpose(test_img, [3, 0, 1, 2]) + + mean_img = np.mean(np.concatenate([train_img, test_img]), axis=0) + + train_img = train_img - mean_img + test_img = test_img - mean_img + train_y = train_label - 1 # 0-based label + test_y = test_label - 1 # 0-based label + + train_label = np.eye(10)[train_y] + test_label = np.eye(10)[test_y] + + return train_img, train_label, test_img, test_label + + +def svhn_tf_preprocess(inp, random_crop=True): + image_size = 32 + image = inp + if random_crop: + print("Apply random cropping") + image = tf.image.resize_image_with_crop_or_pad( + inp, image_size + 4, image_size + 4 + ) + image = tf.random_crop(image, [image_size, image_size, 3]) + return inp, image diff --git a/examples/nips17_adversarial_competition/.gitignore b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/.gitignore similarity index 100% rename from examples/nips17_adversarial_competition/.gitignore rename to cleverhans_v3.1.0/examples/nips17_adversarial_competition/.gitignore diff --git a/examples/nips17_adversarial_competition/README.md b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/README.md similarity index 100% rename from examples/nips17_adversarial_competition/README.md rename to cleverhans_v3.1.0/examples/nips17_adversarial_competition/README.md diff --git a/examples/nips17_adversarial_competition/dataset/README.md b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/dataset/README.md similarity index 100% rename from examples/nips17_adversarial_competition/dataset/README.md rename to cleverhans_v3.1.0/examples/nips17_adversarial_competition/dataset/README.md diff --git a/examples/nips17_adversarial_competition/dataset/dev_dataset.csv b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/dataset/dev_dataset.csv similarity index 100% rename from examples/nips17_adversarial_competition/dataset/dev_dataset.csv rename to cleverhans_v3.1.0/examples/nips17_adversarial_competition/dataset/dev_dataset.csv diff --git a/cleverhans_v3.1.0/examples/nips17_adversarial_competition/dataset/download_images.py b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/dataset/download_images.py new file mode 100644 index 000000000..c854c7636 --- /dev/null +++ b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/dataset/download_images.py @@ -0,0 +1,163 @@ +"""Script which downloads dataset images. + +Usage: + python download_images.py --input_file=INPUT_FILE --output_dir=IMAGES_DIR + +where: + INPUT_FILE is input csv file with dataset description, i.e. dev_dataset.csv + IMAGES_DIR is output directory where all images should be downloaded + +Example: + # create directory for images + mkdir images + # download images declared in dev_dataset.csv + python download_images.py --input_file=dev_dataset.csv --output_dir=images + + +Dependencies: + Python 2.7 or higher. + Pillow library: https://python-pillow.org/ +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import argparse +import csv +import os +import sys +from functools import partial +from io import BytesIO +import multiprocessing +from multiprocessing.dummy import Pool as ThreadPool + +from PIL import Image + +try: + from urllib.request import urlopen +except ImportError: + from urllib2 import urlopen + + +def parse_args(): + """Parses command line arguments.""" + parser = argparse.ArgumentParser(description="Tool to download dataset images.") + parser.add_argument("--input_file", required=True, help="Location of dataset.csv") + parser.add_argument( + "--output_dir", required=True, help="Output path to download images" + ) + parser.add_argument( + "--threads", + default=multiprocessing.cpu_count() + 1, + help="Number of threads to use", + ) + args = parser.parse_args() + return args.input_file, args.output_dir, int(args.threads) + + +def get_image(row, output_dir): + """Downloads the image that corresponds to the given row. + Prints a notification if the download fails.""" + if not download_image( + image_id=row[0], + url=row[1], + x1=float(row[2]), + y1=float(row[3]), + x2=float(row[4]), + y2=float(row[5]), + output_dir=output_dir, + ): + print("Download failed: " + str(row[0])) + + +def download_image(image_id, url, x1, y1, x2, y2, output_dir): + """Downloads one image, crops it, resizes it and saves it locally.""" + output_filename = os.path.join(output_dir, image_id + ".png") + if os.path.exists(output_filename): + # Don't download image if it's already there + return True + try: + # Download image + url_file = urlopen(url) + if url_file.getcode() != 200: + return False + image_buffer = url_file.read() + # Crop, resize and save image + image = Image.open(BytesIO(image_buffer)).convert("RGB") + w = image.size[0] + h = image.size[1] + image = image.crop((int(x1 * w), int(y1 * h), int(x2 * w), int(y2 * h))) + image = image.resize((299, 299), resample=Image.ANTIALIAS) + image.save(output_filename) + except IOError: + return False + return True + + +def main(): + input_filename, output_dir, n_threads = parse_args() + + if not os.path.isdir(output_dir): + print("Output directory {} does not exist".format(output_dir)) + sys.exit() + + with open(input_filename) as input_file: + reader = csv.reader(input_file) + header_row = next(reader) + rows = list(reader) + try: + row_idx_image_id = header_row.index("ImageId") + row_idx_url = header_row.index("URL") + row_idx_x1 = header_row.index("x1") + row_idx_y1 = header_row.index("y1") + row_idx_x2 = header_row.index("x2") + row_idx_y2 = header_row.index("y2") + except ValueError as e: + print("One of the columns was not found in the source file: ", e.message) + + rows = [ + ( + row[row_idx_image_id], + row[row_idx_url], + float(row[row_idx_x1]), + float(row[row_idx_y1]), + float(row[row_idx_x2]), + float(row[row_idx_y2]), + ) + for row in rows + ] + + if n_threads > 1: + pool = ThreadPool(n_threads) + partial_get_images = partial(get_image, output_dir=output_dir) + for i, _ in enumerate(pool.imap_unordered(partial_get_images, rows), 1): + sys.stderr.write("\rDownloaded {0} images".format(i + 1)) + pool.close() + pool.join() + else: + failed_to_download = set() + for idx in range(len(rows)): + row = rows[idx] + if not download_image( + image_id=row[0], + url=row[1], + x1=float(row[2]), + y1=float(row[3]), + x2=float(row[4]), + y2=float(row[5]), + output_dir=output_dir, + ): + failed_to_download.add(row[row_idx_image_id]) + sys.stdout.write("\rDownloaded {0} images".format(idx + 1)) + sys.stdout.flush() + + print() + if failed_to_download: + print("\nUnable to download images with the following IDs:") + for image_id in failed_to_download: + print(image_id) + + +if __name__ == "__main__": + main() diff --git a/examples/nips17_adversarial_competition/dataset/final_dataset.csv b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/dataset/final_dataset.csv similarity index 100% rename from examples/nips17_adversarial_competition/dataset/final_dataset.csv rename to cleverhans_v3.1.0/examples/nips17_adversarial_competition/dataset/final_dataset.csv diff --git a/examples/nips17_adversarial_competition/dev_toolkit/.gitignore b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/dev_toolkit/.gitignore similarity index 100% rename from examples/nips17_adversarial_competition/dev_toolkit/.gitignore rename to cleverhans_v3.1.0/examples/nips17_adversarial_competition/dev_toolkit/.gitignore diff --git a/examples/nips17_adversarial_competition/dev_toolkit/README.md b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/dev_toolkit/README.md similarity index 100% rename from examples/nips17_adversarial_competition/dev_toolkit/README.md rename to cleverhans_v3.1.0/examples/nips17_adversarial_competition/dev_toolkit/README.md diff --git a/examples/nips17_adversarial_competition/dev_toolkit/download_data.sh b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/dev_toolkit/download_data.sh similarity index 100% rename from examples/nips17_adversarial_competition/dev_toolkit/download_data.sh rename to cleverhans_v3.1.0/examples/nips17_adversarial_competition/dev_toolkit/download_data.sh diff --git a/cleverhans_v3.1.0/examples/nips17_adversarial_competition/dev_toolkit/run_attacks_and_defenses.py b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/dev_toolkit/run_attacks_and_defenses.py new file mode 100644 index 000000000..d1dda2e32 --- /dev/null +++ b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/dev_toolkit/run_attacks_and_defenses.py @@ -0,0 +1,646 @@ +"""Tool which runs all attacks against all defenses and computes results.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import argparse +import csv +import json +import os +import subprocess +import numpy as np +from PIL import Image + + +def parse_args(): + """Parses command line arguments.""" + parser = argparse.ArgumentParser(description="Tool to run attacks and defenses.") + parser.add_argument("--attacks_dir", required=True, help="Location of all attacks.") + parser.add_argument( + "--targeted_attacks_dir", + required=True, + help="Location of all targeted attacks.", + ) + parser.add_argument( + "--defenses_dir", required=True, help="Location of all defenses." + ) + parser.add_argument("--dataset_dir", required=True, help="Location of the dataset.") + parser.add_argument( + "--dataset_metadata", required=True, help="Location of the dataset metadata." + ) + parser.add_argument( + "--intermediate_results_dir", + required=True, + help="Directory to store intermediate results.", + ) + parser.add_argument("--output_dir", required=True, help=("Output directory.")) + parser.add_argument( + "--epsilon", + required=False, + type=int, + default=16, + help="Maximum allowed size of adversarial perturbation", + ) + parser.add_argument("--gpu", dest="use_gpu", action="store_true") + parser.add_argument("--nogpu", dest="use_gpu", action="store_false") + parser.set_defaults(use_gpu=False) + parser.add_argument( + "--save_all_classification", dest="save_all_classification", action="store_true" + ) + parser.add_argument( + "--nosave_all_classification", + dest="save_all_classification", + action="store_false", + ) + parser.set_defaults(save_all_classification=False) + return parser.parse_args() + + +class Submission(object): + """Base class for all submissions.""" + + def __init__(self, directory, container, entry_point, use_gpu): + """Initializes instance of Submission class. + + Args: + directory: location of the submission. + container: URL of Docker container which should be used to run submission. + entry_point: entry point script, which invokes submission. + use_gpu: whether to use Docker with GPU or not. + """ + self.name = os.path.basename(directory) + self.directory = directory + self.container = container + self.entry_point = entry_point + self.use_gpu = use_gpu + + def docker_binary(self): + """Returns appropriate Docker binary to use.""" + return "nvidia-docker" if self.use_gpu else "docker" + + +class Attack(Submission): + """Class which stores and runs attack.""" + + def run(self, input_dir, output_dir, epsilon): + """Runs attack inside Docker. + + Args: + input_dir: directory with input (dataset). + output_dir: directory where output (adversarial images) should be written. + epsilon: maximum allowed size of adversarial perturbation, + should be in range [0, 255]. + """ + print("Running attack ", self.name) + cmd = [ + self.docker_binary(), + "run", + "-v", + "{0}:/input_images".format(input_dir), + "-v", + "{0}:/output_images".format(output_dir), + "-v", + "{0}:/code".format(self.directory), + "-w", + "/code", + self.container, + "./" + self.entry_point, + "/input_images", + "/output_images", + str(epsilon), + ] + print(" ".join(cmd)) + subprocess.call(cmd) + + +class Defense(Submission): + """Class which stores and runs defense.""" + + def run(self, input_dir, output_dir): + """Runs defense inside Docker. + + Args: + input_dir: directory with input (adversarial images). + output_dir: directory to write output (classification result). + """ + print("Running defense ", self.name) + cmd = [ + self.docker_binary(), + "run", + "-v", + "{0}:/input_images".format(input_dir), + "-v", + "{0}:/output_data".format(output_dir), + "-v", + "{0}:/code".format(self.directory), + "-w", + "/code", + self.container, + "./" + self.entry_point, + "/input_images", + "/output_data/result.csv", + ] + print(" ".join(cmd)) + subprocess.call(cmd) + + +def read_submissions_from_directory(dirname, use_gpu): + """Scans directory and read all submissions. + + Args: + dirname: directory to scan. + use_gpu: whether submissions should use GPU. This argument is + used to pick proper Docker container for each submission and create + instance of Attack or Defense class. + + Returns: + List with submissions (subclasses of Submission class). + """ + result = [] + for sub_dir in os.listdir(dirname): + submission_path = os.path.join(dirname, sub_dir) + try: + if not os.path.isdir(submission_path): + continue + if not os.path.exists(os.path.join(submission_path, "metadata.json")): + continue + with open(os.path.join(submission_path, "metadata.json")) as f: + metadata = json.load(f) + if use_gpu and ("container_gpu" in metadata): + container = metadata["container_gpu"] + else: + container = metadata["container"] + entry_point = metadata["entry_point"] + submission_type = metadata["type"] + if submission_type == "attack" or submission_type == "targeted_attack": + submission = Attack(submission_path, container, entry_point, use_gpu) + elif submission_type == "defense": + submission = Defense(submission_path, container, entry_point, use_gpu) + else: + raise ValueError("Invalid type of submission: %s" % submission_type) + result.append(submission) + except (IOError, KeyError, ValueError): + print("Failed to read submission from directory ", submission_path) + return result + + +class AttacksOutput(object): + """Helper class to store data about images generated by attacks.""" + + def __init__( + self, + dataset_dir, + attacks_output_dir, + targeted_attacks_output_dir, + all_adv_examples_dir, + epsilon, + ): + """Initializes instance of AttacksOutput class. + + Args: + dataset_dir: location of the dataset. + attacks_output_dir: where to write results of attacks. + targeted_attacks_output_dir: where to write results of targeted attacks. + all_adv_examples_dir: directory to copy all adversarial examples from + all attacks. + epsilon: maximum allowed size of adversarial perturbation. + """ + self.attacks_output_dir = attacks_output_dir + self.targeted_attacks_output_dir = targeted_attacks_output_dir + self.all_adv_examples_dir = all_adv_examples_dir + self._load_dataset_clipping(dataset_dir, epsilon) + self._output_image_idx = 0 + self._output_to_attack_mapping = {} + self._attack_image_count = 0 + self._targeted_attack_image_count = 0 + self._attack_names = set() + self._targeted_attack_names = set() + + def _load_dataset_clipping(self, dataset_dir, epsilon): + """Helper method which loads dataset and determines clipping range. + + Args: + dataset_dir: location of the dataset. + epsilon: maximum allowed size of adversarial perturbation. + """ + self.dataset_max_clip = {} + self.dataset_min_clip = {} + self._dataset_image_count = 0 + for fname in os.listdir(dataset_dir): + if not fname.endswith(".png"): + continue + image_id = fname[:-4] + image = np.array( + Image.open(os.path.join(dataset_dir, fname)).convert("RGB") + ) + image = image.astype("int32") + self._dataset_image_count += 1 + self.dataset_max_clip[image_id] = np.clip(image + epsilon, 0, 255).astype( + "uint8" + ) + self.dataset_min_clip[image_id] = np.clip(image - epsilon, 0, 255).astype( + "uint8" + ) + + def clip_and_copy_attack_outputs(self, attack_name, is_targeted): + """Clips results of attack and copy it to directory with all images. + + Args: + attack_name: name of the attack. + is_targeted: if True then attack is targeted, otherwise non-targeted. + """ + if is_targeted: + self._targeted_attack_names.add(attack_name) + else: + self._attack_names.add(attack_name) + attack_dir = os.path.join( + self.targeted_attacks_output_dir + if is_targeted + else self.attacks_output_dir, + attack_name, + ) + for fname in os.listdir(attack_dir): + if not (fname.endswith(".png") or fname.endswith(".jpg")): + continue + image_id = fname[:-4] + if image_id not in self.dataset_max_clip: + continue + image_max_clip = self.dataset_max_clip[image_id] + image_min_clip = self.dataset_min_clip[image_id] + adversarial_image = np.array( + Image.open(os.path.join(attack_dir, fname)).convert("RGB") + ) + clipped_adv_image = np.clip( + adversarial_image, image_min_clip, image_max_clip + ) + output_basename = "{0:08d}".format(self._output_image_idx) + self._output_image_idx += 1 + self._output_to_attack_mapping[output_basename] = ( + attack_name, + is_targeted, + image_id, + ) + if is_targeted: + self._targeted_attack_image_count += 1 + else: + self._attack_image_count += 1 + Image.fromarray(clipped_adv_image).save( + os.path.join(self.all_adv_examples_dir, output_basename + ".png") + ) + + @property + def attack_names(self): + """Returns list of all non-targeted attacks.""" + return self._attack_names + + @property + def targeted_attack_names(self): + """Returns list of all targeted attacks.""" + return self._targeted_attack_names + + @property + def attack_image_count(self): + """Returns number of all images generated by non-targeted attacks.""" + return self._attack_image_count + + @property + def dataset_image_count(self): + """Returns number of all images in the dataset.""" + return self._dataset_image_count + + @property + def targeted_attack_image_count(self): + """Returns number of all images generated by targeted attacks.""" + return self._targeted_attack_image_count + + def image_by_base_filename(self, filename): + """Returns information about image based on it's filename.""" + return self._output_to_attack_mapping[filename] + + +class DatasetMetadata(object): + """Helper class which loads and stores dataset metadata.""" + + def __init__(self, filename): + """Initializes instance of DatasetMetadata.""" + self._true_labels = {} + self._target_classes = {} + with open(filename) as f: + reader = csv.reader(f) + header_row = next(reader) + try: + row_idx_image_id = header_row.index("ImageId") + row_idx_true_label = header_row.index("TrueLabel") + row_idx_target_class = header_row.index("TargetClass") + except ValueError: + raise IOError("Invalid format of dataset metadata.") + for row in reader: + if len(row) < len(header_row): + # skip partial or empty lines + continue + try: + image_id = row[row_idx_image_id] + self._true_labels[image_id] = int(row[row_idx_true_label]) + self._target_classes[image_id] = int(row[row_idx_target_class]) + except (IndexError, ValueError): + raise IOError("Invalid format of dataset metadata") + + def get_true_label(self, image_id): + """Returns true label for image with given ID.""" + return self._true_labels[image_id] + + def get_target_class(self, image_id): + """Returns target class for image with given ID.""" + return self._target_classes[image_id] + + def save_target_classes(self, filename): + """Saves target classed for all dataset images into given file.""" + with open(filename, "w") as f: + for k, v in self._target_classes.items(): + f.write("{0}.png,{1}\n".format(k, v)) + + +def load_defense_output(filename): + """Loads output of defense from given file.""" + result = {} + with open(filename) as f: + for row in csv.reader(f): + try: + image_filename = row[0] + if image_filename.endswith(".png") or image_filename.endswith(".jpg"): + image_filename = image_filename[: image_filename.rfind(".")] + label = int(row[1]) + except (IndexError, ValueError): + continue + result[image_filename] = label + return result + + +def compute_and_save_scores_and_ranking( + attacks_output, + defenses_output, + dataset_meta, + output_dir, + save_all_classification=False, +): + """Computes scores and ranking and saves it. + + Args: + attacks_output: output of attacks, instance of AttacksOutput class. + defenses_output: outputs of defenses. Dictionary of dictionaries, key in + outer dictionary is name of the defense, key of inner dictionary is + name of the image, value of inner dictionary is classification label. + dataset_meta: dataset metadata, instance of DatasetMetadata class. + output_dir: output directory where results will be saved. + save_all_classification: If True then classification results of all + defenses on all images produces by all attacks will be saved into + all_classification.csv file. Useful for debugging. + + This function saves following files into output directory: + accuracy_on_attacks.csv: matrix with number of correctly classified images + for each pair of defense and attack. + accuracy_on_targeted_attacks.csv: matrix with number of correctly classified + images for each pair of defense and targeted attack. + hit_target_class.csv: matrix with number of times defense classified image + as specified target class for each pair of defense and targeted attack. + defense_ranking.csv: ranking and scores of all defenses. + attack_ranking.csv: ranking and scores of all attacks. + targeted_attack_ranking.csv: ranking and scores of all targeted attacks. + all_classification.csv: results of classification of all defenses on + all images produced by all attacks. Only saved if save_all_classification + argument is True. + """ + + def write_ranking(filename, header, names, scores): + """Helper method which saves submissions' scores and names.""" + order = np.argsort(scores)[::-1] + with open(filename, "w") as f: + writer = csv.writer(f) + writer.writerow(header) + for idx in order: + writer.writerow([names[idx], scores[idx]]) + + def write_score_matrix(filename, scores, row_names, column_names): + """Helper method which saves score matrix.""" + result = np.pad(scores, ((1, 0), (1, 0)), "constant").astype(np.object) + result[0, 0] = "" + result[1:, 0] = row_names + result[0, 1:] = column_names + np.savetxt(filename, result, fmt="%s", delimiter=",") + + attack_names = list(attacks_output.attack_names) + attack_names_idx = {name: index for index, name in enumerate(attack_names)} + targeted_attack_names = list(attacks_output.targeted_attack_names) + targeted_attack_names_idx = { + name: index for index, name in enumerate(targeted_attack_names) + } + defense_names = list(defenses_output.keys()) + defense_names_idx = {name: index for index, name in enumerate(defense_names)} + + # In the matrices below: rows - attacks, columns - defenses. + accuracy_on_attacks = np.zeros( + (len(attack_names), len(defense_names)), dtype=np.int32 + ) + accuracy_on_targeted_attacks = np.zeros( + (len(targeted_attack_names), len(defense_names)), dtype=np.int32 + ) + hit_target_class = np.zeros( + (len(targeted_attack_names), len(defense_names)), dtype=np.int32 + ) + + for defense_name, defense_result in defenses_output.items(): + for image_filename, predicted_label in defense_result.items(): + attack_name, is_targeted, image_id = attacks_output.image_by_base_filename( + image_filename + ) + true_label = dataset_meta.get_true_label(image_id) + defense_idx = defense_names_idx[defense_name] + if is_targeted: + target_class = dataset_meta.get_target_class(image_id) + if true_label == predicted_label: + attack_idx = targeted_attack_names_idx[attack_name] + accuracy_on_targeted_attacks[attack_idx, defense_idx] += 1 + if target_class == predicted_label: + attack_idx = targeted_attack_names_idx[attack_name] + hit_target_class[attack_idx, defense_idx] += 1 + else: + if true_label == predicted_label: + attack_idx = attack_names_idx[attack_name] + accuracy_on_attacks[attack_idx, defense_idx] += 1 + + # Save matrices. + write_score_matrix( + os.path.join(output_dir, "accuracy_on_attacks.csv"), + accuracy_on_attacks, + attack_names, + defense_names, + ) + write_score_matrix( + os.path.join(output_dir, "accuracy_on_targeted_attacks.csv"), + accuracy_on_targeted_attacks, + targeted_attack_names, + defense_names, + ) + write_score_matrix( + os.path.join(output_dir, "hit_target_class.csv"), + hit_target_class, + targeted_attack_names, + defense_names, + ) + + # Compute and save scores and ranking of attacks and defenses, + # higher scores are better. + defense_scores = np.sum(accuracy_on_attacks, axis=0) + np.sum( + accuracy_on_targeted_attacks, axis=0 + ) + attack_scores = attacks_output.dataset_image_count * len(defenses_output) - np.sum( + accuracy_on_attacks, axis=1 + ) + targeted_attack_scores = np.sum(hit_target_class, axis=1) + write_ranking( + os.path.join(output_dir, "defense_ranking.csv"), + ["DefenseName", "Score"], + defense_names, + defense_scores, + ) + write_ranking( + os.path.join(output_dir, "attack_ranking.csv"), + ["AttackName", "Score"], + attack_names, + attack_scores, + ) + write_ranking( + os.path.join(output_dir, "targeted_attack_ranking.csv"), + ["AttackName", "Score"], + targeted_attack_names, + targeted_attack_scores, + ) + + if save_all_classification: + with open(os.path.join(output_dir, "all_classification.csv"), "w") as f: + writer = csv.writer(f) + writer.writerow( + [ + "AttackName", + "IsTargeted", + "DefenseName", + "ImageId", + "PredictedLabel", + "TrueLabel", + "TargetClass", + ] + ) + for defense_name, defense_result in defenses_output.items(): + for image_filename, predicted_label in defense_result.items(): + ( + attack_name, + is_targeted, + image_id, + ) = attacks_output.image_by_base_filename(image_filename) + true_label = dataset_meta.get_true_label(image_id) + target_class = dataset_meta.get_target_class(image_id) + writer.writerow( + [ + attack_name, + is_targeted, + defense_name, + image_id, + predicted_label, + true_label, + target_class, + ] + ) + + +def main(): + """Run all attacks against all defenses and compute results.""" + args = parse_args() + attacks_output_dir = os.path.join(args.intermediate_results_dir, "attacks_output") + targeted_attacks_output_dir = os.path.join( + args.intermediate_results_dir, "targeted_attacks_output" + ) + defenses_output_dir = os.path.join(args.intermediate_results_dir, "defenses_output") + all_adv_examples_dir = os.path.join( + args.intermediate_results_dir, "all_adv_examples" + ) + + # Load dataset metadata. + dataset_meta = DatasetMetadata(args.dataset_metadata) + + # Load attacks and defenses. + attacks = [ + a + for a in read_submissions_from_directory(args.attacks_dir, args.use_gpu) + if isinstance(a, Attack) + ] + targeted_attacks = [ + a + for a in read_submissions_from_directory( + args.targeted_attacks_dir, args.use_gpu + ) + if isinstance(a, Attack) + ] + defenses = [ + d + for d in read_submissions_from_directory(args.defenses_dir, args.use_gpu) + if isinstance(d, Defense) + ] + print("Found attacks: ", [a.name for a in attacks]) + print("Found tageted attacks: ", [a.name for a in targeted_attacks]) + print("Found defenses: ", [d.name for d in defenses]) + + # Prepare subdirectories for intermediate results. + os.mkdir(attacks_output_dir) + os.mkdir(targeted_attacks_output_dir) + os.mkdir(defenses_output_dir) + os.mkdir(all_adv_examples_dir) + for a in attacks: + os.mkdir(os.path.join(attacks_output_dir, a.name)) + for a in targeted_attacks: + os.mkdir(os.path.join(targeted_attacks_output_dir, a.name)) + for d in defenses: + os.mkdir(os.path.join(defenses_output_dir, d.name)) + + # Run all non-targeted attacks. + attacks_output = AttacksOutput( + args.dataset_dir, + attacks_output_dir, + targeted_attacks_output_dir, + all_adv_examples_dir, + args.epsilon, + ) + for a in attacks: + a.run(args.dataset_dir, os.path.join(attacks_output_dir, a.name), args.epsilon) + attacks_output.clip_and_copy_attack_outputs(a.name, False) + + # Run all targeted attacks. + dataset_meta.save_target_classes(os.path.join(args.dataset_dir, "target_class.csv")) + for a in targeted_attacks: + a.run( + args.dataset_dir, + os.path.join(targeted_attacks_output_dir, a.name), + args.epsilon, + ) + attacks_output.clip_and_copy_attack_outputs(a.name, True) + + # Run all defenses. + defenses_output = {} + for d in defenses: + d.run(all_adv_examples_dir, os.path.join(defenses_output_dir, d.name)) + defenses_output[d.name] = load_defense_output( + os.path.join(defenses_output_dir, d.name, "result.csv") + ) + + # Compute and save scoring. + compute_and_save_scores_and_ranking( + attacks_output, + defenses_output, + dataset_meta, + args.output_dir, + args.save_all_classification, + ) + + +if __name__ == "__main__": + main() diff --git a/examples/nips17_adversarial_competition/dev_toolkit/run_attacks_and_defenses.sh b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/dev_toolkit/run_attacks_and_defenses.sh similarity index 100% rename from examples/nips17_adversarial_competition/dev_toolkit/run_attacks_and_defenses.sh rename to cleverhans_v3.1.0/examples/nips17_adversarial_competition/dev_toolkit/run_attacks_and_defenses.sh diff --git a/examples/nips17_adversarial_competition/dev_toolkit/sample_attacks/download_checkpoints.sh b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/dev_toolkit/sample_attacks/download_checkpoints.sh similarity index 100% rename from examples/nips17_adversarial_competition/dev_toolkit/sample_attacks/download_checkpoints.sh rename to cleverhans_v3.1.0/examples/nips17_adversarial_competition/dev_toolkit/sample_attacks/download_checkpoints.sh diff --git a/cleverhans_v3.1.0/examples/nips17_adversarial_competition/dev_toolkit/sample_attacks/fgsm/attack_fgsm.py b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/dev_toolkit/sample_attacks/fgsm/attack_fgsm.py new file mode 100644 index 000000000..f15009c4a --- /dev/null +++ b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/dev_toolkit/sample_attacks/fgsm/attack_fgsm.py @@ -0,0 +1,160 @@ +"""Implementation of sample attack.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import os +import warnings + +import numpy as np +import tensorflow as tf +from tensorflow.contrib.slim.nets import inception +from PIL import Image + +from cleverhans.attacks import FastGradientMethod + +slim = tf.contrib.slim + + +tf.flags.DEFINE_string("master", "", "The address of the TensorFlow master to use.") + +tf.flags.DEFINE_string( + "checkpoint_path", "", "Path to checkpoint for inception network." +) + +tf.flags.DEFINE_string("input_dir", "", "Input directory with images.") + +tf.flags.DEFINE_string("output_dir", "", "Output directory with images.") + +tf.flags.DEFINE_float("max_epsilon", 16.0, "Maximum size of adversarial perturbation.") + +tf.flags.DEFINE_integer("image_width", 299, "Width of each input images.") + +tf.flags.DEFINE_integer("image_height", 299, "Height of each input images.") + +tf.flags.DEFINE_integer("batch_size", 16, "How many images process at one time.") + +FLAGS = tf.flags.FLAGS + + +def load_images(input_dir, batch_shape): + """Read png images from input directory in batches. + + Args: + input_dir: input directory + batch_shape: shape of minibatch array, i.e. [batch_size, height, width, 3] + + Yields: + filenames: list file names without path of each image + Lenght of this list could be less than batch_size, in this case only + first few images of the result are elements of the minibatch. + images: array with all images from this batch + """ + images = np.zeros(batch_shape) + filenames = [] + idx = 0 + batch_size = batch_shape[0] + for filepath in tf.gfile.Glob(os.path.join(input_dir, "*.png")): + with tf.gfile.Open(filepath) as f: + image = np.array(Image.open(f).convert("RGB")).astype(np.float) / 255.0 + # Images for inception classifier are normalized to be in [-1, 1] interval. + images[idx, :, :, :] = image * 2.0 - 1.0 + filenames.append(os.path.basename(filepath)) + idx += 1 + if idx == batch_size: + yield filenames, images + filenames = [] + images = np.zeros(batch_shape) + idx = 0 + if idx > 0: + yield filenames, images + + +def save_images(images, filenames, output_dir): + """Saves images to the output directory. + + Args: + images: array with minibatch of images + filenames: list of filenames without path + If number of file names in this list less than number of images in + the minibatch then only first len(filenames) images will be saved. + output_dir: directory where to save images + """ + for i, filename in enumerate(filenames): + # Images for inception classifier are normalized to be in [-1, 1] interval, + # so rescale them back to [0, 1]. + with tf.gfile.Open(os.path.join(output_dir, filename), "w") as f: + img = (((images[i, :, :, :] + 1.0) * 0.5) * 255.0).astype(np.uint8) + Image.fromarray(img).save(f, format="PNG") + + +class InceptionModel(object): + """Model class for CleverHans library.""" + + def __init__(self, nb_classes=None, num_classes=None): + if num_classes is not None: + if nb_classes is not None: + raise ValueError( + "Should not specify both nb_classes and its deprecated" + " alias, num_classes" + ) + warnings.warn( + "`num_classes` is deprecated. Switch to `nb_classes`." + " `num_classes` may be removed on or after 2019-04-23." + ) + nb_classes = num_classes + del num_classes + self.nb_classes = nb_classes + self.built = False + + def __call__(self, x_input): + """Constructs model and return probabilities for given input.""" + reuse = True if self.built else None + with slim.arg_scope(inception.inception_v3_arg_scope()): + _, end_points = inception.inception_v3( + x_input, num_classes=self.nb_classes, is_training=False, reuse=reuse + ) + self.built = True + output = end_points["Predictions"] + # Strip off the extra reshape op at the output + probs = output.op.inputs[0] + return probs + + +def main(_): + """Run the sample attack""" + # Images for inception classifier are normalized to be in [-1, 1] interval, + # eps is a difference between pixels so it should be in [0, 2] interval. + # Renormalizing epsilon from [0, 255] to [0, 2]. + eps = 2.0 * FLAGS.max_epsilon / 255.0 + batch_shape = [FLAGS.batch_size, FLAGS.image_height, FLAGS.image_width, 3] + nb_classes = 1001 + + tf.logging.set_verbosity(tf.logging.INFO) + + with tf.Graph().as_default(): + # Prepare graph + x_input = tf.placeholder(tf.float32, shape=batch_shape) + + model = InceptionModel(nb_classes) + + fgsm = FastGradientMethod(model) + x_adv = fgsm.generate(x_input, eps=eps, clip_min=-1.0, clip_max=1.0) + + # Run computation + saver = tf.train.Saver(slim.get_model_variables()) + session_creator = tf.train.ChiefSessionCreator( + scaffold=tf.train.Scaffold(saver=saver), + checkpoint_filename_with_path=FLAGS.checkpoint_path, + master=FLAGS.master, + ) + + with tf.train.MonitoredSession(session_creator=session_creator) as sess: + for filenames, images in load_images(FLAGS.input_dir, batch_shape): + adv_images = sess.run(x_adv, feed_dict={x_input: images}) + save_images(adv_images, filenames, FLAGS.output_dir) + + +if __name__ == "__main__": + tf.app.run() diff --git a/examples/nips17_adversarial_competition/dev_toolkit/sample_attacks/fgsm/metadata.json b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/dev_toolkit/sample_attacks/fgsm/metadata.json similarity index 100% rename from examples/nips17_adversarial_competition/dev_toolkit/sample_attacks/fgsm/metadata.json rename to cleverhans_v3.1.0/examples/nips17_adversarial_competition/dev_toolkit/sample_attacks/fgsm/metadata.json diff --git a/examples/nips17_adversarial_competition/dev_toolkit/sample_attacks/fgsm/run_attack.sh b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/dev_toolkit/sample_attacks/fgsm/run_attack.sh similarity index 100% rename from examples/nips17_adversarial_competition/dev_toolkit/sample_attacks/fgsm/run_attack.sh rename to cleverhans_v3.1.0/examples/nips17_adversarial_competition/dev_toolkit/sample_attacks/fgsm/run_attack.sh diff --git a/cleverhans_v3.1.0/examples/nips17_adversarial_competition/dev_toolkit/sample_attacks/noop/attack_noop.py b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/dev_toolkit/sample_attacks/noop/attack_noop.py new file mode 100644 index 000000000..1027524a8 --- /dev/null +++ b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/dev_toolkit/sample_attacks/noop/attack_noop.py @@ -0,0 +1,87 @@ +"""Implementation of sample attack.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import os +import numpy as np + +from scipy.misc import imread +from scipy.misc import imsave + +import tensorflow as tf + + +tf.flags.DEFINE_string("master", "", "The address of the TensorFlow master to use.") + +tf.flags.DEFINE_string("input_dir", "", "Input directory with images.") + +tf.flags.DEFINE_string("output_dir", "", "Output directory with images.") + +tf.flags.DEFINE_float("max_epsilon", 16.0, "Maximum size of adversarial perturbation.") + +tf.flags.DEFINE_integer("image_width", 299, "Width of each input images.") + +tf.flags.DEFINE_integer("image_height", 299, "Height of each input images.") + +tf.flags.DEFINE_integer("batch_size", 16, "How many images process at one time.") + +FLAGS = tf.flags.FLAGS + + +def load_images(input_dir, batch_shape): + """Read png images from input directory in batches. + + Args: + input_dir: input directory + batch_shape: shape of minibatch array, i.e. [batch_size, height, width, 3] + + Yields: + filenames: list file names without path of each image + Length of this list could be less than batch_size, in this case only + first few images of the result are elements of the minibatch. + images: array with all images from this batch + """ + images = np.zeros(batch_shape) + filenames = [] + idx = 0 + batch_size = batch_shape[0] + for filepath in tf.gfile.Glob(os.path.join(input_dir, "*.png")): + with tf.gfile.Open(filepath) as f: + images[idx, :, :, :] = imread(f, mode="RGB").astype(np.float) / 255.0 + filenames.append(os.path.basename(filepath)) + idx += 1 + if idx == batch_size: + yield filenames, images + filenames = [] + images = np.zeros(batch_shape) + idx = 0 + if idx > 0: + yield filenames, images + + +def save_images(images, filenames, output_dir): + """Saves images to the output directory. + + Args: + images: array with minibatch of images + filenames: list of filenames without path + If number of file names in this list less than number of images in + the minibatch then only first len(filenames) images will be saved. + output_dir: directory where to save images + """ + for i, filename in enumerate(filenames): + with tf.gfile.Open(os.path.join(output_dir, filename), "w") as f: + imsave(f, images[i, :, :, :], format="png") + + +def main(_): + """Run the sample attack""" + batch_shape = [FLAGS.batch_size, FLAGS.image_height, FLAGS.image_width, 3] + for filenames, images in load_images(FLAGS.input_dir, batch_shape): + save_images(images, filenames, FLAGS.output_dir) + + +if __name__ == "__main__": + tf.app.run() diff --git a/examples/nips17_adversarial_competition/dev_toolkit/sample_attacks/noop/metadata.json b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/dev_toolkit/sample_attacks/noop/metadata.json similarity index 100% rename from examples/nips17_adversarial_competition/dev_toolkit/sample_attacks/noop/metadata.json rename to cleverhans_v3.1.0/examples/nips17_adversarial_competition/dev_toolkit/sample_attacks/noop/metadata.json diff --git a/examples/nips17_adversarial_competition/dev_toolkit/sample_attacks/noop/run_attack.sh b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/dev_toolkit/sample_attacks/noop/run_attack.sh similarity index 100% rename from examples/nips17_adversarial_competition/dev_toolkit/sample_attacks/noop/run_attack.sh rename to cleverhans_v3.1.0/examples/nips17_adversarial_competition/dev_toolkit/sample_attacks/noop/run_attack.sh diff --git a/cleverhans_v3.1.0/examples/nips17_adversarial_competition/dev_toolkit/sample_attacks/random_noise/attack_random_noise.py b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/dev_toolkit/sample_attacks/random_noise/attack_random_noise.py new file mode 100644 index 000000000..644b8c07d --- /dev/null +++ b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/dev_toolkit/sample_attacks/random_noise/attack_random_noise.py @@ -0,0 +1,96 @@ +"""Implementation of sample attack.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import os +import numpy as np + +from scipy.misc import imread +from scipy.misc import imsave + +import tensorflow as tf + + +tf.flags.DEFINE_string("master", "", "The address of the TensorFlow master to use.") + +tf.flags.DEFINE_string("input_dir", "", "Input directory with images.") + +tf.flags.DEFINE_string("output_dir", "", "Output directory with images.") + +tf.flags.DEFINE_float("max_epsilon", 16.0, "Maximum size of adversarial perturbation.") + +tf.flags.DEFINE_integer("image_width", 299, "Width of each input images.") + +tf.flags.DEFINE_integer("image_height", 299, "Height of each input images.") + +tf.flags.DEFINE_integer("batch_size", 16, "How many images process at one time.") + +FLAGS = tf.flags.FLAGS + + +def load_images(input_dir, batch_shape): + """Read png images from input directory in batches. + + Args: + input_dir: input directory + batch_shape: shape of minibatch array, i.e. [batch_size, height, width, 3] + + Yields: + filenames: list file names without path of each image + Lenght of this list could be less than batch_size, in this case only + first few images of the result are elements of the minibatch. + images: array with all images from this batch + """ + images = np.zeros(batch_shape) + filenames = [] + idx = 0 + batch_size = batch_shape[0] + for filepath in tf.gfile.Glob(os.path.join(input_dir, "*.png")): + with tf.gfile.Open(filepath) as f: + images[idx, :, :, :] = imread(f, mode="RGB").astype(np.float) / 255.0 + filenames.append(os.path.basename(filepath)) + idx += 1 + if idx == batch_size: + yield filenames, images + filenames = [] + images = np.zeros(batch_shape) + idx = 0 + if idx > 0: + yield filenames, images + + +def save_images(images, filenames, output_dir): + """Saves images to the output directory. + + Args: + images: array with minibatch of images + filenames: list of filenames without path + If number of file names in this list less than number of images in + the minibatch then only first len(filenames) images will be saved. + output_dir: directory where to save images + """ + for i, filename in enumerate(filenames): + with tf.gfile.Open(os.path.join(output_dir, filename), "w") as f: + imsave(f, images[i, :, :, :], format="png") + + +def main(_): + """Run the sample attack""" + eps = FLAGS.max_epsilon / 255.0 + batch_shape = [FLAGS.batch_size, FLAGS.image_height, FLAGS.image_width, 3] + + with tf.Graph().as_default(): + x_input = tf.placeholder(tf.float32, shape=batch_shape) + noisy_images = x_input + eps * tf.sign(tf.random_normal(batch_shape)) + x_output = tf.clip_by_value(noisy_images, 0.0, 1.0) + + with tf.Session(FLAGS.master) as sess: + for filenames, images in load_images(FLAGS.input_dir, batch_shape): + out_images = sess.run(x_output, feed_dict={x_input: images}) + save_images(out_images, filenames, FLAGS.output_dir) + + +if __name__ == "__main__": + tf.app.run() diff --git a/examples/nips17_adversarial_competition/dev_toolkit/sample_attacks/random_noise/metadata.json b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/dev_toolkit/sample_attacks/random_noise/metadata.json similarity index 100% rename from examples/nips17_adversarial_competition/dev_toolkit/sample_attacks/random_noise/metadata.json rename to cleverhans_v3.1.0/examples/nips17_adversarial_competition/dev_toolkit/sample_attacks/random_noise/metadata.json diff --git a/examples/nips17_adversarial_competition/dev_toolkit/sample_attacks/random_noise/run_attack.sh b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/dev_toolkit/sample_attacks/random_noise/run_attack.sh similarity index 100% rename from examples/nips17_adversarial_competition/dev_toolkit/sample_attacks/random_noise/run_attack.sh rename to cleverhans_v3.1.0/examples/nips17_adversarial_competition/dev_toolkit/sample_attacks/random_noise/run_attack.sh diff --git a/cleverhans_v3.1.0/examples/nips17_adversarial_competition/dev_toolkit/sample_defenses/adv_inception_v3/defense.py b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/dev_toolkit/sample_defenses/adv_inception_v3/defense.py new file mode 100644 index 000000000..2060cc48c --- /dev/null +++ b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/dev_toolkit/sample_defenses/adv_inception_v3/defense.py @@ -0,0 +1,109 @@ +"""Implementation of sample defense. + +This defense loads inception v3 checkpoint and classifies all images +using loaded checkpoint. +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import os + +import numpy as np +from scipy.misc import imread + +import tensorflow as tf +from tensorflow.contrib.slim.nets import inception + +slim = tf.contrib.slim + + +tf.flags.DEFINE_string("master", "", "The address of the TensorFlow master to use.") + +tf.flags.DEFINE_string( + "checkpoint_path", "", "Path to checkpoint for inception network." +) + +tf.flags.DEFINE_string("input_dir", "", "Input directory with images.") + +tf.flags.DEFINE_string("output_file", "", "Output file to save labels.") + +tf.flags.DEFINE_integer("image_width", 299, "Width of each input images.") + +tf.flags.DEFINE_integer("image_height", 299, "Height of each input images.") + +tf.flags.DEFINE_integer("batch_size", 16, "How many images process at one time.") + +FLAGS = tf.flags.FLAGS + + +def load_images(input_dir, batch_shape): + """Read png images from input directory in batches. + + Args: + input_dir: input directory + batch_shape: shape of minibatch array, i.e. [batch_size, height, width, 3] + + Yields: + filenames: list file names without path of each image + Lenght of this list could be less than batch_size, in this case only + first few images of the result are elements of the minibatch. + images: array with all images from this batch + """ + images = np.zeros(batch_shape) + filenames = [] + idx = 0 + batch_size = batch_shape[0] + for filepath in tf.gfile.Glob(os.path.join(input_dir, "*.png")): + with tf.gfile.Open(filepath) as f: + image = imread(f, mode="RGB").astype(np.float) / 255.0 + # Images for inception classifier are normalized to be in [-1, 1] interval. + images[idx, :, :, :] = image * 2.0 - 1.0 + filenames.append(os.path.basename(filepath)) + idx += 1 + if idx == batch_size: + yield filenames, images + filenames = [] + images = np.zeros(batch_shape) + idx = 0 + if idx > 0: + yield filenames, images + + +def main(_): + """Run the sample defense""" + batch_shape = [FLAGS.batch_size, FLAGS.image_height, FLAGS.image_width, 3] + nb_classes = 1001 + + tf.logging.set_verbosity(tf.logging.INFO) + + with tf.Graph().as_default(): + # Prepare graph + x_input = tf.placeholder(tf.float32, shape=batch_shape) + + with slim.arg_scope(inception.inception_v3_arg_scope()): + _, end_points = inception.inception_v3( + x_input, num_classes=nb_classes, is_training=False + ) + + predicted_labels = tf.argmax(end_points["Predictions"], 1) + + # Run computation + saver = tf.train.Saver(slim.get_model_variables()) + session_creator = tf.train.ChiefSessionCreator( + scaffold=tf.train.Scaffold(saver=saver), + checkpoint_filename_with_path=FLAGS.checkpoint_path, + master=FLAGS.master, + ) + + with tf.train.MonitoredSession(session_creator=session_creator) as sess: + with tf.gfile.Open(FLAGS.output_file, "w") as out_file: + for filenames, images in load_images(FLAGS.input_dir, batch_shape): + labels = sess.run(predicted_labels, feed_dict={x_input: images}) + for filename, label in zip(filenames, labels): + out_file.write("{0},{1}\n".format(filename, label)) + + +if __name__ == "__main__": + tf.app.run() diff --git a/examples/nips17_adversarial_competition/dev_toolkit/sample_defenses/adv_inception_v3/metadata.json b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/dev_toolkit/sample_defenses/adv_inception_v3/metadata.json similarity index 100% rename from examples/nips17_adversarial_competition/dev_toolkit/sample_defenses/adv_inception_v3/metadata.json rename to cleverhans_v3.1.0/examples/nips17_adversarial_competition/dev_toolkit/sample_defenses/adv_inception_v3/metadata.json diff --git a/examples/nips17_adversarial_competition/dev_toolkit/sample_defenses/adv_inception_v3/run_defense.sh b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/dev_toolkit/sample_defenses/adv_inception_v3/run_defense.sh similarity index 100% rename from examples/nips17_adversarial_competition/dev_toolkit/sample_defenses/adv_inception_v3/run_defense.sh rename to cleverhans_v3.1.0/examples/nips17_adversarial_competition/dev_toolkit/sample_defenses/adv_inception_v3/run_defense.sh diff --git a/cleverhans_v3.1.0/examples/nips17_adversarial_competition/dev_toolkit/sample_defenses/base_inception_model/defense.py b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/dev_toolkit/sample_defenses/base_inception_model/defense.py new file mode 100644 index 000000000..cec88d897 --- /dev/null +++ b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/dev_toolkit/sample_defenses/base_inception_model/defense.py @@ -0,0 +1,108 @@ +"""Implementation of sample defense. + +This defense loads inception v3 checkpoint and classifies all images +using loaded checkpoint. +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import os + +import numpy as np +from scipy.misc import imread + +import tensorflow as tf +from tensorflow.contrib.slim.nets import inception + +slim = tf.contrib.slim + + +tf.flags.DEFINE_string("master", "", "The address of the TensorFlow master to use.") + +tf.flags.DEFINE_string( + "checkpoint_path", "", "Path to checkpoint for inception network." +) + +tf.flags.DEFINE_string("input_dir", "", "Input directory with images.") + +tf.flags.DEFINE_string("output_file", "", "Output file to save labels.") + +tf.flags.DEFINE_integer("image_width", 299, "Width of each input images.") + +tf.flags.DEFINE_integer("image_height", 299, "Height of each input images.") + +tf.flags.DEFINE_integer("batch_size", 16, "How many images process at one time.") + +FLAGS = tf.flags.FLAGS + + +def load_images(input_dir, batch_shape): + """Read png images from input directory in batches. + + Args: + input_dir: input directory + batch_shape: shape of minibatch array, i.e. [batch_size, height, width, 3] + + Yields: + filenames: list file names without path of each image + Lenght of this list could be less than batch_size, in this case only + first few images of the result are elements of the minibatch. + images: array with all images from this batch + """ + images = np.zeros(batch_shape) + filenames = [] + idx = 0 + batch_size = batch_shape[0] + for filepath in tf.gfile.Glob(os.path.join(input_dir, "*.png")): + with tf.gfile.Open(filepath) as f: + image = imread(f, mode="RGB").astype(np.float) / 255.0 + # Images for inception classifier are normalized to be in [-1, 1] interval. + images[idx, :, :, :] = image * 2.0 - 1.0 + filenames.append(os.path.basename(filepath)) + idx += 1 + if idx == batch_size: + yield filenames, images + filenames = [] + images = np.zeros(batch_shape) + idx = 0 + if idx > 0: + yield filenames, images + + +def main(_): + batch_shape = [FLAGS.batch_size, FLAGS.image_height, FLAGS.image_width, 3] + nb_classes = 1001 + + tf.logging.set_verbosity(tf.logging.INFO) + + with tf.Graph().as_default(): + # Prepare graph + x_input = tf.placeholder(tf.float32, shape=batch_shape) + + with slim.arg_scope(inception.inception_v3_arg_scope()): + _, end_points = inception.inception_v3( + x_input, num_classes=nb_classes, is_training=False + ) + + predicted_labels = tf.argmax(end_points["Predictions"], 1) + + # Run computation + saver = tf.train.Saver(slim.get_model_variables()) + session_creator = tf.train.ChiefSessionCreator( + scaffold=tf.train.Scaffold(saver=saver), + checkpoint_filename_with_path=FLAGS.checkpoint_path, + master=FLAGS.master, + ) + + with tf.train.MonitoredSession(session_creator=session_creator) as sess: + with tf.gfile.Open(FLAGS.output_file, "w") as out_file: + for filenames, images in load_images(FLAGS.input_dir, batch_shape): + labels = sess.run(predicted_labels, feed_dict={x_input: images}) + for filename, label in zip(filenames, labels): + out_file.write("{0},{1}\n".format(filename, label)) + + +if __name__ == "__main__": + tf.app.run() diff --git a/examples/nips17_adversarial_competition/dev_toolkit/sample_defenses/base_inception_model/metadata.json b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/dev_toolkit/sample_defenses/base_inception_model/metadata.json similarity index 100% rename from examples/nips17_adversarial_competition/dev_toolkit/sample_defenses/base_inception_model/metadata.json rename to cleverhans_v3.1.0/examples/nips17_adversarial_competition/dev_toolkit/sample_defenses/base_inception_model/metadata.json diff --git a/examples/nips17_adversarial_competition/dev_toolkit/sample_defenses/base_inception_model/run_defense.sh b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/dev_toolkit/sample_defenses/base_inception_model/run_defense.sh similarity index 100% rename from examples/nips17_adversarial_competition/dev_toolkit/sample_defenses/base_inception_model/run_defense.sh rename to cleverhans_v3.1.0/examples/nips17_adversarial_competition/dev_toolkit/sample_defenses/base_inception_model/run_defense.sh diff --git a/examples/nips17_adversarial_competition/dev_toolkit/sample_defenses/download_checkpoints.sh b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/dev_toolkit/sample_defenses/download_checkpoints.sh similarity index 100% rename from examples/nips17_adversarial_competition/dev_toolkit/sample_defenses/download_checkpoints.sh rename to cleverhans_v3.1.0/examples/nips17_adversarial_competition/dev_toolkit/sample_defenses/download_checkpoints.sh diff --git a/cleverhans_v3.1.0/examples/nips17_adversarial_competition/dev_toolkit/sample_defenses/ens_adv_inception_resnet_v2/defense.py b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/dev_toolkit/sample_defenses/ens_adv_inception_resnet_v2/defense.py new file mode 100644 index 000000000..a77459ddb --- /dev/null +++ b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/dev_toolkit/sample_defenses/ens_adv_inception_resnet_v2/defense.py @@ -0,0 +1,110 @@ +"""Implementation of sample defense. + +This defense loads inception resnet v2 checkpoint and classifies all images +using loaded checkpoint. +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import os + +import numpy as np +from scipy.misc import imread + +import tensorflow as tf + +import inception_resnet_v2 + +slim = tf.contrib.slim + + +tf.flags.DEFINE_string("master", "", "The address of the TensorFlow master to use.") + +tf.flags.DEFINE_string( + "checkpoint_path", "", "Path to checkpoint for inception network." +) + +tf.flags.DEFINE_string("input_dir", "", "Input directory with images.") + +tf.flags.DEFINE_string("output_file", "", "Output file to save labels.") + +tf.flags.DEFINE_integer("image_width", 299, "Width of each input images.") + +tf.flags.DEFINE_integer("image_height", 299, "Height of each input images.") + +tf.flags.DEFINE_integer("batch_size", 16, "How many images process at one time.") + +FLAGS = tf.flags.FLAGS + + +def load_images(input_dir, batch_shape): + """Read png images from input directory in batches. + + Args: + input_dir: input directory + batch_shape: shape of minibatch array, i.e. [batch_size, height, width, 3] + + Yields: + filenames: list file names without path of each image + Lenght of this list could be less than batch_size, in this case only + first few images of the result are elements of the minibatch. + images: array with all images from this batch + """ + images = np.zeros(batch_shape) + filenames = [] + idx = 0 + batch_size = batch_shape[0] + for filepath in tf.gfile.Glob(os.path.join(input_dir, "*.png")): + with tf.gfile.Open(filepath) as f: + image = imread(f, mode="RGB").astype(np.float) / 255.0 + # Images for inception classifier are normalized to be in [-1, 1] interval. + images[idx, :, :, :] = image * 2.0 - 1.0 + filenames.append(os.path.basename(filepath)) + idx += 1 + if idx == batch_size: + yield filenames, images + filenames = [] + images = np.zeros(batch_shape) + idx = 0 + if idx > 0: + yield filenames, images + + +def main(_): + """Classify all images using the sample defense.""" + batch_shape = [FLAGS.batch_size, FLAGS.image_height, FLAGS.image_width, 3] + nb_classes = 1001 + + tf.logging.set_verbosity(tf.logging.INFO) + + with tf.Graph().as_default(): + # Prepare graph + x_input = tf.placeholder(tf.float32, shape=batch_shape) + + with slim.arg_scope(inception_resnet_v2.inception_resnet_v2_arg_scope()): + _, end_points = inception_resnet_v2.inception_resnet_v2( + x_input, num_classes=nb_classes, is_training=False + ) + + predicted_labels = tf.argmax(end_points["Predictions"], 1) + + # Run computation + saver = tf.train.Saver(slim.get_model_variables()) + session_creator = tf.train.ChiefSessionCreator( + scaffold=tf.train.Scaffold(saver=saver), + checkpoint_filename_with_path=FLAGS.checkpoint_path, + master=FLAGS.master, + ) + + with tf.train.MonitoredSession(session_creator=session_creator) as sess: + with tf.gfile.Open(FLAGS.output_file, "w") as out_file: + for filenames, images in load_images(FLAGS.input_dir, batch_shape): + labels = sess.run(predicted_labels, feed_dict={x_input: images}) + for filename, label in zip(filenames, labels): + out_file.write("{0},{1}\n".format(filename, label)) + + +if __name__ == "__main__": + tf.app.run() diff --git a/cleverhans_v3.1.0/examples/nips17_adversarial_competition/dev_toolkit/sample_defenses/ens_adv_inception_resnet_v2/inception_resnet_v2.py b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/dev_toolkit/sample_defenses/ens_adv_inception_resnet_v2/inception_resnet_v2.py new file mode 100644 index 000000000..c891370f2 --- /dev/null +++ b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/dev_toolkit/sample_defenses/ens_adv_inception_resnet_v2/inception_resnet_v2.py @@ -0,0 +1,466 @@ +# Copyright 2017 The TensorFlow Authors All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== + +"""Contains the definition of the Inception Resnet V2 architecture. + +As described in http://arxiv.org/abs/1602.07261. + + Inception-v4, Inception-ResNet and the Impact of Residual Connections + on Learning + Christian Szegedy, Sergey Ioffe, Vincent Vanhoucke, Alex Alemi +""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import warnings + +import tensorflow as tf + +slim = tf.contrib.slim + + +def block35(net, scale=1.0, activation_fn=tf.nn.relu, scope=None, reuse=None): + """Builds the 35x35 resnet block.""" + with tf.variable_scope(scope, "Block35", [net], reuse=reuse): + with tf.variable_scope("Branch_0"): + tower_conv = slim.conv2d(net, 32, 1, scope="Conv2d_1x1") + with tf.variable_scope("Branch_1"): + tower_conv1_0 = slim.conv2d(net, 32, 1, scope="Conv2d_0a_1x1") + tower_conv1_1 = slim.conv2d(tower_conv1_0, 32, 3, scope="Conv2d_0b_3x3") + with tf.variable_scope("Branch_2"): + tower_conv2_0 = slim.conv2d(net, 32, 1, scope="Conv2d_0a_1x1") + tower_conv2_1 = slim.conv2d(tower_conv2_0, 48, 3, scope="Conv2d_0b_3x3") + tower_conv2_2 = slim.conv2d(tower_conv2_1, 64, 3, scope="Conv2d_0c_3x3") + mixed = tf.concat(axis=3, values=[tower_conv, tower_conv1_1, tower_conv2_2]) + up = slim.conv2d( + mixed, + net.get_shape()[3], + 1, + normalizer_fn=None, + activation_fn=None, + scope="Conv2d_1x1", + ) + net += scale * up + if activation_fn: + net = activation_fn(net) + return net + + +def block17(net, scale=1.0, activation_fn=tf.nn.relu, scope=None, reuse=None): + """Builds the 17x17 resnet block.""" + with tf.variable_scope(scope, "Block17", [net], reuse=reuse): + with tf.variable_scope("Branch_0"): + tower_conv = slim.conv2d(net, 192, 1, scope="Conv2d_1x1") + with tf.variable_scope("Branch_1"): + tower_conv1_0 = slim.conv2d(net, 128, 1, scope="Conv2d_0a_1x1") + tower_conv1_1 = slim.conv2d( + tower_conv1_0, 160, [1, 7], scope="Conv2d_0b_1x7" + ) + tower_conv1_2 = slim.conv2d( + tower_conv1_1, 192, [7, 1], scope="Conv2d_0c_7x1" + ) + mixed = tf.concat(axis=3, values=[tower_conv, tower_conv1_2]) + up = slim.conv2d( + mixed, + net.get_shape()[3], + 1, + normalizer_fn=None, + activation_fn=None, + scope="Conv2d_1x1", + ) + net += scale * up + if activation_fn: + net = activation_fn(net) + return net + + +def block8(net, scale=1.0, activation_fn=tf.nn.relu, scope=None, reuse=None): + """Builds the 8x8 resnet block.""" + with tf.variable_scope(scope, "Block8", [net], reuse=reuse): + with tf.variable_scope("Branch_0"): + tower_conv = slim.conv2d(net, 192, 1, scope="Conv2d_1x1") + with tf.variable_scope("Branch_1"): + tower_conv1_0 = slim.conv2d(net, 192, 1, scope="Conv2d_0a_1x1") + tower_conv1_1 = slim.conv2d( + tower_conv1_0, 224, [1, 3], scope="Conv2d_0b_1x3" + ) + tower_conv1_2 = slim.conv2d( + tower_conv1_1, 256, [3, 1], scope="Conv2d_0c_3x1" + ) + mixed = tf.concat(axis=3, values=[tower_conv, tower_conv1_2]) + up = slim.conv2d( + mixed, + net.get_shape()[3], + 1, + normalizer_fn=None, + activation_fn=None, + scope="Conv2d_1x1", + ) + net += scale * up + if activation_fn: + net = activation_fn(net) + return net + + +def inception_resnet_v2_base( + inputs, + final_endpoint="Conv2d_7b_1x1", + output_stride=16, + align_feature_maps=False, + scope=None, +): + """Inception model from http://arxiv.org/abs/1602.07261. + + Constructs an Inception Resnet v2 network from inputs to the given final + endpoint. This method can construct the network up to the final inception + block Conv2d_7b_1x1. + + Args: + inputs: a tensor of size [batch_size, height, width, channels]. + final_endpoint: specifies the endpoint to construct the network up to. It + can be one of ['Conv2d_1a_3x3', 'Conv2d_2a_3x3', 'Conv2d_2b_3x3', + 'MaxPool_3a_3x3', 'Conv2d_3b_1x1', 'Conv2d_4a_3x3', 'MaxPool_5a_3x3', + 'Mixed_5b', 'Mixed_6a', 'PreAuxLogits', 'Mixed_7a', 'Conv2d_7b_1x1'] + output_stride: A scalar that specifies the requested ratio of input to + output spatial resolution. Only supports 8 and 16. + align_feature_maps: When true, changes all the VALID paddings in the network + to SAME padding so that the feature maps are aligned. + scope: Optional variable_scope. + + Returns: + tensor_out: output tensor corresponding to the final_endpoint. + end_points: a set of activations for external use, for example summaries or + losses. + + Raises: + ValueError: if final_endpoint is not set to one of the predefined values, + or if the output_stride is not 8 or 16, or if the output_stride is 8 and + we request an end point after 'PreAuxLogits'. + """ + if output_stride != 8 and output_stride != 16: + raise ValueError("output_stride must be 8 or 16.") + + padding = "SAME" if align_feature_maps else "VALID" + + end_points = {} + + def add_and_check_final(name, net): + """ + TODO: write this + """ + end_points[name] = net + return name == final_endpoint + + with tf.variable_scope(scope, "InceptionResnetV2", [inputs]): + with slim.arg_scope( + [slim.conv2d, slim.max_pool2d, slim.avg_pool2d], stride=1, padding="SAME" + ): + # 149 x 149 x 32 + net = slim.conv2d( + inputs, 32, 3, stride=2, padding=padding, scope="Conv2d_1a_3x3" + ) + if add_and_check_final("Conv2d_1a_3x3", net): + return net, end_points + + # 147 x 147 x 32 + net = slim.conv2d(net, 32, 3, padding=padding, scope="Conv2d_2a_3x3") + if add_and_check_final("Conv2d_2a_3x3", net): + return net, end_points + # 147 x 147 x 64 + net = slim.conv2d(net, 64, 3, scope="Conv2d_2b_3x3") + if add_and_check_final("Conv2d_2b_3x3", net): + return net, end_points + # 73 x 73 x 64 + net = slim.max_pool2d( + net, 3, stride=2, padding=padding, scope="MaxPool_3a_3x3" + ) + if add_and_check_final("MaxPool_3a_3x3", net): + return net, end_points + # 73 x 73 x 80 + net = slim.conv2d(net, 80, 1, padding=padding, scope="Conv2d_3b_1x1") + if add_and_check_final("Conv2d_3b_1x1", net): + return net, end_points + # 71 x 71 x 192 + net = slim.conv2d(net, 192, 3, padding=padding, scope="Conv2d_4a_3x3") + if add_and_check_final("Conv2d_4a_3x3", net): + return net, end_points + # 35 x 35 x 192 + net = slim.max_pool2d( + net, 3, stride=2, padding=padding, scope="MaxPool_5a_3x3" + ) + if add_and_check_final("MaxPool_5a_3x3", net): + return net, end_points + + # 35 x 35 x 320 + with tf.variable_scope("Mixed_5b"): + with tf.variable_scope("Branch_0"): + tower_conv = slim.conv2d(net, 96, 1, scope="Conv2d_1x1") + with tf.variable_scope("Branch_1"): + tower_conv1_0 = slim.conv2d(net, 48, 1, scope="Conv2d_0a_1x1") + tower_conv1_1 = slim.conv2d( + tower_conv1_0, 64, 5, scope="Conv2d_0b_5x5" + ) + with tf.variable_scope("Branch_2"): + tower_conv2_0 = slim.conv2d(net, 64, 1, scope="Conv2d_0a_1x1") + tower_conv2_1 = slim.conv2d( + tower_conv2_0, 96, 3, scope="Conv2d_0b_3x3" + ) + tower_conv2_2 = slim.conv2d( + tower_conv2_1, 96, 3, scope="Conv2d_0c_3x3" + ) + with tf.variable_scope("Branch_3"): + tower_pool = slim.avg_pool2d( + net, 3, stride=1, padding="SAME", scope="AvgPool_0a_3x3" + ) + tower_pool_1 = slim.conv2d(tower_pool, 64, 1, scope="Conv2d_0b_1x1") + net = tf.concat( + [tower_conv, tower_conv1_1, tower_conv2_2, tower_pool_1], 3 + ) + + if add_and_check_final("Mixed_5b", net): + return net, end_points + # TODO(alemi): Register intermediate endpoints + net = slim.repeat(net, 10, block35, scale=0.17) + + # 17 x 17 x 1088 if output_stride == 8, + # 33 x 33 x 1088 if output_stride == 16 + use_atrous = output_stride == 8 + + with tf.variable_scope("Mixed_6a"): + with tf.variable_scope("Branch_0"): + tower_conv = slim.conv2d( + net, + 384, + 3, + stride=1 if use_atrous else 2, + padding=padding, + scope="Conv2d_1a_3x3", + ) + with tf.variable_scope("Branch_1"): + tower_conv1_0 = slim.conv2d(net, 256, 1, scope="Conv2d_0a_1x1") + tower_conv1_1 = slim.conv2d( + tower_conv1_0, 256, 3, scope="Conv2d_0b_3x3" + ) + tower_conv1_2 = slim.conv2d( + tower_conv1_1, + 384, + 3, + stride=1 if use_atrous else 2, + padding=padding, + scope="Conv2d_1a_3x3", + ) + with tf.variable_scope("Branch_2"): + tower_pool = slim.max_pool2d( + net, + 3, + stride=1 if use_atrous else 2, + padding=padding, + scope="MaxPool_1a_3x3", + ) + net = tf.concat([tower_conv, tower_conv1_2, tower_pool], 3) + + if add_and_check_final("Mixed_6a", net): + return net, end_points + + # TODO(alemi): register intermediate endpoints + with slim.arg_scope([slim.conv2d], rate=2 if use_atrous else 1): + net = slim.repeat(net, 20, block17, scale=0.10) + if add_and_check_final("PreAuxLogits", net): + return net, end_points + + if output_stride == 8: + # TODO(gpapan): Properly support output_stride for the rest of the net. + raise ValueError( + "output_stride==8 is only supported up to the " + "PreAuxlogits end_point for now." + ) + + # 8 x 8 x 2080 + with tf.variable_scope("Mixed_7a"): + with tf.variable_scope("Branch_0"): + tower_conv = slim.conv2d(net, 256, 1, scope="Conv2d_0a_1x1") + tower_conv_1 = slim.conv2d( + tower_conv, + 384, + 3, + stride=2, + padding=padding, + scope="Conv2d_1a_3x3", + ) + with tf.variable_scope("Branch_1"): + tower_conv1 = slim.conv2d(net, 256, 1, scope="Conv2d_0a_1x1") + tower_conv1_1 = slim.conv2d( + tower_conv1, + 288, + 3, + stride=2, + padding=padding, + scope="Conv2d_1a_3x3", + ) + with tf.variable_scope("Branch_2"): + tower_conv2 = slim.conv2d(net, 256, 1, scope="Conv2d_0a_1x1") + tower_conv2_1 = slim.conv2d( + tower_conv2, 288, 3, scope="Conv2d_0b_3x3" + ) + tower_conv2_2 = slim.conv2d( + tower_conv2_1, + 320, + 3, + stride=2, + padding=padding, + scope="Conv2d_1a_3x3", + ) + with tf.variable_scope("Branch_3"): + tower_pool = slim.max_pool2d( + net, 3, stride=2, padding=padding, scope="MaxPool_1a_3x3" + ) + net = tf.concat( + [tower_conv_1, tower_conv1_1, tower_conv2_2, tower_pool], 3 + ) + + if add_and_check_final("Mixed_7a", net): + return net, end_points + + # TODO(alemi): register intermediate endpoints + net = slim.repeat(net, 9, block8, scale=0.20) + net = block8(net, activation_fn=None) + + # 8 x 8 x 1536 + net = slim.conv2d(net, 1536, 1, scope="Conv2d_7b_1x1") + if add_and_check_final("Conv2d_7b_1x1", net): + return net, end_points + + raise ValueError("final_endpoint (%s) not recognized" % final_endpoint) + + +def inception_resnet_v2( + inputs, + nb_classes=1001, + is_training=True, + dropout_keep_prob=0.8, + reuse=None, + scope="InceptionResnetV2", + create_aux_logits=True, + num_classes=None, +): + """Creates the Inception Resnet V2 model. + + Args: + inputs: a 4-D tensor of size [batch_size, height, width, 3]. + nb_classes: number of predicted classes. + is_training: whether is training or not. + dropout_keep_prob: float, the fraction to keep before final layer. + reuse: whether or not the network and its variables should be reused. To be + able to reuse 'scope' must be given. + scope: Optional variable_scope. + create_aux_logits: Whether to include the auxilliary logits. + num_classes: depricated alias for nb_classes + + Returns: + logits: the logits outputs of the model. + end_points: the set of end_points from the inception model. + """ + if num_classes is not None: + warnings.warn( + "`num_classes` is deprecated. Switch to `nb_classes`." + " `num_classes` may be removed on or after 2019-04-23." + ) + nb_classes = num_classes + del num_classes + end_points = {} + + with tf.variable_scope( + scope, "InceptionResnetV2", [inputs, nb_classes], reuse=reuse + ) as var_scope: + with slim.arg_scope([slim.batch_norm, slim.dropout], is_training=is_training): + + net, end_points = inception_resnet_v2_base(inputs, scope=var_scope) + + if create_aux_logits: + with tf.variable_scope("AuxLogits"): + aux = end_points["PreAuxLogits"] + aux = slim.avg_pool2d( + aux, 5, stride=3, padding="VALID", scope="Conv2d_1a_3x3" + ) + aux = slim.conv2d(aux, 128, 1, scope="Conv2d_1b_1x1") + aux = slim.conv2d( + aux, + 768, + aux.get_shape()[1:3], + padding="VALID", + scope="Conv2d_2a_5x5", + ) + aux = slim.flatten(aux) + aux = slim.fully_connected( + aux, nb_classes, activation_fn=None, scope="Logits" + ) + end_points["AuxLogits"] = aux + + with tf.variable_scope("Logits"): + net = slim.avg_pool2d( + net, net.get_shape()[1:3], padding="VALID", scope="AvgPool_1a_8x8" + ) + net = slim.flatten(net) + + net = slim.dropout( + net, dropout_keep_prob, is_training=is_training, scope="Dropout" + ) + + end_points["PreLogitsFlatten"] = net + logits = slim.fully_connected( + net, nb_classes, activation_fn=None, scope="Logits" + ) + end_points["Logits"] = logits + end_points["Predictions"] = tf.nn.softmax(logits, name="Predictions") + + return logits, end_points + + +inception_resnet_v2.default_image_size = 299 + + +def inception_resnet_v2_arg_scope( + weight_decay=0.00004, batch_norm_decay=0.9997, batch_norm_epsilon=0.001 +): + """Returns the scope with the default parameters for inception_resnet_v2. + + Args: + weight_decay: the weight decay for weights variables. + batch_norm_decay: decay for the moving average of batch_norm momentums. + batch_norm_epsilon: small float added to variance to avoid dividing by zero. + + Returns: + a arg_scope with the parameters needed for inception_resnet_v2. + """ + # Set weight_decay for weights in conv2d and fully_connected layers. + with slim.arg_scope( + [slim.conv2d, slim.fully_connected], + weights_regularizer=slim.l2_regularizer(weight_decay), + biases_regularizer=slim.l2_regularizer(weight_decay), + ): + + batch_norm_params = { + "decay": batch_norm_decay, + "epsilon": batch_norm_epsilon, + } + # Set activation_fn and parameters for batch_norm. + with slim.arg_scope( + [slim.conv2d], + activation_fn=tf.nn.relu, + normalizer_fn=slim.batch_norm, + normalizer_params=batch_norm_params, + ) as scope: + return scope diff --git a/examples/nips17_adversarial_competition/dev_toolkit/sample_defenses/ens_adv_inception_resnet_v2/metadata.json b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/dev_toolkit/sample_defenses/ens_adv_inception_resnet_v2/metadata.json similarity index 100% rename from examples/nips17_adversarial_competition/dev_toolkit/sample_defenses/ens_adv_inception_resnet_v2/metadata.json rename to cleverhans_v3.1.0/examples/nips17_adversarial_competition/dev_toolkit/sample_defenses/ens_adv_inception_resnet_v2/metadata.json diff --git a/examples/nips17_adversarial_competition/dev_toolkit/sample_defenses/ens_adv_inception_resnet_v2/run_defense.sh b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/dev_toolkit/sample_defenses/ens_adv_inception_resnet_v2/run_defense.sh similarity index 100% rename from examples/nips17_adversarial_competition/dev_toolkit/sample_defenses/ens_adv_inception_resnet_v2/run_defense.sh rename to cleverhans_v3.1.0/examples/nips17_adversarial_competition/dev_toolkit/sample_defenses/ens_adv_inception_resnet_v2/run_defense.sh diff --git a/examples/nips17_adversarial_competition/dev_toolkit/sample_targeted_attacks/download_checkpoints.sh b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/dev_toolkit/sample_targeted_attacks/download_checkpoints.sh similarity index 100% rename from examples/nips17_adversarial_competition/dev_toolkit/sample_targeted_attacks/download_checkpoints.sh rename to cleverhans_v3.1.0/examples/nips17_adversarial_competition/dev_toolkit/sample_targeted_attacks/download_checkpoints.sh diff --git a/cleverhans_v3.1.0/examples/nips17_adversarial_competition/dev_toolkit/sample_targeted_attacks/iter_target_class/attack_iter_target_class.py b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/dev_toolkit/sample_targeted_attacks/iter_target_class/attack_iter_target_class.py new file mode 100644 index 000000000..dedc5df8b --- /dev/null +++ b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/dev_toolkit/sample_targeted_attacks/iter_target_class/attack_iter_target_class.py @@ -0,0 +1,171 @@ +"""Implementation of sample attack.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import csv +import os + +import numpy as np +import tensorflow as tf +from tensorflow.contrib.slim.nets import inception +from scipy.misc import imread +from scipy.misc import imsave + + +slim = tf.contrib.slim + + +tf.flags.DEFINE_string("master", "", "The address of the TensorFlow master to use.") + +tf.flags.DEFINE_string( + "checkpoint_path", "", "Path to checkpoint for inception network." +) + +tf.flags.DEFINE_string("input_dir", "", "Input directory with images.") + +tf.flags.DEFINE_string("output_dir", "", "Output directory with images.") + +tf.flags.DEFINE_float("max_epsilon", 16.0, "Maximum size of adversarial perturbation.") + +tf.flags.DEFINE_float("iter_alpha", 1.0, "Step size for one iteration.") + +tf.flags.DEFINE_integer("num_iter", 20, "Number of iterations.") + +tf.flags.DEFINE_integer("image_width", 299, "Width of each input images.") + +tf.flags.DEFINE_integer("image_height", 299, "Height of each input images.") + +tf.flags.DEFINE_integer("batch_size", 16, "How many images process at one time.") + +FLAGS = tf.flags.FLAGS + + +def load_target_class(input_dir): + """Loads target classes.""" + with tf.gfile.Open(os.path.join(input_dir, "target_class.csv")) as f: + return {row[0]: int(row[1]) for row in csv.reader(f) if len(row) >= 2} + + +def load_images(input_dir, batch_shape): + """Read png images from input directory in batches. + + Args: + input_dir: input directory + batch_shape: shape of minibatch array, i.e. [batch_size, height, width, 3] + + Yields: + filenames: list file names without path of each image + Lenght of this list could be less than batch_size, in this case only + first few images of the result are elements of the minibatch. + images: array with all images from this batch + """ + images = np.zeros(batch_shape) + filenames = [] + idx = 0 + batch_size = batch_shape[0] + for filepath in tf.gfile.Glob(os.path.join(input_dir, "*.png")): + with tf.gfile.Open(filepath) as f: + image = imread(f, mode="RGB").astype(np.float) / 255.0 + # Images for inception classifier are normalized to be in [-1, 1] interval. + images[idx, :, :, :] = image * 2.0 - 1.0 + filenames.append(os.path.basename(filepath)) + idx += 1 + if idx == batch_size: + yield filenames, images + filenames = [] + images = np.zeros(batch_shape) + idx = 0 + if idx > 0: + yield filenames, images + + +def save_images(images, filenames, output_dir): + """Saves images to the output directory. + + Args: + images: array with minibatch of images + filenames: list of filenames without path + If number of file names in this list less than number of images in + the minibatch then only first len(filenames) images will be saved. + output_dir: directory where to save images + """ + for i, filename in enumerate(filenames): + # Images for inception classifier are normalized to be in [-1, 1] interval, + # so rescale them back to [0, 1]. + with tf.gfile.Open(os.path.join(output_dir, filename), "w") as f: + imsave(f, (images[i, :, :, :] + 1.0) * 0.5, format="png") + + +def main(_): + """Run the sample attack""" + # Images for inception classifier are normalized to be in [-1, 1] interval, + # eps is a difference between pixels so it should be in [0, 2] interval. + # Renormalizing epsilon from [0, 255] to [0, 2]. + eps = 2.0 * FLAGS.max_epsilon / 255.0 + alpha = 2.0 * FLAGS.iter_alpha / 255.0 + num_iter = FLAGS.num_iter + batch_shape = [FLAGS.batch_size, FLAGS.image_height, FLAGS.image_width, 3] + nb_classes = 1001 + + tf.logging.set_verbosity(tf.logging.INFO) + + all_images_taget_class = load_target_class(FLAGS.input_dir) + + with tf.Graph().as_default(): + # Prepare graph + x_input = tf.placeholder(tf.float32, shape=batch_shape) + x_max = tf.clip_by_value(x_input + eps, -1.0, 1.0) + x_min = tf.clip_by_value(x_input - eps, -1.0, 1.0) + + with slim.arg_scope(inception.inception_v3_arg_scope()): + inception.inception_v3(x_input, num_classes=nb_classes, is_training=False) + + x_adv = x_input + target_class_input = tf.placeholder(tf.int32, shape=[FLAGS.batch_size]) + one_hot_target_class = tf.one_hot(target_class_input, nb_classes) + + for _ in range(num_iter): + with slim.arg_scope(inception.inception_v3_arg_scope()): + logits, end_points = inception.inception_v3( + x_adv, num_classes=nb_classes, is_training=False, reuse=True + ) + cross_entropy = tf.losses.softmax_cross_entropy( + one_hot_target_class, logits, label_smoothing=0.1, weights=1.0 + ) + cross_entropy += tf.losses.softmax_cross_entropy( + one_hot_target_class, + end_points["AuxLogits"], + label_smoothing=0.1, + weights=0.4, + ) + x_next = x_adv - alpha * tf.sign(tf.gradients(cross_entropy, x_adv)[0]) + x_next = tf.clip_by_value(x_next, x_min, x_max) + x_adv = x_next + + # Run computation + saver = tf.train.Saver(slim.get_model_variables()) + session_creator = tf.train.ChiefSessionCreator( + scaffold=tf.train.Scaffold(saver=saver), + checkpoint_filename_with_path=FLAGS.checkpoint_path, + master=FLAGS.master, + ) + + with tf.train.MonitoredSession(session_creator=session_creator) as sess: + for filenames, images in load_images(FLAGS.input_dir, batch_shape): + target_class_for_batch = [ + all_images_taget_class[n] for n in filenames + ] + [0] * (FLAGS.batch_size - len(filenames)) + adv_images = sess.run( + x_adv, + feed_dict={ + x_input: images, + target_class_input: target_class_for_batch, + }, + ) + save_images(adv_images, filenames, FLAGS.output_dir) + + +if __name__ == "__main__": + tf.app.run() diff --git a/examples/nips17_adversarial_competition/dev_toolkit/sample_targeted_attacks/iter_target_class/metadata.json b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/dev_toolkit/sample_targeted_attacks/iter_target_class/metadata.json similarity index 100% rename from examples/nips17_adversarial_competition/dev_toolkit/sample_targeted_attacks/iter_target_class/metadata.json rename to cleverhans_v3.1.0/examples/nips17_adversarial_competition/dev_toolkit/sample_targeted_attacks/iter_target_class/metadata.json diff --git a/examples/nips17_adversarial_competition/dev_toolkit/sample_targeted_attacks/iter_target_class/run_attack.sh b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/dev_toolkit/sample_targeted_attacks/iter_target_class/run_attack.sh similarity index 100% rename from examples/nips17_adversarial_competition/dev_toolkit/sample_targeted_attacks/iter_target_class/run_attack.sh rename to cleverhans_v3.1.0/examples/nips17_adversarial_competition/dev_toolkit/sample_targeted_attacks/iter_target_class/run_attack.sh diff --git a/cleverhans_v3.1.0/examples/nips17_adversarial_competition/dev_toolkit/sample_targeted_attacks/step_target_class/attack_step_target_class.py b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/dev_toolkit/sample_targeted_attacks/step_target_class/attack_step_target_class.py new file mode 100644 index 000000000..0db4454af --- /dev/null +++ b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/dev_toolkit/sample_targeted_attacks/step_target_class/attack_step_target_class.py @@ -0,0 +1,156 @@ +"""Implementation of sample attack.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import csv +import os + +import numpy as np +import tensorflow as tf +from tensorflow.contrib.slim.nets import inception +from scipy.misc import imread +from scipy.misc import imsave + + +slim = tf.contrib.slim + + +tf.flags.DEFINE_string("master", "", "The address of the TensorFlow master to use.") + +tf.flags.DEFINE_string( + "checkpoint_path", "", "Path to checkpoint for inception network." +) + +tf.flags.DEFINE_string("input_dir", "", "Input directory with images.") + +tf.flags.DEFINE_string("output_dir", "", "Output directory with images.") + +tf.flags.DEFINE_float("max_epsilon", 16.0, "Maximum size of adversarial perturbation.") + +tf.flags.DEFINE_integer("image_width", 299, "Width of each input images.") + +tf.flags.DEFINE_integer("image_height", 299, "Height of each input images.") + +tf.flags.DEFINE_integer("batch_size", 16, "How many images process at one time.") + +FLAGS = tf.flags.FLAGS + + +def load_target_class(input_dir): + """Loads target classes.""" + with tf.gfile.Open(os.path.join(input_dir, "target_class.csv")) as f: + return {row[0]: int(row[1]) for row in csv.reader(f) if len(row) >= 2} + + +def load_images(input_dir, batch_shape): + """Read png images from input directory in batches. + + Args: + input_dir: input directory + batch_shape: shape of minibatch array, i.e. [batch_size, height, width, 3] + + Yields: + filenames: list file names without path of each image + Lenght of this list could be less than batch_size, in this case only + first few images of the result are elements of the minibatch. + images: array with all images from this batch + """ + images = np.zeros(batch_shape) + filenames = [] + idx = 0 + batch_size = batch_shape[0] + for filepath in tf.gfile.Glob(os.path.join(input_dir, "*.png")): + with tf.gfile.Open(filepath) as f: + image = imread(f, mode="RGB").astype(np.float) / 255.0 + # Images for inception classifier are normalized to be in [-1, 1] interval. + images[idx, :, :, :] = image * 2.0 - 1.0 + filenames.append(os.path.basename(filepath)) + idx += 1 + if idx == batch_size: + yield filenames, images + filenames = [] + images = np.zeros(batch_shape) + idx = 0 + if idx > 0: + yield filenames, images + + +def save_images(images, filenames, output_dir): + """Saves images to the output directory. + + Args: + images: array with minibatch of images + filenames: list of filenames without path + If number of file names in this list less than number of images in + the minibatch then only first len(filenames) images will be saved. + output_dir: directory where to save images + """ + for i, filename in enumerate(filenames): + # Images for inception classifier are normalized to be in [-1, 1] interval, + # so rescale them back to [0, 1]. + with tf.gfile.Open(os.path.join(output_dir, filename), "w") as f: + imsave(f, (images[i, :, :, :] + 1.0) * 0.5, format="png") + + +def main(_): + # Images for inception classifier are normalized to be in [-1, 1] interval, + # eps is a difference between pixels so it should be in [0, 2] interval. + # Renormalizing epsilon from [0, 255] to [0, 2]. + eps = 2.0 * FLAGS.max_epsilon / 255.0 + batch_shape = [FLAGS.batch_size, FLAGS.image_height, FLAGS.image_width, 3] + nb_classes = 1001 + + tf.logging.set_verbosity(tf.logging.INFO) + + all_images_taget_class = load_target_class(FLAGS.input_dir) + + with tf.Graph().as_default(): + # Prepare graph + x_input = tf.placeholder(tf.float32, shape=batch_shape) + + with slim.arg_scope(inception.inception_v3_arg_scope()): + logits, end_points = inception.inception_v3( + x_input, num_classes=nb_classes, is_training=False + ) + + target_class_input = tf.placeholder(tf.int32, shape=[FLAGS.batch_size]) + one_hot_target_class = tf.one_hot(target_class_input, nb_classes) + cross_entropy = tf.losses.softmax_cross_entropy( + one_hot_target_class, logits, label_smoothing=0.1, weights=1.0 + ) + cross_entropy += tf.losses.softmax_cross_entropy( + one_hot_target_class, + end_points["AuxLogits"], + label_smoothing=0.1, + weights=0.4, + ) + x_adv = x_input - eps * tf.sign(tf.gradients(cross_entropy, x_input)[0]) + x_adv = tf.clip_by_value(x_adv, -1.0, 1.0) + + # Run computation + saver = tf.train.Saver(slim.get_model_variables()) + session_creator = tf.train.ChiefSessionCreator( + scaffold=tf.train.Scaffold(saver=saver), + checkpoint_filename_with_path=FLAGS.checkpoint_path, + master=FLAGS.master, + ) + + with tf.train.MonitoredSession(session_creator=session_creator) as sess: + for filenames, images in load_images(FLAGS.input_dir, batch_shape): + target_class_for_batch = [ + all_images_taget_class[n] for n in filenames + ] + [0] * (FLAGS.batch_size - len(filenames)) + adv_images = sess.run( + x_adv, + feed_dict={ + x_input: images, + target_class_input: target_class_for_batch, + }, + ) + save_images(adv_images, filenames, FLAGS.output_dir) + + +if __name__ == "__main__": + tf.app.run() diff --git a/examples/nips17_adversarial_competition/dev_toolkit/sample_targeted_attacks/step_target_class/metadata.json b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/dev_toolkit/sample_targeted_attacks/step_target_class/metadata.json similarity index 100% rename from examples/nips17_adversarial_competition/dev_toolkit/sample_targeted_attacks/step_target_class/metadata.json rename to cleverhans_v3.1.0/examples/nips17_adversarial_competition/dev_toolkit/sample_targeted_attacks/step_target_class/metadata.json diff --git a/examples/nips17_adversarial_competition/dev_toolkit/sample_targeted_attacks/step_target_class/run_attack.sh b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/dev_toolkit/sample_targeted_attacks/step_target_class/run_attack.sh similarity index 100% rename from examples/nips17_adversarial_competition/dev_toolkit/sample_targeted_attacks/step_target_class/run_attack.sh rename to cleverhans_v3.1.0/examples/nips17_adversarial_competition/dev_toolkit/sample_targeted_attacks/step_target_class/run_attack.sh diff --git a/examples/nips17_adversarial_competition/dev_toolkit/validation_tool/README.md b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/dev_toolkit/validation_tool/README.md similarity index 100% rename from examples/nips17_adversarial_competition/dev_toolkit/validation_tool/README.md rename to cleverhans_v3.1.0/examples/nips17_adversarial_competition/dev_toolkit/validation_tool/README.md diff --git a/cleverhans_v3.1.0/examples/nips17_adversarial_competition/dev_toolkit/validation_tool/submission_validator_lib.py b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/dev_toolkit/validation_tool/submission_validator_lib.py new file mode 100644 index 000000000..a8142ee38 --- /dev/null +++ b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/dev_toolkit/validation_tool/submission_validator_lib.py @@ -0,0 +1,470 @@ +"""Helper library which performs validation of the submission.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from builtins import int # long in python 2 + +import csv +import json +import logging +import os +import re +import subprocess + +import numpy as np +from six import iteritems +from PIL import Image + + +EXTRACT_COMMAND = { + ".zip": ["unzip", "${src}", "-d", "${dst}"], + ".tar": ["tar", "xvf", "${src}", "-C", "${dst}"], + ".tar.gz": ["tar", "xvzf", "${src}", "-C", "${dst}"], +} + +ALLOWED_SUBMISSION_TYPES = ["attack", "targeted_attack", "defense"] + +REQUIRED_METADATA_JSON_FIELDS = ["entry_point", "container", "container_gpu", "type"] + +CMD_VARIABLE_RE = re.compile("^\\$\\{(\\w+)\\}$") + +BATCH_SIZE = 100 +IMAGE_NAME_PATTERN = "IMG{0:04}.png" + +ALLOWED_EPS = [4, 8, 12, 16] + +MAX_SUBMISSION_SIZE_ZIPPED = 8 * 1024 * 1024 * 1024 # 8 GiB +MAX_SUBMISSION_SIZE_UNPACKED = 16 * 1024 * 1024 * 1024 # 16 GiB +MAX_DOCKER_IMAGE_SIZE = 8 * 1024 * 1024 * 1024 # 8 GiB + + +def get_extract_command_template(filename): + """Returns extraction command based on the filename extension.""" + for k, v in iteritems(EXTRACT_COMMAND): + if filename.endswith(k): + return v + return None + + +def shell_call(command, **kwargs): + """Calls shell command with parameter substitution. + + Args: + command: command to run as a list of tokens + **kwargs: dirctionary with substitutions + + Returns: + whether command was successful, i.e. returned 0 status code + + Example of usage: + shell_call(['cp', '${A}', '${B}'], A='src_file', B='dst_file') + will call shell command: + cp src_file dst_file + """ + command = list(command) + for i in range(len(command)): + m = CMD_VARIABLE_RE.match(command[i]) + if m: + var_id = m.group(1) + if var_id in kwargs: + command[i] = kwargs[var_id] + return subprocess.call(command) == 0 + + +def make_directory_writable(dirname): + """Makes directory readable and writable by everybody. + + Args: + dirname: name of the directory + + Returns: + True if operation was successfull + + If you run something inside Docker container and it writes files, then + these files will be written as root user with restricted permissions. + So to be able to read/modify these files outside of Docker you have to change + permissions to be world readable and writable. + """ + retval = shell_call( + [ + "docker", + "run", + "-v", + "{0}:/output_dir".format(dirname), + "busybox:1.27.2", + "chmod", + "-R", + "a+rwx", + "/output_dir", + ] + ) + if not retval: + logging.error("Failed to change permissions on directory: %s", dirname) + return retval + + +def load_defense_output(filename): + """Loads output of defense from given file.""" + result = {} + with open(filename) as f: + for row in csv.reader(f): + try: + image_filename = row[0] + if not image_filename.endswith(".png"): + image_filename += ".png" + label = int(row[1]) + except (IndexError, ValueError): + continue + result[image_filename] = label + return result + + +class SubmissionValidator(object): + """Class which performs validation of the submission.""" + + def __init__(self, temp_dir, use_gpu): + """Initializes instance of SubmissionValidator. + + Args: + temp_dir: temporary working directory + use_gpu: whether to use GPU + """ + self._temp_dir = temp_dir + self._use_gpu = use_gpu + self._tmp_extracted_dir = os.path.join(self._temp_dir, "tmp_extracted") + self._extracted_submission_dir = os.path.join(self._temp_dir, "extracted") + self._sample_input_dir = os.path.join(self._temp_dir, "input") + self._sample_output_dir = os.path.join(self._temp_dir, "output") + + def _prepare_temp_dir(self): + """Cleans up and prepare temporary directory.""" + shell_call(["rm", "-rf", os.path.join(self._temp_dir, "*")]) + # NOTE: we do not create self._extracted_submission_dir + # this is intentional because self._tmp_extracted_dir or it's subdir + # will be renames into self._extracted_submission_dir + os.mkdir(self._tmp_extracted_dir) + os.mkdir(self._sample_input_dir) + os.mkdir(self._sample_output_dir) + # make output dir world writable + shell_call(["chmod", "a+rwX", "-R", self._sample_output_dir]) + + def _extract_submission(self, filename): + """Extracts submission and moves it into self._extracted_submission_dir.""" + # verify filesize + file_size = os.path.getsize(filename) + if file_size > MAX_SUBMISSION_SIZE_ZIPPED: + logging.error( + "Submission archive size %d is exceeding limit %d", + file_size, + MAX_SUBMISSION_SIZE_ZIPPED, + ) + return False + # determime archive type + exctract_command_tmpl = get_extract_command_template(filename) + if not exctract_command_tmpl: + logging.error( + "Input file has to be zip, tar or tar.gz archive; however " "found: %s", + filename, + ) + return False + # extract archive + submission_dir = os.path.dirname(filename) + submission_basename = os.path.basename(filename) + logging.info("Extracting archive %s", filename) + retval = shell_call( + [ + "docker", + "run", + "--network=none", + "-v", + "{0}:/input_dir".format(submission_dir), + "-v", + "{0}:/output_dir".format(self._tmp_extracted_dir), + "busybox:1.27.2", + ] + + exctract_command_tmpl, + src=os.path.join("/input_dir", submission_basename), + dst="/output_dir", + ) + if not retval: + logging.error("Failed to extract submission from file %s", filename) + return False + if not make_directory_writable(self._tmp_extracted_dir): + return False + # find submission root + root_dir = self._tmp_extracted_dir + root_dir_content = [d for d in os.listdir(root_dir) if d != "__MACOSX"] + if len(root_dir_content) == 1 and os.path.isdir( + os.path.join(root_dir, root_dir_content[0]) + ): + logging.info( + 'Looks like submission root is in subdirectory "%s" of ' "the archive", + root_dir_content[0], + ) + root_dir = os.path.join(root_dir, root_dir_content[0]) + # Move files to self._extracted_submission_dir. + # At this point self._extracted_submission_dir does not exist, + # so following command will simply rename root_dir into + # self._extracted_submission_dir + if not shell_call(["mv", root_dir, self._extracted_submission_dir]): + logging.error("Can" "t move submission files from root directory") + return False + return True + + def _verify_submission_size(self): + submission_size = 0 + for dirname, _, filenames in os.walk(self._extracted_submission_dir): + for f in filenames: + submission_size += os.path.getsize(os.path.join(dirname, f)) + logging.info("Unpacked submission size: %d", submission_size) + if submission_size > MAX_SUBMISSION_SIZE_UNPACKED: + logging.error( + "Submission size exceeding limit %d", MAX_SUBMISSION_SIZE_UNPACKED + ) + return submission_size <= MAX_SUBMISSION_SIZE_UNPACKED + + def _load_and_verify_metadata(self, submission_type): + """Loads and verifies metadata. + + Args: + submission_type: type of the submission + + Returns: + dictionaty with metadata or None if metadata not found or invalid + """ + metadata_filename = os.path.join( + self._extracted_submission_dir, "metadata.json" + ) + if not os.path.isfile(metadata_filename): + logging.error("metadata.json not found") + return None + try: + with open(metadata_filename, "r") as f: + metadata = json.load(f) + except IOError as e: + logging.error("Failed to load metadata: %s", e) + return None + for field_name in REQUIRED_METADATA_JSON_FIELDS: + if field_name not in metadata: + logging.error("Field %s not found in metadata", field_name) + return None + # Verify submission type + if submission_type != metadata["type"]: + logging.error( + 'Invalid submission type in metadata, expected "%s", ' 'actual "%s"', + submission_type, + metadata["type"], + ) + return None + # Check submission entry point + entry_point = metadata["entry_point"] + if not os.path.isfile( + os.path.join(self._extracted_submission_dir, entry_point) + ): + logging.error("Entry point not found: %s", entry_point) + return None + if not entry_point.endswith(".sh"): + logging.warning( + "Entry point is not an .sh script. " + "This is not necessarily a problem, but if submission " + "won" + "t run double check entry point first: %s", + entry_point, + ) + # Metadata verified + return metadata + + def _verify_docker_image_size(self, image_name): + """Verifies size of Docker image. + + Args: + image_name: name of the Docker image. + + Returns: + True if image size is withing the limits, False otherwise. + """ + shell_call(["docker", "pull", image_name]) + try: + image_size = subprocess.check_output( + ["docker", "inspect", "--format={{.Size}}", image_name] + ).strip() + image_size = int(image_size) + except (ValueError, subprocess.CalledProcessError) as e: + logging.error("Failed to determine docker image size: %s", e) + return False + logging.info("Size of docker image %s is %d", image_name, image_size) + if image_size > MAX_DOCKER_IMAGE_SIZE: + logging.error("Image size exceeds limit %d", MAX_DOCKER_IMAGE_SIZE) + return image_size <= MAX_DOCKER_IMAGE_SIZE + + def _prepare_sample_data(self, submission_type): + """Prepares sample data for the submission. + + Args: + submission_type: type of the submission. + """ + # write images + images = np.random.randint( + 0, 256, size=[BATCH_SIZE, 299, 299, 3], dtype=np.uint8 + ) + for i in range(BATCH_SIZE): + Image.fromarray(images[i, :, :, :]).save( + os.path.join(self._sample_input_dir, IMAGE_NAME_PATTERN.format(i)) + ) + # write target class for targeted attacks + if submission_type == "targeted_attack": + target_classes = np.random.randint(1, 1001, size=[BATCH_SIZE]) + target_class_filename = os.path.join( + self._sample_input_dir, "target_class.csv" + ) + with open(target_class_filename, "w") as f: + for i in range(BATCH_SIZE): + f.write( + (IMAGE_NAME_PATTERN + ",{1}\n").format(i, target_classes[i]) + ) + + def _run_submission(self, metadata): + """Runs submission inside Docker container. + + Args: + metadata: dictionary with submission metadata + + Returns: + True if status code of Docker command was success (i.e. zero), + False otherwise. + """ + if self._use_gpu: + docker_binary = "nvidia-docker" + container_name = metadata["container_gpu"] + else: + docker_binary = "docker" + container_name = metadata["container"] + if metadata["type"] == "defense": + cmd = [ + docker_binary, + "run", + "--network=none", + "-m=24g", + "-v", + "{0}:/input_images:ro".format(self._sample_input_dir), + "-v", + "{0}:/output_data".format(self._sample_output_dir), + "-v", + "{0}:/code".format(self._extracted_submission_dir), + "-w", + "/code", + container_name, + "./" + metadata["entry_point"], + "/input_images", + "/output_data/result.csv", + ] + else: + epsilon = np.random.choice(ALLOWED_EPS) + cmd = [ + docker_binary, + "run", + "--network=none", + "-m=24g", + "-v", + "{0}:/input_images:ro".format(self._sample_input_dir), + "-v", + "{0}:/output_images".format(self._sample_output_dir), + "-v", + "{0}:/code".format(self._extracted_submission_dir), + "-w", + "/code", + container_name, + "./" + metadata["entry_point"], + "/input_images", + "/output_images", + str(epsilon), + ] + logging.info("Command to run submission: %s", " ".join(cmd)) + return shell_call(cmd) + + def _verify_output(self, submission_type): + """Verifies correctness of the submission output. + + Args: + submission_type: type of the submission + + Returns: + True if output looks valid + """ + result = True + if submission_type == "defense": + try: + image_classification = load_defense_output( + os.path.join(self._sample_output_dir, "result.csv") + ) + expected_keys = [ + IMAGE_NAME_PATTERN.format(i) for i in range(BATCH_SIZE) + ] + if set(image_classification.keys()) != set(expected_keys): + logging.error("Classification results are not saved for all images") + result = False + except IOError as e: + logging.error("Failed to read defense output file: %s", e) + result = False + else: + for i in range(BATCH_SIZE): + image_filename = os.path.join( + self._sample_output_dir, IMAGE_NAME_PATTERN.format(i) + ) + try: + img = np.array(Image.open(image_filename).convert("RGB")) + if list(img.shape) != [299, 299, 3]: + logging.error( + "Invalid image size %s for image %s", + str(img.shape), + image_filename, + ) + result = False + except IOError as e: + result = False + return result + + def validate_submission(self, filename, submission_type): + """Validates submission. + + Args: + filename: submission filename + submission_type: type of the submission, + one of 'attack', 'targeted_attack' or 'defense' + + Returns: + whether submission is valid + """ + if submission_type not in ALLOWED_SUBMISSION_TYPES: + logging.error("Invalid submission type: %s", submission_type) + return False + self._prepare_temp_dir() + # Convert filename to be absolute path, + # relative path might cause problems when monting directory in Docker + filename = os.path.abspath(filename) + # extract submission + if not self._extract_submission(filename): + return False + # verify submission size + if not self._verify_submission_size(): + return False + # Load metadata + metadata = self._load_and_verify_metadata(submission_type) + if not metadata: + return False + # verify docker container size + if not self._verify_docker_image_size(metadata["container_gpu"]): + return False + # Try to run submission on sample data + self._prepare_sample_data(submission_type) + if not self._run_submission(metadata): + logging.error("Failure while running submission") + return False + if not self._verify_output(submission_type): + logging.warning( + "Some of the outputs of your submission are invalid or " + "missing. You submission still will be evaluation " + "but you might get lower score." + ) + return True diff --git a/cleverhans_v3.1.0/examples/nips17_adversarial_competition/dev_toolkit/validation_tool/validate_submission.py b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/dev_toolkit/validation_tool/validate_submission.py new file mode 100644 index 000000000..fda740bba --- /dev/null +++ b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/dev_toolkit/validation_tool/validate_submission.py @@ -0,0 +1,91 @@ +r"""Tool to validate submission for adversarial competition. + +Usage: + python validate_submission.py \ + --submission_filename=FILENAME \ + --submission_type=TYPE \ + [--use_gpu] + +Where: + FILENAME - filename of the submission + TYPE - type of the submission, one of the following without quotes: + "attack", "targeted_attack" or "defense" + --use_gpu - if argument specified then submission will be run on GPU using + nvidia-docker, otherwise will be run on CPU. + +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import argparse +import logging +import random +import subprocess +import tempfile +import submission_validator_lib + + +def print_in_box(text): + """ + Prints `text` surrounded by a box made of *s + """ + print("") + print("*" * (len(text) + 6)) + print("** " + text + " **") + print("*" * (len(text) + 6)) + print("") + + +def main(args): + """ + Validates the submission. + """ + print_in_box("Validating submission " + args.submission_filename) + random.seed() + temp_dir = args.temp_dir + delete_temp_dir = False + if not temp_dir: + temp_dir = tempfile.mkdtemp() + logging.info("Created temporary directory: %s", temp_dir) + delete_temp_dir = True + validator = submission_validator_lib.SubmissionValidator(temp_dir, args.use_gpu) + if validator.validate_submission(args.submission_filename, args.submission_type): + print_in_box("Submission is VALID!") + else: + print_in_box("Submission is INVALID, see log messages for details") + if delete_temp_dir: + logging.info("Deleting temporary directory: %s", temp_dir) + subprocess.call(["rm", "-rf", temp_dir]) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Submission validation script.") + parser.add_argument( + "--submission_filename", required=True, help="Filename of the submission." + ) + parser.add_argument( + "--submission_type", + required=True, + help="Type of the submission, " + 'one of "attack", "targeted_attack" or "defense"', + ) + parser.add_argument( + "--temp_dir", + required=False, + default="", + help="Temporary directory to extract and run submission. " + "If empty then temporary directory will be created " + "by the script and then deleted in the end.", + ) + parser.add_argument("--use_gpu", dest="use_gpu", action="store_true") + parser.add_argument("--nouse_gpu", dest="use_gpu", action="store_false") + parser.set_defaults(use_gpu=False) + loggint_format = ( + "%(asctime)s - %(filename)s:%(lineno)d - %(levelname)s -- " "%(message)s" + ) + logging.basicConfig( + format=loggint_format, level=logging.INFO, datefmt="%Y-%m-%d %H:%M:%S" + ) + main(parser.parse_args()) diff --git a/examples/nips17_adversarial_competition/eval_infra/README.md b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/eval_infra/README.md similarity index 100% rename from examples/nips17_adversarial_competition/eval_infra/README.md rename to cleverhans_v3.1.0/examples/nips17_adversarial_competition/eval_infra/README.md diff --git a/examples/nips17_adversarial_competition/eval_infra/code/eval_lib/__init__.py b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/eval_infra/code/eval_lib/__init__.py similarity index 100% rename from examples/nips17_adversarial_competition/eval_infra/code/eval_lib/__init__.py rename to cleverhans_v3.1.0/examples/nips17_adversarial_competition/eval_infra/code/eval_lib/__init__.py diff --git a/cleverhans_v3.1.0/examples/nips17_adversarial_competition/eval_infra/code/eval_lib/classification_results.py b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/eval_infra/code/eval_lib/classification_results.py new file mode 100644 index 000000000..dad5e65bb --- /dev/null +++ b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/eval_infra/code/eval_lib/classification_results.py @@ -0,0 +1,413 @@ +"""Module with classes to compute, read and store classification results. +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import csv +from io import BytesIO +from io import StringIO +import logging +import os +import pickle +import time +from six import iteritems +from six import iterkeys +from six import itervalues +from six import PY3 + +KIND_CLASSIFICATION_BATCH = u"ClassificationBatch" +CLASSIFICATION_BATCH_ID_PATTERN = u"CBATCH{:06}" + +CLASSIFICATION_BATCHES_SUBDIR = "classification_batches" + +TO_STR_MAX_BATCHES = 10 + +MAX_ALLOWED_CLASSIFICATION_RESULT_SIZE = 10000 + + +def read_classification_results(storage_client, file_path): + """Reads classification results from the file in Cloud Storage. + + This method reads file with classification results produced by running + defense on singe batch of adversarial images. + + Args: + storage_client: instance of CompetitionStorageClient or None for local file + file_path: path of the file with results + + Returns: + dictionary where keys are image names or IDs and values are classification + labels + """ + if storage_client: + # file on Cloud + success = False + retry_count = 0 + while retry_count < 4: + try: + blob = storage_client.get_blob(file_path) + if not blob: + return {} + if blob.size > MAX_ALLOWED_CLASSIFICATION_RESULT_SIZE: + logging.warning( + "Skipping classification result because it" + "s too " + "big: %d bytes for %s", + blob.size, + file_path, + ) + return None + buf = BytesIO() + blob.download_to_file(buf) + buf.seek(0) + success = True + break + except Exception: + retry_count += 1 + time.sleep(5) + if not success: + return None + else: + # local file + try: + with open(file_path, "rb") as f: + buf = BytesIO(f.read()) + except IOError: + return None + result = {} + if PY3: + buf = StringIO(buf.read().decode("UTF-8")) + for row in csv.reader(buf): + try: + image_filename = row[0] + if image_filename.endswith(".png") or image_filename.endswith(".jpg"): + image_filename = image_filename[: image_filename.rfind(".")] + label = int(row[1]) + except (IndexError, ValueError): + continue + result[image_filename] = label + return result + + +def analyze_one_classification_result( + storage_client, file_path, adv_batch, dataset_batches, dataset_meta +): + """Reads and analyzes one classification result. + + This method reads file with classification result and counts + how many images were classified correctly and incorrectly, + how many times target class was hit and total number of images. + + Args: + storage_client: instance of CompetitionStorageClient + file_path: result file path + adv_batch: AversarialBatches.data[adv_batch_id] + adv_batch_id is stored in each ClassificationBatch entity + dataset_batches: instance of DatasetBatches + dataset_meta: instance of DatasetMetadata + + Returns: + Tuple of (count_correctly_classified, count_errors, count_hit_target_class, + num_images) + """ + class_result = read_classification_results(storage_client, file_path) + if class_result is None: + return 0, 0, 0, 0 + adv_images = adv_batch["images"] + dataset_batch_images = dataset_batches.data[adv_batch["dataset_batch_id"]]["images"] + count_correctly_classified = 0 + count_errors = 0 + count_hit_target_class = 0 + num_images = 0 + for adv_img_id, label in iteritems(class_result): + if adv_img_id not in adv_images: + continue + num_images += 1 + clean_image_id = adv_images[adv_img_id]["clean_image_id"] + dataset_image_id = dataset_batch_images[clean_image_id]["dataset_image_id"] + if label == dataset_meta.get_true_label(dataset_image_id): + count_correctly_classified += 1 + else: + count_errors += 1 + if label == dataset_meta.get_target_class(dataset_image_id): + count_hit_target_class += 1 + return ( + count_correctly_classified, + count_errors, + count_hit_target_class, + num_images, + ) + + +class ResultMatrix(object): + """Sparse matrix where rows and columns are indexed using string. + + This matrix is used to store resutls of the competition evaluation. + """ + + def __init__(self, default_value=0): + """Initializes empty matrix.""" + self._items = {} + self._dim0 = set() + self._dim1 = set() + self._default_value = default_value + + @property + def dim0(self): + """Returns set of rows.""" + return self._dim0 + + @property + def dim1(self): + """Returns set of columns.""" + return self._dim1 + + def __getitem__(self, key): + """Returns element of the matrix indexed by given key. + + Args: + key: tuple of (row_idx, column_idx) + + Returns: + Element of the matrix + + Raises: + IndexError: if key is invalid. + """ + if not isinstance(key, tuple) or len(key) != 2: + raise IndexError("Invalid index: {0}".format(key)) + return self._items.get(key, self._default_value) + + def __setitem__(self, key, value): + """Sets element of the matrix at position indexed by key. + + Args: + key: tuple of (row_idx, column_idx) + value: new value of the element of the matrix + + Raises: + IndexError: if key is invalid. + """ + if not isinstance(key, tuple) or len(key) != 2: + raise IndexError("Invalid index: {0}".format(key)) + self._dim0.add(key[0]) + self._dim1.add(key[1]) + self._items[key] = value + + def save_to_file(self, filename, remap_dim0=None, remap_dim1=None): + """Saves matrix to the file. + + Args: + filename: name of the file where to save matrix + remap_dim0: dictionary with mapping row indices to row names which should + be saved to file. If none then indices will be used as names. + remap_dim1: dictionary with mapping column indices to column names which + should be saved to file. If none then indices will be used as names. + """ + # rows - first index + # columns - second index + with open(filename, "w") as fobj: + columns = list(sorted(self._dim1)) + for col in columns: + fobj.write(",") + fobj.write(str(remap_dim1[col] if remap_dim1 else col)) + fobj.write("\n") + for row in sorted(self._dim0): + fobj.write(str(remap_dim0[row] if remap_dim0 else row)) + for col in columns: + fobj.write(",") + fobj.write(str(self[row, col])) + fobj.write("\n") + + +class ClassificationBatches(object): + """Class which generates and stores classification batches. + + Each classification batch contains result of the classification of one + batch of adversarial images. + """ + + def __init__(self, datastore_client, storage_client, round_name): + """Initializes ClassificationBatches. + + Args: + datastore_client: instance of CompetitionDatastoreClient + storage_client: instance of CompetitionStorageClient + round_name: name of the round + """ + self._datastore_client = datastore_client + self._storage_client = storage_client + self._round_name = round_name + # Data is dict of dicts {CLASSIFICATION_BATCH_ID: { ... }} + self._data = {} + + def serialize(self, fobj): + """Serializes data stored in this class.""" + pickle.dump(self._data, fobj) + + def deserialize(self, fobj): + """Deserializes data from file into this class.""" + self._data = pickle.load(fobj) + + @property + def data(self): + """Returns dictionary with data.""" + return self._data + + def __getitem__(self, key): + """Returns one classification batch by given key.""" + return self._data[key] + + def init_from_adversarial_batches_write_to_datastore( + self, submissions, adv_batches + ): + """Populates data from adversarial batches and writes to datastore. + + Args: + submissions: instance of CompetitionSubmissions + adv_batches: instance of AversarialBatches + """ + # prepare classification batches + idx = 0 + for s_id in iterkeys(submissions.defenses): + for adv_id in iterkeys(adv_batches.data): + class_batch_id = CLASSIFICATION_BATCH_ID_PATTERN.format(idx) + idx += 1 + self.data[class_batch_id] = { + "adversarial_batch_id": adv_id, + "submission_id": s_id, + "result_path": os.path.join( + self._round_name, + CLASSIFICATION_BATCHES_SUBDIR, + s_id + "_" + adv_id + ".csv", + ), + } + # save them to datastore + client = self._datastore_client + with client.no_transact_batch() as batch: + for key, value in iteritems(self.data): + entity = client.entity(client.key(KIND_CLASSIFICATION_BATCH, key)) + entity.update(value) + batch.put(entity) + + def init_from_datastore(self): + """Initializes data by reading it from the datastore.""" + self._data = {} + client = self._datastore_client + for entity in client.query_fetch(kind=KIND_CLASSIFICATION_BATCH): + class_batch_id = entity.key.flat_path[-1] + self.data[class_batch_id] = dict(entity) + + def read_batch_from_datastore(self, class_batch_id): + """Reads and returns single batch from the datastore.""" + client = self._datastore_client + key = client.key(KIND_CLASSIFICATION_BATCH, class_batch_id) + result = client.get(key) + if result is not None: + return dict(result) + else: + raise KeyError("Key {0} not found in the datastore".format(key.flat_path)) + + def compute_classification_results( + self, adv_batches, dataset_batches, dataset_meta, defense_work=None + ): + """Computes classification results. + + Args: + adv_batches: instance of AversarialBatches + dataset_batches: instance of DatasetBatches + dataset_meta: instance of DatasetMetadata + defense_work: instance of DefenseWorkPieces + + Returns: + accuracy_matrix, error_matrix, hit_target_class_matrix, + processed_images_count + """ + class_batch_to_work = {} + if defense_work: + for v in itervalues(defense_work.work): + class_batch_to_work[v["output_classification_batch_id"]] = v + + # accuracy_matrix[defense_id, attack_id] = num correctly classified + accuracy_matrix = ResultMatrix() + # error_matrix[defense_id, attack_id] = num misclassfied + error_matrix = ResultMatrix() + # hit_target_class_matrix[defense_id, attack_id] = num hit target class + hit_target_class_matrix = ResultMatrix() + # processed_images_count[defense_id] = num processed images by defense + processed_images_count = {} + + total_count = len(self.data) + processed_count = 0 + logging.info("Processing %d files with classification results", len(self.data)) + for k, v in iteritems(self.data): + if processed_count % 100 == 0: + logging.info( + "Processed %d out of %d classification results", + processed_count, + total_count, + ) + processed_count += 1 + defense_id = v["submission_id"] + adv_batch = adv_batches.data[v["adversarial_batch_id"]] + attack_id = adv_batch["submission_id"] + + work_item = class_batch_to_work.get(k) + required_work_stats = [ + "stat_correct", + "stat_error", + "stat_target_class", + "stat_num_images", + ] + if work_item and work_item["error"]: + # ignore batches with error + continue + if work_item and all( + work_item.get(i) is not None for i in required_work_stats + ): + count_correctly_classified = work_item["stat_correct"] + count_errors = work_item["stat_error"] + count_hit_target_class = work_item["stat_target_class"] + num_images = work_item["stat_num_images"] + else: + logging.warning("Recomputing accuracy for classification batch %s", k) + ( + count_correctly_classified, + count_errors, + count_hit_target_class, + num_images, + ) = analyze_one_classification_result( + self._storage_client, + v["result_path"], + adv_batch, + dataset_batches, + dataset_meta, + ) + + # update accuracy and hit target class + accuracy_matrix[defense_id, attack_id] += count_correctly_classified + error_matrix[defense_id, attack_id] += count_errors + hit_target_class_matrix[defense_id, attack_id] += count_hit_target_class + # update number of processed images + processed_images_count[defense_id] = ( + processed_images_count.get(defense_id, 0) + num_images + ) + return ( + accuracy_matrix, + error_matrix, + hit_target_class_matrix, + processed_images_count, + ) + + def __str__(self): + """Returns human readable string representation, useful for debugging.""" + buf = StringIO() + for idx, (class_batch_id, class_val) in enumerate(iteritems(self.data)): + if idx >= TO_STR_MAX_BATCHES: + buf.write(u" ...\n") + break + buf.write(u' ClassBatch "{0}"\n'.format(class_batch_id)) + buf.write(u" {0}\n".format(str(class_val))) + return buf.getvalue() diff --git a/cleverhans_v3.1.0/examples/nips17_adversarial_competition/eval_infra/code/eval_lib/cloud_client.py b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/eval_infra/code/eval_lib/cloud_client.py new file mode 100644 index 000000000..0e5ecba80 --- /dev/null +++ b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/eval_infra/code/eval_lib/cloud_client.py @@ -0,0 +1,250 @@ +"""Helper classes and wrappers to access Google Cloud. + +Google Cloud API is encapsulated with these wrappers, so it's easier to +test the code with help of fake (declared in testing/fake_cloud_client.py). +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import logging +import math +import random +import time +import traceback as tb + +from google.cloud import datastore +from google.cloud import storage +from google.cloud.exceptions import TooManyRequests + +# To resolve InsecurePlatformWarning +try: + import urllib3.contrib.pyopenssl + + urllib3.contrib.pyopenssl.inject_into_urllib3() + print("Pyopenssl fix for urllib3 succesfully injected.") +except ImportError: + print("Failed to inject pyopenssl fix for urllib3.") + +# Cloud Datastore has 500 mutations per batch limit. +MAX_MUTATIONS_IN_BATCH = 500 + + +class CompetitionStorageClient(object): + """Client wrapper to access Google Cloud Storage.""" + + def __init__(self, project_id, bucket_name): + """Initialize client with project id and name of the storage bucket.""" + self.project_id = project_id + self.bucket_name = bucket_name + self.client = storage.Client(project=project_id) + self.bucket = self.client.get_bucket(bucket_name) + + def list_blobs(self, prefix=""): + """Lists names of all blobs by their prefix.""" + return [b.name for b in self.bucket.list_blobs(prefix=prefix)] + + def get_blob(self, blob_name): + """Gets google.cloud.storage.blob.Blob object by blob name.""" + return self.bucket.get_blob(blob_name) + + def new_blob(self, blob_name): + """Creates new storage blob with provided name.""" + return storage.Blob(blob_name, self.bucket) + + +class NoTransactionBatch(object): + """No transaction batch to write large number of entities. + + Usage: + client = ... # instance of CompetitionDatastoreClient + with NoTransactionBatch(client) as batch: + batch.put(entity1) + ... + batch.put(entityN) + batch.delete(del_entity1) + ... + batch.delete(del_entityM) + + It could be also used via CompetitionDatastoreClient.no_transact_batch: + client = ... # instance of CompetitionDatastoreClient + with client.no_transact_batch() as batch: + batch.put(entity1) + ... + batch.put(entityN) + batch.delete(del_entity1) + ... + batch.delete(del_entityM) + + Most methods of this class are provided to simulate + google.cloud.datastore.batch.Batch interface, so they could be used + interchangeably. + Practically speaking, this class works by maintaining a buffer of + pending mutations and committing them as soon as the length of the buffer + reaches MAX_MUTATIONS_IN_BATCH. + """ + + def __init__(self, client): + """Init NoTransactionBatch with provided CompetitionDatastoreClient.""" + self._client = client + self._cur_batch = None + self._num_mutations = 0 + + def begin(self): + """Begins a batch.""" + if self._cur_batch: + raise ValueError("Previous batch is not committed.") + self._cur_batch = self._client.batch() + self._cur_batch.begin() + self._num_mutations = 0 + + def commit(self): + """Commits all pending mutations.""" + self._cur_batch.commit() + self._cur_batch = None + self._num_mutations = 0 + + def rollback(self): + """Rolls back pending mutations. + + Keep in mind that NoTransactionBatch splits all mutations into smaller + batches and commit them as soon as mutation buffer reaches maximum length. + That's why rollback method will only roll back pending mutations from the + buffer, but won't be able to rollback already committed mutations. + """ + try: + if self._cur_batch: + self._cur_batch.rollback() + except ValueError: + # ignore "Batch must be in progress to rollback" error + pass + self._cur_batch = None + self._num_mutations = 0 + + def put(self, entity): + """Adds mutation of the entity to the mutation buffer. + + If mutation buffer reaches its capacity then this method commit all pending + mutations from the buffer and emties it. + + Args: + entity: entity which should be put into the datastore + """ + self._cur_batch.put(entity) + self._num_mutations += 1 + if self._num_mutations >= MAX_MUTATIONS_IN_BATCH: + self.commit() + self.begin() + + def delete(self, key): + """Adds deletion of the entity with given key to the mutation buffer. + + If mutation buffer reaches its capacity then this method commit all pending + mutations from the buffer and emties it. + + Args: + key: key of the entity which should be deleted + """ + self._cur_batch.delete(key) + self._num_mutations += 1 + if self._num_mutations >= MAX_MUTATIONS_IN_BATCH: + self.commit() + self.begin() + + def __enter__(self): + self.begin() + return self + + def __exit__(self, exc_type, exc_value, traceback): + if exc_type is None: + self.commit() + else: + err = tb.format_exception(exc_type, exc_value, traceback) + logging.error("Exception occurred during write:\n%s", err) + self.rollback() + + +def iterate_with_exp_backoff( + base_iter, + max_num_tries=6, + max_backoff=300.0, + start_backoff=4.0, + backoff_multiplier=2.0, + frac_random_backoff=0.25, +): + """Iterate with exponential backoff on failures. + + Useful to wrap results of datastore Query.fetch to avoid 429 error. + + Args: + base_iter: basic iterator of generator object + max_num_tries: maximum number of tries for each request + max_backoff: maximum backoff, in seconds + start_backoff: initial value of backoff + backoff_multiplier: backoff multiplier + frac_random_backoff: fraction of the value of random part of the backoff + + Yields: + values of yielded by base iterator + """ + try_number = 0 + if hasattr(base_iter, "__iter__"): + base_iter = iter(base_iter) + while True: + try: + yield next(base_iter) + try_number = 0 + except StopIteration: + break + except TooManyRequests as e: + logging.warning("TooManyRequests error: %s", tb.format_exc()) + if try_number >= max_num_tries: + logging.error("Number of tries exceeded, too many requests: %s", e) + raise + # compute sleep time for truncated exponential backoff + sleep_time = start_backoff * math.pow(backoff_multiplier, try_number) + sleep_time *= 1.0 + frac_random_backoff * random.random() + sleep_time = min(sleep_time, max_backoff) + logging.warning( + "Too many requests error, " "retrying with exponential backoff %.3f", + sleep_time, + ) + time.sleep(sleep_time) + try_number += 1 + + +class CompetitionDatastoreClient(object): + """Client wrapper to access Google Cloud Datastore.""" + + def __init__(self, project_id, namespace=None): + """Init this method with given project id and optional namespace.""" + self._client = datastore.Client(project=project_id, namespace=namespace) + + def key(self, *args, **kwargs): + """Creates datastore key.""" + return self._client.key(*args, **kwargs) + + def entity(self, key): + """Creates datastore entity.""" + return datastore.Entity(key) + + def no_transact_batch(self): + """Starts batch of mutation which is committed without transaction.""" + return NoTransactionBatch(self._client) + + def batch(self): + """Starts batch of mutations.""" + return self._client.batch() + + def transaction(self): + """Starts transaction.""" + return self._client.transaction() + + def get(self, key, transaction=None): + """Retrieves an entity given its key.""" + return self._client.get(key, transaction=transaction) + + def query_fetch(self, **kwargs): + """Queries datastore (using exponential backoff).""" + return iterate_with_exp_backoff(self._client.query(**kwargs).fetch()) diff --git a/cleverhans_v3.1.0/examples/nips17_adversarial_competition/eval_infra/code/eval_lib/dataset_helper.py b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/eval_infra/code/eval_lib/dataset_helper.py new file mode 100644 index 000000000..6fb5aba3a --- /dev/null +++ b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/eval_infra/code/eval_lib/dataset_helper.py @@ -0,0 +1,158 @@ +"""Various helpers for the dataset. +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import csv +import hashlib +import logging +import os +import shutil +import subprocess + +import numpy as np +from PIL import Image + +from six import iteritems + + +class DatasetMetadata(object): + """Helper class which loads and stores dataset metadata. + + Dataset metadata stored by this class contains true labels and target classes + for all images in the dataset. + """ + + def __init__(self, fobj): + """Initializes instance of DatasetMetadata. + + Args: + fobj: file object + """ + self._true_labels = {} + self._target_classes = {} + reader = csv.reader(fobj) + header_row = next(reader) + try: + row_idx_image_id = header_row.index("ImageId") + row_idx_true_label = header_row.index("TrueLabel") + row_idx_target_class = header_row.index("TargetClass") + except ValueError: + raise IOError("Invalid format of the dataset metadata.") + for row in reader: + if len(row) < len(header_row): + # skip partial or empty lines + continue + try: + image_id = row[row_idx_image_id] + self._true_labels[image_id] = int(row[row_idx_true_label]) + self._target_classes[image_id] = int(row[row_idx_target_class]) + except (IndexError, ValueError): + raise IOError("Invalid format of dataset metadata") + + def get_true_label(self, image_id): + """Returns true label for image with given ID.""" + return self._true_labels[image_id] + + def get_target_class(self, image_id): + """Returns target class for image with given ID.""" + return self._target_classes[image_id] + + def save_target_classes_for_batch(self, filename, image_batches, batch_id): + """Saves file with target class for given dataset batch. + + Args: + filename: output filename + image_batches: instance of ImageBatchesBase with dataset batches + batch_id: dataset batch ID + """ + images = image_batches.data[batch_id]["images"] + with open(filename, "w") as f: + for image_id, image_val in iteritems(images): + target_class = self.get_target_class(image_val["dataset_image_id"]) + f.write("{0}.png,{1}\n".format(image_id, target_class)) + + +def enforce_epsilon_and_compute_hash(dataset_batch_dir, adv_dir, output_dir, epsilon): + """Enforces size of perturbation on images, and compute hashes for all images. + + Args: + dataset_batch_dir: directory with the images of specific dataset batch + adv_dir: directory with generated adversarial images + output_dir: directory where to copy result + epsilon: size of perturbation + + Returns: + dictionary with mapping form image ID to hash. + """ + dataset_images = [f for f in os.listdir(dataset_batch_dir) if f.endswith(".png")] + image_hashes = {} + resize_warning = False + for img_name in dataset_images: + if not os.path.exists(os.path.join(adv_dir, img_name)): + logging.warning("Image %s not found in the output", img_name) + continue + image = np.array( + Image.open(os.path.join(dataset_batch_dir, img_name)).convert("RGB") + ) + image = image.astype("int32") + image_max_clip = np.clip(image + epsilon, 0, 255).astype("uint8") + image_min_clip = np.clip(image - epsilon, 0, 255).astype("uint8") + # load and resize adversarial image if needed + adv_image = Image.open(os.path.join(adv_dir, img_name)).convert("RGB") + # Image.size is reversed compared to np.array.shape + if adv_image.size[::-1] != image.shape[:2]: + resize_warning = True + adv_image = adv_image.resize( + (image.shape[1], image.shape[0]), Image.BICUBIC + ) + adv_image = np.array(adv_image) + clipped_adv_image = np.clip(adv_image, image_min_clip, image_max_clip) + Image.fromarray(clipped_adv_image).save(os.path.join(output_dir, img_name)) + # compute hash + image_hashes[img_name[:-4]] = hashlib.sha1( + clipped_adv_image.view(np.uint8) + ).hexdigest() + if resize_warning: + logging.warning("One or more adversarial images had incorrect size") + return image_hashes + + +def download_dataset( + storage_client, image_batches, target_dir, local_dataset_copy=None +): + """Downloads dataset, organize it by batches and rename images. + + Args: + storage_client: instance of the CompetitionStorageClient + image_batches: subclass of ImageBatchesBase with data about images + target_dir: target directory, should exist and be empty + local_dataset_copy: directory with local dataset copy, if local copy is + available then images will be takes from there instead of Cloud Storage + + Data in the target directory will be organized into subdirectories by batches, + thus path to each image will be "target_dir/BATCH_ID/IMAGE_ID.png" + where BATCH_ID - ID of the batch (key of image_batches.data), + IMAGE_ID - ID of the image (key of image_batches.data[batch_id]['images']) + """ + for batch_id, batch_value in iteritems(image_batches.data): + batch_dir = os.path.join(target_dir, batch_id) + os.mkdir(batch_dir) + for image_id, image_val in iteritems(batch_value["images"]): + dst_filename = os.path.join(batch_dir, image_id + ".png") + # try to use local copy first + if local_dataset_copy: + local_filename = os.path.join( + local_dataset_copy, os.path.basename(image_val["image_path"]) + ) + if os.path.exists(local_filename): + shutil.copyfile(local_filename, dst_filename) + continue + # download image from cloud + cloud_path = ( + "gs://" + storage_client.bucket_name + "/" + image_val["image_path"] + ) + if not os.path.exists(dst_filename): + subprocess.call(["gsutil", "cp", cloud_path, dst_filename]) diff --git a/cleverhans_v3.1.0/examples/nips17_adversarial_competition/eval_infra/code/eval_lib/image_batches.py b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/eval_infra/code/eval_lib/image_batches.py new file mode 100644 index 000000000..ee9eee6ab --- /dev/null +++ b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/eval_infra/code/eval_lib/image_batches.py @@ -0,0 +1,322 @@ +"""Module with classes to read and store image batches. +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import copy +from io import BytesIO +from io import StringIO +import itertools +import logging +import os +import zipfile + +from six import iteritems +from six import iterkeys +from six import itervalues + + +# Cloud Datastore constants +KIND_DATASET_BATCH = u"DatasetBatch" +KIND_DATASET_IMAGE = u"DatasetImage" +KIND_ADVERSARIAL_BATCH = u"AdversarialBatch" +KIND_ADVERSARIAL_IMAGE = u"AdversarialImage" + +# Cloud Datastore ID patterns +DATASET_BATCH_ID_PATTERN = u"BATCH{:03}" +DATASET_IMAGE_ID_PATTERN = u"IMG{:06}" +ADVERSARIAL_BATCH_ID_PATTERN = u"ADVBATCH{:03}" +ADVERSARIAL_IMAGE_ID_PATTERN = u"ADV{:06}" + +# Default list of possible adversarial peturbations +DEFAULT_EPSILON = [4, 8, 12, 16] + +# Constants for __str__ +TO_STR_MAX_BATCHES = 5 +TO_STR_MAX_IMAGES_PER_BATCH = 10 + + +class ImageBatchesBase(object): + """Base class to store image batches. + + Subclasses of this class are used to store batches of images from the dataset + or batches of adversarial images. + """ + + def __init__(self, datastore_client, entity_kind_batches, entity_kind_images): + """Initialize ImageBatchesBase. + + Args: + datastore_client: instance of the CompetitionDatastoreClient + entity_kind_batches: Cloud Datastore entity kind which is used to store + batches of images. + entity_kind_images: Cloud Datastore entity kind which is used to store + individual images. + """ + self._datastore_client = datastore_client + self._entity_kind_batches = entity_kind_batches + self._entity_kind_images = entity_kind_images + # data is a dictionary with following structure: + # self._data[batch_id] = { + # 'batch_k1': batch_v1, + # ... + # 'batch_kN': batch_vN, + # 'images': { + # image_id: { 'img_k1': img_v1, ... } + # } + # } + self._data = {} + + def _write_single_batch_images_internal(self, batch_id, client_batch): + """Helper method to write images from single batch into datastore.""" + client = self._datastore_client + batch_key = client.key(self._entity_kind_batches, batch_id) + for img_id, img in iteritems(self._data[batch_id]["images"]): + img_entity = client.entity( + client.key(self._entity_kind_images, img_id, parent=batch_key) + ) + for k, v in iteritems(img): + img_entity[k] = v + client_batch.put(img_entity) + + def write_to_datastore(self): + """Writes all image batches to the datastore.""" + client = self._datastore_client + with client.no_transact_batch() as client_batch: + for batch_id, batch_data in iteritems(self._data): + batch_key = client.key(self._entity_kind_batches, batch_id) + batch_entity = client.entity(batch_key) + for k, v in iteritems(batch_data): + if k != "images": + batch_entity[k] = v + client_batch.put(batch_entity) + self._write_single_batch_images_internal(batch_id, client_batch) + + def write_single_batch_images_to_datastore(self, batch_id): + """Writes only images from one batch to the datastore.""" + client = self._datastore_client + with client.no_transact_batch() as client_batch: + self._write_single_batch_images_internal(batch_id, client_batch) + + def init_from_datastore(self): + """Initializes batches by reading from the datastore.""" + self._data = {} + for entity in self._datastore_client.query_fetch( + kind=self._entity_kind_batches + ): + batch_id = entity.key.flat_path[-1] + self._data[batch_id] = dict(entity) + self._data[batch_id]["images"] = {} + for entity in self._datastore_client.query_fetch(kind=self._entity_kind_images): + batch_id = entity.key.flat_path[-3] + image_id = entity.key.flat_path[-1] + self._data[batch_id]["images"][image_id] = dict(entity) + + @property + def data(self): + """Dictionary with data.""" + return self._data + + def __getitem__(self, key): + """Returns specific batch by its key.""" + return self._data[key] + + def add_batch(self, batch_id, batch_properties=None): + """Adds batch with give ID and list of properties.""" + if batch_properties is None: + batch_properties = {} + if not isinstance(batch_properties, dict): + raise ValueError( + "batch_properties has to be dict, however it was: " + + str(type(batch_properties)) + ) + self._data[batch_id] = batch_properties.copy() + self._data[batch_id]["images"] = {} + + def add_image(self, batch_id, image_id, image_properties=None): + """Adds image to given batch.""" + if batch_id not in self._data: + raise KeyError('Batch with ID "{0}" does not exist'.format(batch_id)) + if image_properties is None: + image_properties = {} + if not isinstance(image_properties, dict): + raise ValueError( + "image_properties has to be dict, however it was: " + + str(type(image_properties)) + ) + self._data[batch_id]["images"][image_id] = image_properties.copy() + + def count_num_images(self): + """Counts total number of images in all batches.""" + return sum([len(v["images"]) for v in itervalues(self.data)]) + + def __str__(self): + """Returns human readable representation, which is useful for debugging.""" + buf = StringIO() + for batch_idx, (batch_id, batch_val) in enumerate(iteritems(self.data)): + if batch_idx >= TO_STR_MAX_BATCHES: + buf.write(u"...\n") + break + buf.write(u'BATCH "{0}"\n'.format(batch_id)) + for k, v in iteritems(batch_val): + if k != "images": + buf.write(u" {0}: {1}\n".format(k, v)) + for img_idx, img_id in enumerate(iterkeys(batch_val["images"])): + if img_idx >= TO_STR_MAX_IMAGES_PER_BATCH: + buf.write(u" ...") + break + buf.write( + u' IMAGE "{0}" -- {1}\n'.format( + img_id, batch_val["images"][img_id] + ) + ) + buf.write(u"\n") + return buf.getvalue() + + +class DatasetBatches(ImageBatchesBase): + """Class which stores batches of images from the dataset.""" + + def __init__(self, datastore_client, storage_client, dataset_name): + """Initializes DatasetBatches. + + Args: + datastore_client: instance of CompetitionDatastoreClient + storage_client: instance of CompetitionStorageClient + dataset_name: name of the dataset ('dev' or 'final') + """ + super(DatasetBatches, self).__init__( + datastore_client=datastore_client, + entity_kind_batches=KIND_DATASET_BATCH, + entity_kind_images=KIND_DATASET_IMAGE, + ) + self._storage_client = storage_client + self._dataset_name = dataset_name + + def _read_image_list(self, skip_image_ids=None): + """Reads list of dataset images from the datastore.""" + if skip_image_ids is None: + skip_image_ids = [] + images = self._storage_client.list_blobs( + prefix=os.path.join("dataset", self._dataset_name) + "/" + ) + zip_files = [i for i in images if i.endswith(".zip")] + if len(zip_files) == 1: + # we have a zip archive with images + zip_name = zip_files[0] + logging.info("Reading list of images from zip file %s", zip_name) + blob = self._storage_client.get_blob(zip_name) + buf = BytesIO() + logging.info("Downloading zip") + blob.download_to_file(buf) + buf.seek(0) + logging.info("Reading content of the zip") + with zipfile.ZipFile(buf) as f: + images = [ + os.path.join(zip_name, os.path.basename(n)) + for n in f.namelist() + if n.endswith(".png") + ] + buf.close() + logging.info("Found %d images", len(images)) + else: + # we have just a directory with images, filter non-PNG files + logging.info("Reading list of images from png files in storage") + images = [i for i in images if i.endswith(".png")] + logging.info("Found %d images", len(images)) + # filter images which should be skipped + images = [i for i in images if os.path.basename(i)[:-4] not in skip_image_ids] + # assign IDs to images + images = [ + (DATASET_IMAGE_ID_PATTERN.format(idx), i) + for idx, i in enumerate(sorted(images)) + ] + return images + + def init_from_storage_write_to_datastore( + self, + batch_size=100, + allowed_epsilon=None, + skip_image_ids=None, + max_num_images=None, + ): + """Initializes dataset batches from the list of images in the datastore. + + Args: + batch_size: batch size + allowed_epsilon: list of allowed epsilon or None to use default + skip_image_ids: list of image ids to skip + max_num_images: maximum number of images to read + """ + if allowed_epsilon is None: + allowed_epsilon = copy.copy(DEFAULT_EPSILON) + # init dataset batches from data in storage + self._dataset_batches = {} + # read all blob names from storage + images = self._read_image_list(skip_image_ids) + if max_num_images: + images = images[:max_num_images] + for batch_idx, batch_start in enumerate(range(0, len(images), batch_size)): + batch = images[batch_start : batch_start + batch_size] + batch_id = DATASET_BATCH_ID_PATTERN.format(batch_idx) + batch_epsilon = allowed_epsilon[batch_idx % len(allowed_epsilon)] + self.add_batch(batch_id, {"epsilon": batch_epsilon}) + for image_id, image_path in batch: + self.add_image( + batch_id, + image_id, + { + "dataset_image_id": os.path.basename(image_path)[:-4], + "image_path": image_path, + }, + ) + # write data to datastore + self.write_to_datastore() + + +class AversarialBatches(ImageBatchesBase): + """Class which stores batches of adversarial images generated by attacks.""" + + def __init__(self, datastore_client): + """Initializes AversarialBatches. + + Args: + datastore_client: instance of CompetitionDatastoreClient + """ + super(AversarialBatches, self).__init__( + datastore_client=datastore_client, + entity_kind_batches=KIND_ADVERSARIAL_BATCH, + entity_kind_images=KIND_ADVERSARIAL_IMAGE, + ) + + def init_from_dataset_and_submissions_write_to_datastore( + self, dataset_batches, attack_submission_ids + ): + """Init list of adversarial batches from dataset batches and submissions. + + Args: + dataset_batches: instances of DatasetBatches + attack_submission_ids: iterable with IDs of all (targeted and nontargeted) + attack submissions, could be obtains as + CompetitionSubmissions.get_all_attack_ids() + """ + batches_x_attacks = itertools.product( + dataset_batches.data.keys(), attack_submission_ids + ) + for idx, (dataset_batch_id, attack_id) in enumerate(batches_x_attacks): + adv_batch_id = ADVERSARIAL_BATCH_ID_PATTERN.format(idx) + self.add_batch( + adv_batch_id, + {"dataset_batch_id": dataset_batch_id, "submission_id": attack_id}, + ) + self.write_to_datastore() + + def count_generated_adv_examples(self): + """Returns total number of all generated adversarial examples.""" + result = {} + for v in itervalues(self.data): + s_id = v["submission_id"] + result[s_id] = result.get(s_id, 0) + len(v["images"]) + return result diff --git a/cleverhans_v3.1.0/examples/nips17_adversarial_competition/eval_infra/code/eval_lib/submissions.py b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/eval_infra/code/eval_lib/submissions.py new file mode 100644 index 000000000..3b48b2589 --- /dev/null +++ b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/eval_infra/code/eval_lib/submissions.py @@ -0,0 +1,256 @@ +"""Classes and functions to manage submissions. +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from collections import namedtuple +from io import StringIO +import os +from six import iteritems + +# Cloud Storage directories +ATTACK_SUBDIR = "submissions/nontargeted" +TARGETED_ATTACK_SUBDIR = "submissions/targeted" +DEFENSE_SUBDIR = "submissions/defense" + +# Cloud Datastore entity keys +ATTACKS_ENTITY_KEY = [u"SubmissionType", u"Attacks"] +TARGET_ATTACKS_ENTITY_KEY = [u"SubmissionType", u"TargetedAttacks"] +DEFENSES_ENTITY_KEY = [u"SubmissionType", u"Defenses"] +KIND_SUBMISSION = u"Submission" + +# Cloud Datastore ID patterns +ATTACK_ID_PATTERN = u"SUBA{:03}" +TARGETED_ATTACK_ID_PATTERN = u"SUBT{:03}" +DEFENSE_ID_PATTERN = u"SUBD{:03}" + +# Constants for __str__ +TO_STR_MAX_SUBMISSIONS = 5 + +ALLOWED_EXTENSIONS = [".zip", ".tar", ".tar.gz"] + + +def participant_from_submission_path(submission_path): + """Parses type of participant based on submission filename. + + Args: + submission_path: path to the submission in Google Cloud Storage + + Returns: + dict with one element. Element key correspond to type of participant + (team, baseline), element value is ID of the participant. + + Raises: + ValueError: is participant can't be determined based on submission path. + """ + basename = os.path.basename(submission_path) + file_ext = None + for e in ALLOWED_EXTENSIONS: + if basename.endswith(e): + file_ext = e + break + if not file_ext: + raise ValueError("Invalid submission path: " + submission_path) + basename = basename[: -len(file_ext)] + if basename.isdigit(): + return {"team_id": int(basename)} + if basename.startswith("baseline_"): + return {"baseline_id": basename[len("baseline_") :]} + raise ValueError("Invalid submission path: " + submission_path) + + +SubmissionDescriptor = namedtuple("SubmissionDescriptor", ["path", "participant_id"]) + + +class CompetitionSubmissions(object): + """Class which holds information about all submissions. + + All submissions are stored in 3 dictionaries, one for targeted attacks, + one for non-targeted attacks and one for defenses. + All submissions are identified using internal competition ID, + which looks like 'SUB????'. Additionally each submission has external + identified which could be name of baseline or Kaggle ID. + External ID only used when list of submissions is formed and when + scorebored is built. Internal submission IDs are used for all actual + evaluation. Thus all identifiers are internal IDs unless otherwise noted. + """ + + def __init__(self, datastore_client, storage_client, round_name): + """Initializes CompetitionSubmissions. + + Args: + datastore_client: instance of CompetitionDatastoreClient + storage_client: instance of CompetitionStorageClient + round_name: name of the round + """ + self._datastore_client = datastore_client + self._storage_client = storage_client + self._round_name = round_name + # each of the variables is a dictionary, + # where key - submission ID + # value - SubmissionDescriptor namedtuple + self._attacks = None + self._targeted_attacks = None + self._defenses = None + + def _load_submissions_from_datastore_dir(self, dir_suffix, id_pattern): + """Loads list of submissions from the directory. + + Args: + dir_suffix: suffix of the directory where submissions are stored, + one of the folowing constants: ATTACK_SUBDIR, TARGETED_ATTACK_SUBDIR + or DEFENSE_SUBDIR. + id_pattern: pattern which is used to generate (internal) IDs + for submissins. One of the following constants: ATTACK_ID_PATTERN, + TARGETED_ATTACK_ID_PATTERN or DEFENSE_ID_PATTERN. + + Returns: + dictionary with all found submissions + """ + submissions = self._storage_client.list_blobs( + prefix=os.path.join(self._round_name, dir_suffix) + ) + return { + id_pattern.format(idx): SubmissionDescriptor( + path=s, participant_id=participant_from_submission_path(s) + ) + for idx, s in enumerate(submissions) + } + + def init_from_storage_write_to_datastore(self): + """Init list of sumibssions from Storage and saves them to Datastore. + + Should be called only once (typically by master) during evaluation of + the competition. + """ + # Load submissions + self._attacks = self._load_submissions_from_datastore_dir( + ATTACK_SUBDIR, ATTACK_ID_PATTERN + ) + self._targeted_attacks = self._load_submissions_from_datastore_dir( + TARGETED_ATTACK_SUBDIR, TARGETED_ATTACK_ID_PATTERN + ) + self._defenses = self._load_submissions_from_datastore_dir( + DEFENSE_SUBDIR, DEFENSE_ID_PATTERN + ) + self._write_to_datastore() + + def _write_to_datastore(self): + """Writes all submissions to datastore.""" + # Populate datastore + roots_and_submissions = zip( + [ATTACKS_ENTITY_KEY, TARGET_ATTACKS_ENTITY_KEY, DEFENSES_ENTITY_KEY], + [self._attacks, self._targeted_attacks, self._defenses], + ) + client = self._datastore_client + with client.no_transact_batch() as batch: + for root_key, submissions in roots_and_submissions: + batch.put(client.entity(client.key(*root_key))) + for k, v in iteritems(submissions): + entity = client.entity( + client.key(*(root_key + [KIND_SUBMISSION, k])) + ) + entity["submission_path"] = v.path + entity.update(participant_from_submission_path(v.path)) + batch.put(entity) + + def init_from_datastore(self): + """Init list of submission from Datastore. + + Should be called by each worker during initialization. + """ + self._attacks = {} + self._targeted_attacks = {} + self._defenses = {} + for entity in self._datastore_client.query_fetch(kind=KIND_SUBMISSION): + submission_id = entity.key.flat_path[-1] + submission_path = entity["submission_path"] + participant_id = { + k: entity[k] for k in ["team_id", "baseline_id"] if k in entity + } + submission_descr = SubmissionDescriptor( + path=submission_path, participant_id=participant_id + ) + if list(entity.key.flat_path[0:2]) == ATTACKS_ENTITY_KEY: + self._attacks[submission_id] = submission_descr + elif list(entity.key.flat_path[0:2]) == TARGET_ATTACKS_ENTITY_KEY: + self._targeted_attacks[submission_id] = submission_descr + elif list(entity.key.flat_path[0:2]) == DEFENSES_ENTITY_KEY: + self._defenses[submission_id] = submission_descr + + @property + def attacks(self): + """Dictionary with all non-targeted attacks.""" + return self._attacks + + @property + def targeted_attacks(self): + """Dictionary with all targeted attacks.""" + return self._targeted_attacks + + @property + def defenses(self): + """Dictionary with all defenses.""" + return self._defenses + + def get_all_attack_ids(self): + """Returns IDs of all attacks (targeted and non-targeted).""" + return list(self.attacks.keys()) + list(self.targeted_attacks.keys()) + + def find_by_id(self, submission_id): + """Finds submission by ID. + + Args: + submission_id: ID of the submission + + Returns: + SubmissionDescriptor with information about submission or None if + submission is not found. + """ + return self._attacks.get( + submission_id, + self._defenses.get( + submission_id, self._targeted_attacks.get(submission_id, None) + ), + ) + + def get_external_id(self, submission_id): + """Returns human readable submission external ID. + + Args: + submission_id: internal submission ID. + + Returns: + human readable ID. + """ + submission = self.find_by_id(submission_id) + if not submission: + return None + if "team_id" in submission.participant_id: + return submission.participant_id["team_id"] + elif "baseline_id" in submission.participant_id: + return "baseline_" + submission.participant_id["baseline_id"] + else: + return "" + + def __str__(self): + """Returns human readable representation, useful for debugging purposes.""" + buf = StringIO() + title_values = zip( + [u"Attacks", u"Targeted Attacks", u"Defenses"], + [self._attacks, self._targeted_attacks, self._defenses], + ) + for idx, (title, values) in enumerate(title_values): + if idx >= TO_STR_MAX_SUBMISSIONS: + buf.write("...\n") + break + buf.write(title) + buf.write(u":\n") + for k, v in iteritems(values): + buf.write( + u"{0} -- {1} {2}\n".format(k, v.path, str(v.participant_id)) + ) + buf.write(u"\n") + return buf.getvalue() diff --git a/cleverhans_v3.1.0/examples/nips17_adversarial_competition/eval_infra/code/eval_lib/tests/__init__.py b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/eval_infra/code/eval_lib/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/cleverhans_v3.1.0/examples/nips17_adversarial_competition/eval_infra/code/eval_lib/tests/classification_results_test.py b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/eval_infra/code/eval_lib/tests/classification_results_test.py new file mode 100644 index 000000000..4cfa08df6 --- /dev/null +++ b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/eval_infra/code/eval_lib/tests/classification_results_test.py @@ -0,0 +1,345 @@ +"""Tests for eval_lib.classification_results.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import unittest + +from six import assertCountEqual + +from eval_lib import classification_results +from eval_lib import image_batches +from eval_lib import submissions +from eval_lib import work_data +from eval_lib.tests import fake_cloud_client + + +ROUND_NAME = "round-name" + + +class FakeDatasetMeta(object): + """Fake for DatasetMetadata which alwasy returns constants.""" + + def get_true_label(self, _): + return 1 + + def get_target_class(self, _): + return 2 + + +class ClassificationResultsTest(unittest.TestCase): + def setUp(self): + self.storage_client = fake_cloud_client.FakeStorageClient() + self.datastore_client = fake_cloud_client.FakeDatastoreClient() + self.submissions = submissions.CompetitionSubmissions( + datastore_client=self.datastore_client, + storage_client=self.storage_client, + round_name=ROUND_NAME, + ) + # we only need list of submissin ids in CompetitionSubmissions for this test + self.submissions._defenses = { + "SUBD000": {}, + "SUBD001": {}, + } + self.adv_batches = image_batches.AversarialBatches( + datastore_client=self.datastore_client + ) + self.adv_batches._data = { + "ADVBATCH000": { + "dataset_batch_id": "BATCH000", + "images": {}, + "submission_id": "SUBA000", + }, + "ADVBATCH001": { + "dataset_batch_id": "BATCH000", + "images": {}, + "submission_id": "SUBA001", + }, + "ADVBATCH002": { + "dataset_batch_id": "BATCH000", + "images": {}, + "submission_id": "SUBT000", + }, + } + + def verify_classification_batches(self, class_batches): + assertCountEqual( + self, + [ + { + "adversarial_batch_id": "ADVBATCH000", + "submission_id": "SUBD000", + "result_path": ( + ROUND_NAME + "/classification_batches/SUBD000_ADVBATCH000.csv" + ), + }, + { + "adversarial_batch_id": "ADVBATCH000", + "submission_id": "SUBD001", + "result_path": ( + ROUND_NAME + "/classification_batches/SUBD001_ADVBATCH000.csv" + ), + }, + { + "adversarial_batch_id": "ADVBATCH001", + "submission_id": "SUBD000", + "result_path": ( + ROUND_NAME + "/classification_batches/SUBD000_ADVBATCH001.csv" + ), + }, + { + "adversarial_batch_id": "ADVBATCH001", + "submission_id": "SUBD001", + "result_path": ( + ROUND_NAME + "/classification_batches/SUBD001_ADVBATCH001.csv" + ), + }, + { + "adversarial_batch_id": "ADVBATCH002", + "submission_id": "SUBD000", + "result_path": ( + ROUND_NAME + "/classification_batches/SUBD000_ADVBATCH002.csv" + ), + }, + { + "adversarial_batch_id": "ADVBATCH002", + "submission_id": "SUBD001", + "result_path": ( + ROUND_NAME + "/classification_batches/SUBD001_ADVBATCH002.csv" + ), + }, + ], + class_batches.data.values(), + ) + + def test_init_from_adv_batches_and_submissions(self): + class_batches = classification_results.ClassificationBatches( + self.datastore_client, self.storage_client, ROUND_NAME + ) + class_batches.init_from_adversarial_batches_write_to_datastore( + self.submissions, self.adv_batches + ) + self.verify_classification_batches(class_batches) + class_batches = classification_results.ClassificationBatches( + self.datastore_client, self.storage_client, ROUND_NAME + ) + class_batches.init_from_datastore() + self.verify_classification_batches(class_batches) + + def test_read_batch_from_datastore(self): + class_batches = classification_results.ClassificationBatches( + self.datastore_client, self.storage_client, ROUND_NAME + ) + class_batches.init_from_adversarial_batches_write_to_datastore( + self.submissions, self.adv_batches + ) + class_batches = classification_results.ClassificationBatches( + self.datastore_client, self.storage_client, ROUND_NAME + ) + # read first batch from datastore and verify that only one batch was read + batch = class_batches.read_batch_from_datastore("CBATCH000000") + self.assertEqual(0, len(class_batches.data)) + assertCountEqual( + self, ["result_path", "adversarial_batch_id", "submission_id"], batch.keys() + ) + + def test_compute_classification_results_from_defense_work(self): + # Test computation of the results for the following case: + # - one dataset batch BATCH000 with 5 images + # - two defenses: SUBD000, SUBD001 + # - three attacks with corresponding adversarial batches: + # SUBA000 - ADVBATCH000 + # SUBA001 - ADVBATCH001 + # SUBT000 - ADVBATCH002 + # + # Results are following (correct/incorrect/hit tc/total adv img): + # | SUBD000 | SUBD001 | + # ----------+------------------+------------------+ + # SUBA000 | defense error | 3 / 1 / 0 / 4 | + # | WORK000 | WORK001 | + # ----------+------------------+------------------+ + # SUBA001 | 2 / 2 / 1 / 5 | 4 / 1 / 0 / 5 | + # | WORK002 | WORK003 | + # ----------+------------------+------------------+ + # SUBT000 | 1 / 4 / 4 / 5 | 3 / 2 / 1 / 5 | + # | WORK004 | WORK005 | + + class_batches = classification_results.ClassificationBatches( + self.datastore_client, self.storage_client, ROUND_NAME + ) + result_path_prefix = ROUND_NAME + "/classification_batches/" + class_batches._data = { + "CBATCH000000": { + "adversarial_batch_id": "ADVBATCH000", + "submission_id": "SUBD000", + "result_path": result_path_prefix + "SUBD000_ADVBATCH000.csv", + }, + "CBATCH000001": { + "adversarial_batch_id": "ADVBATCH000", + "submission_id": "SUBD001", + "result_path": result_path_prefix + "SUBD001_ADVBATCH000.csv", + }, + "CBATCH000002": { + "adversarial_batch_id": "ADVBATCH001", + "submission_id": "SUBD000", + "result_path": result_path_prefix + "SUBD000_ADVBATCH001.csv", + }, + "CBATCH000003": { + "adversarial_batch_id": "ADVBATCH001", + "submission_id": "SUBD001", + "result_path": result_path_prefix + "SUBD001_ADVBATCH001.csv", + }, + "CBATCH000004": { + "adversarial_batch_id": "ADVBATCH002", + "submission_id": "SUBD000", + "result_path": result_path_prefix + "SUBD000_ADVBATCH002.csv", + }, + "CBATCH000005": { + "adversarial_batch_id": "ADVBATCH002", + "submission_id": "SUBD001", + "result_path": result_path_prefix + "SUBD001_ADVBATCH002.csv", + }, + } + defense_work = work_data.DefenseWorkPieces(self.datastore_client) + defense_work._work = { + "WORK000": { + "output_classification_batch_id": "CBATCH000000", + "error": "error", + }, + "WORK001": { + "output_classification_batch_id": "CBATCH000001", + "stat_correct": 3, + "stat_error": 1, + "stat_target_class": 0, + "stat_num_images": 4, + "error": None, + }, + "WORK002": { + "output_classification_batch_id": "CBATCH000002", + "stat_correct": 2, + "stat_error": 2, + "stat_target_class": 1, + "stat_num_images": 5, + "error": None, + }, + "WORK003": { + "output_classification_batch_id": "CBATCH000003", + "stat_correct": 4, + "stat_error": 1, + "stat_target_class": 0, + "stat_num_images": 5, + "error": None, + }, + "WORK004": { + "output_classification_batch_id": "CBATCH000004", + "stat_correct": 1, + "stat_error": 4, + "stat_target_class": 4, + "stat_num_images": 5, + "error": None, + }, + "WORK005": { + "output_classification_batch_id": "CBATCH000005", + "stat_correct": 3, + "stat_error": 2, + "stat_target_class": 1, + "stat_num_images": 5, + "error": None, + }, + } + # Compute and verify results + ( + accuracy_matrix, + error_matrix, + hit_target_class_matrix, + processed_images_count, + ) = class_batches.compute_classification_results( + self.adv_batches, + dataset_batches=None, + dataset_meta=None, + defense_work=defense_work, + ) + self.assertDictEqual( + { + ("SUBD001", "SUBA000"): 3, + ("SUBD000", "SUBA001"): 2, + ("SUBD001", "SUBA001"): 4, + ("SUBD000", "SUBT000"): 1, + ("SUBD001", "SUBT000"): 3, + }, + accuracy_matrix._items, + ) + self.assertDictEqual( + { + ("SUBD001", "SUBA000"): 1, + ("SUBD000", "SUBA001"): 2, + ("SUBD001", "SUBA001"): 1, + ("SUBD000", "SUBT000"): 4, + ("SUBD001", "SUBT000"): 2, + }, + error_matrix._items, + ) + self.assertDictEqual( + { + ("SUBD001", "SUBA000"): 0, + ("SUBD000", "SUBA001"): 1, + ("SUBD001", "SUBA001"): 0, + ("SUBD000", "SUBT000"): 4, + ("SUBD001", "SUBT000"): 1, + }, + hit_target_class_matrix._items, + ) + self.assertDictEqual({"SUBD000": 10, "SUBD001": 14}, processed_images_count) + + def test_read_classification_results(self): + self.storage_client = fake_cloud_client.FakeStorageClient( + {"filename": "img1.png,123\nimg2.jpg,456"} + ) + results = classification_results.read_classification_results( + self.storage_client, "filename" + ) + self.assertDictEqual({"img1": 123, "img2": 456}, results) + + def test_analyze_one_classification_result(self): + self.storage_client = fake_cloud_client.FakeStorageClient( + {"filename": "a1.png,1\na2.png,4\na3.png,1\na4.png,1\na5.png,2\na6.png,9"} + ) + adv_batch = { + "dataset_batch_id": "BATCH000", + "images": { + "a" + str(i): {"clean_image_id": "c" + str(i)} for i in range(1, 6) + }, + } + dataset_batches = image_batches.DatasetBatches( + datastore_client=self.datastore_client, + storage_client=self.storage_client, + dataset_name="final", + ) + dataset_batches._data = { + "BATCH000": { + "images": { + "c" + str(i): {"dataset_image_id": str(i)} for i in range(1, 6) + } + }, + } + ( + count_correctly_classified, + count_errors, + count_hit_target_class, + num_images, + ) = classification_results.analyze_one_classification_result( + self.storage_client, + "filename", + adv_batch, + dataset_batches, + FakeDatasetMeta(), + ) + self.assertEqual(3, count_correctly_classified) + self.assertEqual(2, count_errors) + self.assertEqual(1, count_hit_target_class) + self.assertEqual(5, num_images) + + +if __name__ == "__main__": + unittest.main() diff --git a/cleverhans_v3.1.0/examples/nips17_adversarial_competition/eval_infra/code/eval_lib/tests/fake_cloud_client.py b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/eval_infra/code/eval_lib/tests/fake_cloud_client.py new file mode 100644 index 000000000..b017b9a97 --- /dev/null +++ b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/eval_infra/code/eval_lib/tests/fake_cloud_client.py @@ -0,0 +1,396 @@ +"""Library with fake Google Cloud client, used for testing of eval_lib. +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import copy +from io import StringIO +import six + + +class FakeBlob(object): + """Fake for google.cloud.storage.blob.Blob to be used in tests.""" + + def __init__(self, content): + """Initializes FakeBlob with given content.""" + if six.PY3 and isinstance(content, str): + self._content = content.encode() + else: + self._content = content + self.size = len(content) + + def download_to_file(self, fobj): + """Writes content of this blob into given file object.""" + fobj.write(self._content) + + +class FakeStorageClient(object): + """Fake for CompetitionStorageClient to be used in tests.""" + + def __init__(self, blobs=None): + """Inits FakeStorageClient with given blobs. + + Args: + blobs: either list of blob names or dict with mapping from blob names to + their content + + Raises: + TypeError: if blobs argument has invalid type + """ + if blobs is not None: + if isinstance(blobs, dict): + self._blobs = copy.deepcopy(blobs) + elif isinstance(blobs, list): + self._blobs = {k: "" for k in blobs} + else: + raise TypeError("Invalid type of blobs argument") + else: + self._blobs = {} + + def list_blobs(self, prefix=""): + """Lists names of all blobs by their prefix.""" + return [b for b in self._blobs.keys() if b.startswith(prefix)] + + def get_blob(self, blob_name): + """Gets google.cloud.storage.blob.Blob object by blob name.""" + if blob_name in self._blobs: + return FakeBlob(self._blobs[blob_name]) + else: + return None + + def new_blob(self, blob_name): + """Creates new storage blob with provided name.""" + del blob_name + raise NotImplementedError("new_blob is not implemented in fake client.") + + +class FakeDatastoreKey(object): + """Fake datastore key. + + Fake datastore key is represented as a list with flat path. + """ + + def __init__(self, *args, **kwargs): + if "parent" not in kwargs: + self._flat_path = args + else: + parent = kwargs["parent"] + if not isinstance(parent, FakeDatastoreKey): + raise ValueError("Invalid type of parent: " + str(type(parent))) + self._flat_path = parent.flat_path + args + + @property + def flat_path(self): + return self._flat_path + + def __hash__(self): + return hash(self._flat_path) + + def __eq__(self, other): + return isinstance(other, FakeDatastoreKey) and ( + self.flat_path == other.flat_path + ) + + def __ne__(self, other): + return not self.__eq__(other) + + def __str__(self): + return self.__repr__() + + def __repr__(self): + return "".format(self._flat_path) + + +class FakeDatastoreEntity(dict): + """Fake Datstore Entity. + + Fake datastore entity is just a dict, which additionally has key property. + """ + + def __init__(self, key): + super(FakeDatastoreEntity, self).__init__() + if not isinstance(key, FakeDatastoreKey): + raise TypeError("Wrong type of key: " + str(type(key))) + self._key = key + + @property + def key(self): + return self._key + + def __eq__(self, other): + if not isinstance(other, FakeDatastoreEntity): + return False + return other.key == self.key and (set(self.items()) == set(other.items())) + + def __ne__(self, other): + return not self.__eq__(other) + + def __str__(self): + return self.__repr__() + + def __repr__(self): + return "".format( + self.key, super(FakeDatastoreEntity, self).__repr__() + ) + + +def make_entity(key): + """Helper method to make FakeDatastoreEntity. + + This method allows to path either tuple or FakeDatastoreKey as a key. + + Args: + key: entity key, either tuple or FakeDatastoreKey + + Returns: + Instance of FakeDatastoreEntity + """ + if isinstance(key, tuple): + key = FakeDatastoreKey(*key) + return FakeDatastoreEntity(key) + + +class FakeDatastoreClientBatch(object): + """Fake for NoTransactionBatch.""" + + def __init__(self, fake_datastore_client): + """Init FakeDatastoreClientBatch.""" + self._fake_datastore_client = fake_datastore_client + self._mutations = [] + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + if exc_type is None: + for m in self._mutations: + self._fake_datastore_client.put(m) + + def put(self, entity): + """Adds entity mutation to the batch.""" + assert isinstance(entity, FakeDatastoreEntity) + self._mutations.append(copy.deepcopy(entity)) + + +class FakeDatastoreClientTransaction(object): + """Fake for datastore transaction. + + See https://cloud.google.com/datastore/docs/concepts/transactions + for details of how transactions work in Cloud Datastore. + """ + + def __init__(self, fake_datastore_client): + """Init FakeDatastoreClientTransaction.""" + self._client = fake_datastore_client + # snapshot of the data in the fake datastore + self._data_snapshot = copy.deepcopy(fake_datastore_client.entities) + # transaction stated: 'init', 'started', 'committed', 'rolledback' + self._state = "init" + # list of mutations in this transactions in sequential order + # each mutation is instance of FakeDatastoreEntity + self._mutations = [] + # set of keys read in this transaction + self._read_keys = set() + + def _check_transaction_started(self): + """Helper method to check that transaction has been started.""" + if self._state != "started": + raise ValueError( + ("Invalid state of transaction, " "expected started, was %s") + % self._state + ) + + def _check_update_state(self, old_state, new_state): + """Checks old state and updates it to new state.""" + if self._state != old_state: + raise ValueError( + "Invalid state of transaction, expected %s, was %s" + % (old_state, self._state) + ) + self._state = new_state + + def begin(self): + """Begins transaction.""" + self._check_update_state("init", "started") + + def commit(self): + """Commits transaction.""" + self._check_transaction_started() + # before committing transaction verity that all entities which + # were read or updated in the transaction were not modified outside + # of transaction + touched_keys = self._read_keys | set([e.key for e in self._mutations]) + for k in touched_keys: + old_value = self._data_snapshot.get(k) + cur_value = self._client.entities.get(k) + if old_value != cur_value: + self.rollback() + raise Exception( + "Transaction can not be committed due to " + "conflicted updates in datastore." + ) + # commit all changes + self._state = "committed" + for m in self._mutations: + self._client.put(m) + + def rollback(self): + """Rolls back current transaction.""" + self._check_update_state("started", "rolledback") + self._mutations = [] + + def put(self, entity): + """Puts entity to datastore.""" + assert isinstance(entity, FakeDatastoreEntity) + self._check_transaction_started() + self._mutations.append(copy.deepcopy(entity)) + + def get(self, key): + """Gets entity from the datastore.""" + assert isinstance(key, FakeDatastoreKey) + self._check_transaction_started() + self._read_keys.add(key) + return copy.deepcopy(self._data_snapshot.get(key)) + + def __enter__(self): + self.begin() + return self + + def __exit__(self, exc_type, exc_value, traceback): + if exc_type is None: + self.commit() + else: + self.rollback() + + +_QUERY_FILTER_OPERATOR = { + "<": lambda x, y: x < y, + "<=": lambda x, y: x <= y, + "=": lambda x, y: x == y, + ">": lambda x, y: x > y, + ">=": lambda x, y: x >= y, +} + + +class FakeDatastoreClient(object): + """Fake for CompetitionDatastoreClient.""" + + def __init__(self, entities=None): + """Init FakeDatastoreClient with specified entities.""" + self._transaction_hook = None + if isinstance(entities, list): + self._entities = {e.key: e for e in entities} + elif isinstance(entities, dict): + self._entities = entities + elif entities is None: + self._entities = {} + else: + raise ValueError("Invalid type of entities: " + str(type(entities))) + assert all([isinstance(k, FakeDatastoreKey) for k in self._entities.keys()]) + + @property + def entities(self): + """List of stored entities.""" + return self._entities + + def key(self, *args, **kwargs): + """Creates datastore key.""" + return FakeDatastoreKey(*args, **kwargs) + + def entity(self, key): + """Creates datastore entity.""" + assert isinstance(key, FakeDatastoreKey) + return FakeDatastoreEntity(key) + + def no_transact_batch(self): + """Starts batch of mutation which is committed without transaction.""" + return FakeDatastoreClientBatch(self) + + def transaction(self): + """Starts datastore transaction.""" + result = FakeDatastoreClientTransaction(self) + if self._transaction_hook: + self._transaction_hook(self) + self._transaction_hook = None + return result + + def get(self, key, transaction=None): + """Gets an entity with given key.""" + assert isinstance(key, FakeDatastoreKey) + if transaction: + return transaction.get(key) + return copy.deepcopy(self._entities.get(key)) + + def put(self, entity): + """Updates entity in the datastore.""" + assert isinstance(entity, FakeDatastoreEntity) + entity = copy.deepcopy(entity) + if entity.key in self.entities: + self.entities[entity.key].update(entity) + else: + self.entities[entity.key] = entity + + def batch(self): + """Starts batch of mutations.""" + raise NotImplementedError("FakeDatastoreClient.batch not implemented") + + def query_fetch(self, **kwargs): + """Queries datastore.""" + kind = kwargs.get("kind", None) + ancestor = kwargs.get("ancestor", None) + filters = kwargs.get("filters", []) + if ancestor and not isinstance(ancestor, FakeDatastoreKey): + raise ValueError("Invalid ancestor type: " + str(type(ancestor))) + if ("projection" in kwargs) or ("order" in kwargs) or ("distinct_on" in kwargs): + raise ValueError("Unsupported clause in arguments: " + str(kwargs)) + for f in filters: + if not isinstance(f, tuple) or len(f) != 3: + raise ValueError("Invalid filter: " + str(filters)) + if f[1] not in _QUERY_FILTER_OPERATOR.keys(): + raise ValueError("Unsupported operator in filters: " + str(filters)) + for e in self._entities.values(): + key_tuple = e.key.flat_path + if (kind is not None) and (key_tuple[-2] != kind): + continue + if (ancestor is not None) and (key_tuple[:-2] != ancestor.flat_path): + continue + all_filters_true = True + for f in filters: + if f[0] not in e: + all_filters_true = False + break + if not _QUERY_FILTER_OPERATOR[f[1]](e[f[0]], f[2]): + all_filters_true = False + break + if not all_filters_true: + continue + yield e + + def set_transaction_hook(self, hook): + """Sets transaction hook. + + This hook will be executed right after next transaction created. + It helps to model a situation when data are modified outside of transaction. + To be used in tests to test how your code handles edits concurrent with + transaction. + + Args: + hook: transaction hook, should be a function which takes exactly + one argument - instance of this class. + + Raises: + ValueError: if transaction hook was already set + """ + if self._transaction_hook is not None: + raise ValueError("Attempt to set transaction hook twice") + self._transaction_hook = hook + + def __str__(self): + """Returns string representation of all stored entities.""" + buf = StringIO() + for entity in self.entities.values(): + buf.write(u"Entity {0}:\n".format(entity.key.flat_path)) + buf.write(u" {0}\n".format(dict(entity))) + return buf.getvalue() diff --git a/cleverhans_v3.1.0/examples/nips17_adversarial_competition/eval_infra/code/eval_lib/tests/fake_cloud_client_test.py b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/eval_infra/code/eval_lib/tests/fake_cloud_client_test.py new file mode 100644 index 000000000..ba7561ac8 --- /dev/null +++ b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/eval_infra/code/eval_lib/tests/fake_cloud_client_test.py @@ -0,0 +1,483 @@ +"""Tests for eval_lib.testing.fake_cloud_client.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import copy +from io import BytesIO +import unittest +from eval_lib.tests import fake_cloud_client +from six import assertCountEqual +from six import b as six_b + + +class FakeStorageClientTest(unittest.TestCase): + def test_list_blobs(self): + all_blobs = [ + "some_blob", + "dataset/dev_dataset.csv", + "dataset/dev/img1.png", + "dataset/dev/img2.png", + ] + client = fake_cloud_client.FakeStorageClient(all_blobs) + assertCountEqual(self, all_blobs, client.list_blobs()) + assertCountEqual( + self, + ["dataset/dev_dataset.csv", "dataset/dev/img1.png", "dataset/dev/img2.png"], + client.list_blobs("dataset/dev"), + ) + assertCountEqual( + self, + ["dataset/dev/img1.png", "dataset/dev/img2.png"], + client.list_blobs("dataset/dev/"), + ) + + def test_get_blob(self): + client = fake_cloud_client.FakeStorageClient( + {"some_blob": "some_content", "blob2": "another_content"} + ) + self.assertIsNone(client.get_blob("blob3")) + buf = BytesIO() + client.get_blob("some_blob").download_to_file(buf) + self.assertEqual(six_b("some_content"), buf.getvalue()) + + +class FakeDatastoreKeyTest(unittest.TestCase): + def test_flat_path(self): + key1 = fake_cloud_client.FakeDatastoreKey("abc", "1") + self.assertTupleEqual(("abc", "1"), key1.flat_path) + key2 = fake_cloud_client.FakeDatastoreKey("def", "xyz", parent=key1) + self.assertTupleEqual(("abc", "1", "def", "xyz"), key2.flat_path) + + def test_equality(self): + key1a = fake_cloud_client.FakeDatastoreKey("abc", "1") + key1b = fake_cloud_client.FakeDatastoreKey("abc", "1") + key2a = fake_cloud_client.FakeDatastoreKey("def", "xyz", parent=key1a) + key2b = fake_cloud_client.FakeDatastoreKey("def", "xyz", parent=key1a) + # key equal to self + self.assertTrue(key1a == key1a) + self.assertFalse(key1a != key1a) + # key equal to the same key + self.assertTrue(key1a == key1b) + self.assertFalse(key1a != key1b) + self.assertTrue(key2a == key2b) + self.assertFalse(key2a != key2b) + # key different from other key + self.assertFalse(key1a == key2a) + self.assertTrue(key1a != key2a) + # key not equal to tuple + self.assertTrue(key1a != key1a.flat_path) + self.assertFalse(key1a == key1a.flat_path) + + +class FakeDatastoreEntityTest(unittest.TestCase): + def test_key(self): + entity = fake_cloud_client.make_entity(("abc", "1")) + self.assertEqual(entity.key, fake_cloud_client.FakeDatastoreKey("abc", "1")) + + def test_equality_keys(self): + entity1a = fake_cloud_client.make_entity(("abc", "1")) + entity1b = fake_cloud_client.make_entity(("abc", "1")) + entity2 = fake_cloud_client.make_entity(("abc", "2")) + self.assertFalse(entity1a == entity2) + self.assertTrue(entity1a != entity2) + self.assertTrue(entity1a == entity1b) + self.assertFalse(entity1b != entity1b) + + def test_equality_dict(self): + entity1 = fake_cloud_client.make_entity(("abc", "1")) + entity1["k1"] = "v1" + entity2 = fake_cloud_client.make_entity(("abc", "1")) + entity2["k1"] = "v2" + entity3 = fake_cloud_client.make_entity(("abc", "1")) + entity1["k1"] = "v1" + entity1["k2"] = "v2" + # compare to self + self.assertTrue(entity1 == entity1) + self.assertFalse(entity1 != entity1) + self.assertTrue(entity2 == entity2) + self.assertFalse(entity2 != entity2) + self.assertTrue(entity3 == entity3) + self.assertFalse(entity3 != entity3) + # compare to others + self.assertFalse(entity1 == entity2) + self.assertTrue(entity1 != entity2) + self.assertFalse(entity1 == entity3) + self.assertTrue(entity1 != entity3) + self.assertFalse(entity2 == entity3) + self.assertTrue(entity2 != entity3) + + def test_copy(self): + entity1 = fake_cloud_client.make_entity(("abc", "1")) + entity1["k1"] = ["v1"] + self.assertEqual(entity1.key, fake_cloud_client.FakeDatastoreKey("abc", "1")) + self.assertEqual(dict(entity1), {"k1": ["v1"]}) + entity2 = copy.copy(entity1) + entity2["k1"].append("v2") + entity2["k3"] = "v3" + self.assertIsInstance(entity2, fake_cloud_client.FakeDatastoreEntity) + self.assertEqual(entity1.key, fake_cloud_client.FakeDatastoreKey("abc", "1")) + self.assertEqual(dict(entity1), {"k1": ["v1", "v2"]}) + self.assertEqual(entity2.key, fake_cloud_client.FakeDatastoreKey("abc", "1")) + self.assertEqual(dict(entity2), {"k1": ["v1", "v2"], "k3": "v3"}) + + def test_deep_copy(self): + entity1 = fake_cloud_client.make_entity(("abc", "1")) + entity1["k1"] = ["v1"] + self.assertEqual(entity1.key, fake_cloud_client.FakeDatastoreKey("abc", "1")) + self.assertEqual(dict(entity1), {"k1": ["v1"]}) + entity2 = copy.deepcopy(entity1) + entity2["k1"].append("v2") + entity2["k3"] = "v3" + self.assertIsInstance(entity2, fake_cloud_client.FakeDatastoreEntity) + self.assertEqual(entity1.key, fake_cloud_client.FakeDatastoreKey("abc", "1")) + self.assertEqual(dict(entity1), {"k1": ["v1"]}) + self.assertEqual(entity2.key, fake_cloud_client.FakeDatastoreKey("abc", "1")) + self.assertEqual(dict(entity2), {"k1": ["v1", "v2"], "k3": "v3"}) + + +class FakeDatastoreClientTest(unittest.TestCase): + def setUp(self): + self._client = fake_cloud_client.FakeDatastoreClient() + self._key1 = self._client.key("abc", "def") + self._key2 = self._client.key("qwe", "rty", parent=self._key1) + self._entity1 = self._client.entity(self._key1) + self._entity1["k1"] = "v1" + self._entity2 = self._client.entity(self._key2) + self._entity2["k2"] = "v2" + self._entity2["k3"] = "v3" + + def test_make_key(self): + self.assertTupleEqual(("abc", "def"), self._key1.flat_path) + self.assertTupleEqual(("abc", "def", "qwe", "rty"), self._key2.flat_path) + + def test_make_entity(self): + self.assertTupleEqual(("abc", "def"), self._entity1.key.flat_path) + + def test_put_entity(self): + self.assertDictEqual({}, self._client.entities) + self._client.put(self._entity1) + self.assertDictEqual({self._key1: self._entity1}, self._client.entities) + self._client.put(self._entity2) + self.assertDictEqual( + {self._key1: self._entity1, self._key2: self._entity2}, + self._client.entities, + ) + + def test_get_entity(self): + self._client.put(self._entity1) + self._client.put(self._entity2) + self.assertEqual(self._entity1, self._client.get(self._key1)) + self.assertEqual(self._entity2, self._client.get(self._key2)) + + def test_write_batch(self): + with self._client.no_transact_batch() as batch: + batch.put(self._entity1) + batch.put(self._entity2) + assertCountEqual(self, [self._key1, self._key2], self._client.entities.keys()) + self.assertEqual(self._key1, self._client.entities[self._key1].key) + self.assertDictEqual({"k1": "v1"}, dict(self._client.entities[self._key1])) + self.assertEqual(self._key2, self._client.entities[self._key2].key) + self.assertDictEqual( + {"k2": "v2", "k3": "v3"}, dict(self._client.entities[self._key2]) + ) + + def test_overwrite_values(self): + client = fake_cloud_client.FakeDatastoreClient() + key1 = client.key("abc", "def") + entity1 = client.entity(key1) + entity1["k1"] = "v1" + entity2 = client.entity(key1) + entity2["k1"] = "v2" + entity2["k2"] = "v3" + with client.no_transact_batch() as batch: + batch.put(entity1) + assertCountEqual(self, [key1], client.entities.keys()) + self.assertEqual(key1, client.entities[key1].key) + self.assertDictEqual({"k1": "v1"}, dict(client.entities[key1])) + with client.no_transact_batch() as batch: + batch.put(entity2) + assertCountEqual(self, [key1], client.entities.keys()) + self.assertEqual(key1, client.entities[key1].key) + self.assertDictEqual({"k1": "v2", "k2": "v3"}, dict(client.entities[key1])) + + def test_query_fetch_all(self): + entity1 = fake_cloud_client.make_entity(("abc", "1")) + entity1["k1"] = "v1" + entity2 = fake_cloud_client.make_entity(("abc", "1", "def", "2")) + entity2["k2"] = "v2" + client = fake_cloud_client.FakeDatastoreClient([entity1, entity2]) + assertCountEqual(self, [entity1, entity2], client.query_fetch()) + + def test_query_fetch_kind_filter(self): + entity1 = fake_cloud_client.make_entity(("abc", "1")) + entity1["k1"] = "v1" + entity2 = fake_cloud_client.make_entity(("abc", "1", "def", "2")) + entity2["k2"] = "v2" + client = fake_cloud_client.FakeDatastoreClient([entity1, entity2]) + assertCountEqual(self, [entity1], client.query_fetch(kind="abc")) + assertCountEqual(self, [entity2], client.query_fetch(kind="def")) + + def test_query_fetch_ancestor_filter(self): + entity1 = fake_cloud_client.make_entity(("abc", "1", "def", "2")) + entity1["k1"] = "v1" + entity2 = fake_cloud_client.make_entity(("xyz", "3", "qwe", "4")) + entity2["k2"] = "v2" + client = fake_cloud_client.FakeDatastoreClient([entity1, entity2]) + assertCountEqual( + self, [entity1], client.query_fetch(ancestor=client.key("abc", "1")) + ) + assertCountEqual( + self, [entity2], client.query_fetch(ancestor=client.key("xyz", "3")) + ) + + def test_query_fetch_ancestor_and_kind_filter(self): + entity1 = fake_cloud_client.make_entity(("abc", "1", "def", "2")) + entity1["k1"] = "v1" + entity2 = fake_cloud_client.make_entity(("abc", "1", "xyz", "3")) + entity2["k2"] = "v2" + entity3 = fake_cloud_client.make_entity(("def", "4")) + entity3["k2"] = "v2" + client = fake_cloud_client.FakeDatastoreClient([entity1, entity2, entity3]) + assertCountEqual( + self, + [entity1], + client.query_fetch(kind="def", ancestor=client.key("abc", "1")), + ) + + def test_query_fetch_data_filter(self): + entity1 = fake_cloud_client.make_entity(("abc", "1")) + entity1["k1"] = "v1" + entity2 = fake_cloud_client.make_entity(("abc", "2")) + entity2["k1"] = "v2" + entity2["k2"] = "v2" + entity3 = fake_cloud_client.make_entity(("abc", "3")) + entity3["k2"] = "v3" + client = fake_cloud_client.FakeDatastoreClient([entity1, entity2, entity3]) + assertCountEqual( + self, [entity1], client.query_fetch(filters=[("k1", "=", "v1")]) + ) + assertCountEqual( + self, [entity2], client.query_fetch(filters=[("k1", ">", "v1")]) + ) + assertCountEqual( + self, [entity1, entity2], client.query_fetch(filters=[("k1", ">=", "v1")]) + ) + assertCountEqual( + self, [entity2], client.query_fetch(filters=[("k2", "<", "v3")]) + ) + assertCountEqual( + self, [entity2, entity3], client.query_fetch(filters=[("k2", "<=", "v3")]) + ) + assertCountEqual( + self, + [entity2], + client.query_fetch(filters=[("k1", ">=", "v1"), ("k2", "<=", "v3")]), + ) + + +class FakeDatastoreClientTransactionTest(unittest.TestCase): + def setUp(self): + self._client = fake_cloud_client.FakeDatastoreClient() + self._key1 = self._client.key("abc", "def") + self._key2 = self._client.key("qwe", "rty", parent=self._key1) + self._key3 = self._client.key("123", "456") + self._entity1 = self._client.entity(self._key1) + self._entity1["k1"] = "v1" + self._entity2 = self._client.entity(self._key2) + self._entity2["k2"] = "v2" + self._entity2["k3"] = "v3" + self._entity3 = self._client.entity(self._key3) + self._entity3["k4"] = "v4" + self._entity3["k5"] = "v5" + self._entity3["k6"] = "v6" + self._client.put(self._entity1) + self._client.put(self._entity2) + self._client.put(self._entity3) + # verify datastore content + assertCountEqual( + self, [self._key1, self._key2, self._key3], self._client.entities.keys() + ) + self.assertDictEqual({"k1": "v1"}, dict(self._client.entities[self._key1])) + self.assertDictEqual( + {"k2": "v2", "k3": "v3"}, dict(self._client.entities[self._key2]) + ) + self.assertDictEqual( + {"k4": "v4", "k5": "v5", "k6": "v6"}, + dict(self._client.entities[self._key3]), + ) + + def test_transaction_write_only_no_concurrent(self): + key4 = self._client.key("zxc", "vbn") + entity4 = self._client.entity(key4) + entity4["k7"] = "v7" + entity3_upd = self._client.entity(self._key3) + entity3_upd["k4"] = "upd_v4" + with self._client.transaction() as transaction: + # first write in transaction + transaction.put(entity4) + # second write in transaction + transaction.put(entity3_upd) + # verify datastore content + assertCountEqual( + self, + [self._key1, self._key2, self._key3, key4], + self._client.entities.keys(), + ) + self.assertDictEqual({"k1": "v1"}, dict(self._client.entities[self._key1])) + self.assertDictEqual( + {"k2": "v2", "k3": "v3"}, dict(self._client.entities[self._key2]) + ) + self.assertDictEqual( + {"k4": "upd_v4", "k5": "v5", "k6": "v6"}, + dict(self._client.entities[self._key3]), + ) + self.assertDictEqual({"k7": "v7"}, dict(self._client.entities[key4])) + + def test_transaction_read_write_no_concurrent(self): + key4 = self._client.key("zxc", "vbn") + entity4 = self._client.entity(key4) + entity4["k7"] = "v7" + entity3_upd = self._client.entity(self._key3) + entity3_upd["k4"] = "upd_v4" + with self._client.transaction() as transaction: + # reading in transaction always returns data snapshot before transaction + read_entity = self._client.get(self._key3, transaction=transaction) + self.assertDictEqual( + {"k4": "v4", "k5": "v5", "k6": "v6"}, dict(read_entity) + ) + # first write in transaction + transaction.put(entity3_upd) + # second write in transaction + transaction.put(entity4) + # reading in transaction always returns data snapshot before transaction + read_entity = self._client.get(self._key3, transaction=transaction) + self.assertDictEqual( + {"k4": "v4", "k5": "v5", "k6": "v6"}, dict(read_entity) + ) + # verify datastore content + assertCountEqual( + self, + [self._key1, self._key2, self._key3, key4], + self._client.entities.keys(), + ) + self.assertDictEqual({"k1": "v1"}, dict(self._client.entities[self._key1])) + self.assertDictEqual( + {"k2": "v2", "k3": "v3"}, dict(self._client.entities[self._key2]) + ) + self.assertDictEqual( + {"k4": "upd_v4", "k5": "v5", "k6": "v6"}, + dict(self._client.entities[self._key3]), + ) + self.assertDictEqual({"k7": "v7"}, dict(self._client.entities[key4])) + + def test_transaction_read_write_concurrent_not_intersecting(self): + key4 = self._client.key("zxc", "vbn") + entity4 = self._client.entity(key4) + entity4["k7"] = "v7" + entity3_upd = self._client.entity(self._key3) + entity3_upd["k4"] = "upd_v4" + entity1_upd = self._client.entity(self._key1) + entity1_upd["k1"] = "upd_v1" + with self._client.transaction() as transaction: + # reading in transaction always returns data snapshot before transaction + read_entity = self._client.get(self._key3, transaction=transaction) + self.assertDictEqual( + {"k4": "v4", "k5": "v5", "k6": "v6"}, dict(read_entity) + ) + # first write in transaction + transaction.put(entity3_upd) + # modify some data which are not references in the transaction + self._client.put(entity1_upd) + # second write in transaction + transaction.put(entity4) + # reading in transaction always returns data snapshot before transaction + read_entity = self._client.get(self._key3, transaction=transaction) + self.assertDictEqual( + {"k4": "v4", "k5": "v5", "k6": "v6"}, dict(read_entity) + ) + # verify datastore content + assertCountEqual( + self, + [self._key1, self._key2, self._key3, key4], + self._client.entities.keys(), + ) + self.assertDictEqual({"k1": "upd_v1"}, dict(self._client.entities[self._key1])) + self.assertDictEqual( + {"k2": "v2", "k3": "v3"}, dict(self._client.entities[self._key2]) + ) + self.assertDictEqual( + {"k4": "upd_v4", "k5": "v5", "k6": "v6"}, + dict(self._client.entities[self._key3]), + ) + self.assertDictEqual({"k7": "v7"}, dict(self._client.entities[key4])) + + def test_transaction_write_concurrent(self): + key4 = self._client.key("zxc", "vbn") + entity4 = self._client.entity(key4) + entity4["k7"] = "v7" + entity3_upd = self._client.entity(self._key3) + entity3_upd["k4"] = "upd_v4" + entity3_upd_no_transact = self._client.entity(self._key3) + entity3_upd_no_transact["k4"] = "another_v4" + reached_end_of_transaction = False + with self.assertRaises(Exception): + with self._client.transaction() as transaction: + # first write in transaction + transaction.put(entity3_upd) + # modify some data which are not references in the transaction + self._client.put(entity3_upd_no_transact) + # second write in transaction + transaction.put(entity4) + reached_end_of_transaction = True + self.assertTrue(reached_end_of_transaction) + # verify datastore content + assertCountEqual( + self, [self._key1, self._key2, self._key3], self._client.entities.keys() + ) + self.assertDictEqual({"k1": "v1"}, dict(self._client.entities[self._key1])) + self.assertDictEqual( + {"k2": "v2", "k3": "v3"}, dict(self._client.entities[self._key2]) + ) + self.assertDictEqual( + {"k4": "another_v4", "k5": "v5", "k6": "v6"}, + dict(self._client.entities[self._key3]), + ) + + def test_transaction_read_concurrent(self): + key4 = self._client.key("zxc", "vbn") + entity4 = self._client.entity(key4) + entity4["k7"] = "v7" + entity3_upd_no_transact = self._client.entity(self._key3) + entity3_upd_no_transact["k4"] = "another_v4" + reached_end_of_transaction = False + with self.assertRaises(Exception): + with self._client.transaction() as transaction: + # write in transaction + transaction.put(entity4) + # read in transaction + read_entity = self._client.get(self._key3, transaction=transaction) + self.assertDictEqual( + {"k4": "v4", "k5": "v5", "k6": "v6"}, dict(read_entity) + ) + # modify some data which are not references in the transaction + self._client.put(entity3_upd_no_transact) + reached_end_of_transaction = True + self.assertTrue(reached_end_of_transaction) + # verify datastore content + assertCountEqual( + self, [self._key1, self._key2, self._key3], self._client.entities.keys() + ) + self.assertDictEqual({"k1": "v1"}, dict(self._client.entities[self._key1])) + self.assertDictEqual( + {"k2": "v2", "k3": "v3"}, dict(self._client.entities[self._key2]) + ) + self.assertDictEqual( + {"k4": "another_v4", "k5": "v5", "k6": "v6"}, + dict(self._client.entities[self._key3]), + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/cleverhans_v3.1.0/examples/nips17_adversarial_competition/eval_infra/code/eval_lib/tests/image_batches_test.py b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/eval_infra/code/eval_lib/tests/image_batches_test.py new file mode 100644 index 000000000..dbe75ff29 --- /dev/null +++ b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/eval_infra/code/eval_lib/tests/image_batches_test.py @@ -0,0 +1,304 @@ +"""Tests for eval_lib.image_batches.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import itertools +import unittest + +from six import assertCountEqual + +from eval_lib import image_batches +from eval_lib import submissions +from eval_lib.tests import fake_cloud_client + + +ROUND_NAME = "round-name" + + +class ImageBatchesBaseTest(unittest.TestCase): + def setUp(self): + self.datastore_client = fake_cloud_client.FakeDatastoreClient() + self.image_batches = image_batches.ImageBatchesBase( + datastore_client=self.datastore_client, + entity_kind_batches="Batch", + entity_kind_images="Image", + ) + + def test_add_batch(self): + self.assertEqual(0, len(self.image_batches.data)) + self.image_batches.add_batch( + "batch1", batch_properties={"k1": "v1", "k2": "v2"} + ) + self.assertEqual(1, len(self.image_batches.data)) + self.assertDictEqual( + {"k1": "v1", "k2": "v2", "images": {}}, self.image_batches["batch1"] + ) + self.image_batches.add_batch("batch2", batch_properties={"k3": "v3"}) + self.assertEqual(2, len(self.image_batches.data)) + self.assertDictEqual({"k3": "v3", "images": {}}, self.image_batches["batch2"]) + + def test_add_image(self): + self.assertEqual(0, len(self.image_batches.data)) + self.image_batches.add_batch( + "batch1", batch_properties={"k1": "v1", "k2": "v2"} + ) + self.image_batches.add_image("batch1", "img1", image_properties={"k4": "v4"}) + self.assertEqual(1, len(self.image_batches.data)) + self.assertDictEqual( + {"k1": "v1", "k2": "v2", "images": {"img1": {"k4": "v4"}}}, + self.image_batches["batch1"], + ) + self.image_batches.add_image("batch1", "img2", image_properties={"k5": "v5"}) + self.assertEqual(1, len(self.image_batches.data)) + self.assertDictEqual( + { + "k1": "v1", + "k2": "v2", + "images": {"img1": {"k4": "v4"}, "img2": {"k5": "v5"}}, + }, + self.image_batches["batch1"], + ) + + def test_write_to_datastore(self): + # add 2 batches and 3 images, write everything to datastore + self.image_batches.add_batch( + "batch1", batch_properties={"k1": "v1", "k2": "v2"} + ) + self.image_batches.add_batch("batch2", batch_properties={"k3": "v3"}) + self.image_batches.add_image("batch1", "img1", image_properties={"k4": "v4"}) + self.image_batches.add_image("batch1", "img2", image_properties={"k5": "v5"}) + self.image_batches.add_image("batch2", "img3", image_properties={"k6": "v6"}) + self.image_batches.write_to_datastore() + # verify batches + batch_entity1 = self.datastore_client.entity( + fake_cloud_client.FakeDatastoreKey("Batch", "batch1") + ) + batch_entity1.update({"k1": "v1", "k2": "v2"}) + batch_entity2 = self.datastore_client.entity( + fake_cloud_client.FakeDatastoreKey("Batch", "batch2") + ) + batch_entity2.update({"k3": "v3"}) + assertCountEqual( + self, + [batch_entity1, batch_entity2], + self.datastore_client.query_fetch(kind="Batch"), + ) + # verify images + img_entity1 = self.datastore_client.entity( + fake_cloud_client.FakeDatastoreKey("Batch", "batch2", "Image", "img1") + ) + img_entity1.update({"k4": "v4"}) + img_entity2 = self.datastore_client.entity( + fake_cloud_client.FakeDatastoreKey("Batch", "batch2", "Image", "img2") + ) + img_entity2.update({"k5": "v5"}) + img_entity3 = self.datastore_client.entity( + fake_cloud_client.FakeDatastoreKey("Batch", "batch2", "Image", "img3") + ) + img_entity3.update({"k6": "v6"}) + + def test_write_single_batch_images_to_datastore(self): + # add 2 batches and 3 images, write only one batch to datastore + self.image_batches.add_batch( + "batch1", batch_properties={"k1": "v1", "k2": "v2"} + ) + self.image_batches.add_batch("batch2", batch_properties={"k3": "v3"}) + self.image_batches.add_image("batch1", "img1", image_properties={"k4": "v4"}) + self.image_batches.add_image("batch1", "img2", image_properties={"k5": "v5"}) + self.image_batches.add_image("batch2", "img3", image_properties={"k6": "v6"}) + self.image_batches.write_single_batch_images_to_datastore("batch2") + # verify batches + # write_single_batch_images_to_datastore writes only images, so no batches + assertCountEqual(self, [], self.datastore_client.query_fetch(kind="Batch")) + # verify images + img_entity3 = self.datastore_client.entity( + fake_cloud_client.FakeDatastoreKey("Batch", "batch2", "Image", "img3") + ) + img_entity3.update({"k6": "v6"}) + assertCountEqual( + self, [img_entity3], self.datastore_client.query_fetch(kind="Image") + ) + + +class DatasetBatchesTest(unittest.TestCase): + def setUp(self): + storage_blobs = [ + "dataset/dev/img1.png", + "dataset/dev/img2.png", + "dataset/dev/img3.png", + "dataset/dev/img4.png", + "dataset/dev/img5.png", + "dataset/dev_dataset.csv", + ] + self.storage_client = fake_cloud_client.FakeStorageClient(storage_blobs) + self.datastore_client = fake_cloud_client.FakeDatastoreClient() + self.dataset_batches = image_batches.DatasetBatches( + datastore_client=self.datastore_client, + storage_client=self.storage_client, + dataset_name="dev", + ) + + def verify_dataset_batches(self): + self.assertEqual(2, len(self.dataset_batches.data)) + all_images = {} + for batch in self.dataset_batches.data.values(): + self.assertIn(batch["epsilon"], [4, 8, 12, 16]) + self.assertGreaterEqual(3, len(batch["images"])) + self.assertTrue( + set(all_images.keys()).isdisjoint(batch["images"].keys()), + msg=( + "all_images and batch['images'] contains common keys %s" + % set(all_images.keys()).intersection(batch["images"].keys()) + ), + ) + all_images.update(batch["images"]) + assertCountEqual( + self, + [ + {"dataset_image_id": "img1", "image_path": "dataset/dev/img1.png"}, + {"dataset_image_id": "img2", "image_path": "dataset/dev/img2.png"}, + {"dataset_image_id": "img3", "image_path": "dataset/dev/img3.png"}, + {"dataset_image_id": "img4", "image_path": "dataset/dev/img4.png"}, + {"dataset_image_id": "img5", "image_path": "dataset/dev/img5.png"}, + ], + all_images.values(), + ) + + def verify_datastore_entities(self): + # Verify 'DatasetBatch' entities + expected_batch_entities = [] + for batch_id, batch in self.dataset_batches.data.items(): + entity = self.datastore_client.entity( + fake_cloud_client.FakeDatastoreKey("DatasetBatch", batch_id) + ) + entity["epsilon"] = batch["epsilon"] + expected_batch_entities.append(entity) + assertCountEqual( + self, + expected_batch_entities, + self.datastore_client.query_fetch(kind="DatasetBatch"), + ) + # Verify 'DatasetImage' entities + expected_image_entities = [] + for batch_id, batch in self.dataset_batches.data.items(): + for image_id, image in batch["images"].items(): + entity = self.datastore_client.entity( + fake_cloud_client.FakeDatastoreKey( + "DatasetBatch", batch_id, "DatasetImage", image_id + ) + ) + entity.update(image) + expected_image_entities.append(entity) + assertCountEqual( + self, + expected_image_entities, + self.datastore_client.query_fetch(kind="DatasetImage"), + ) + + def test_init_from_storage(self): + self.dataset_batches.init_from_storage_write_to_datastore(batch_size=3) + self.verify_dataset_batches() + self.verify_datastore_entities() + + def test_init_from_datastore(self): + self.dataset_batches.init_from_storage_write_to_datastore(batch_size=3) + self.dataset_batches = image_batches.DatasetBatches( + datastore_client=self.datastore_client, + storage_client=self.storage_client, + dataset_name="dev", + ) + self.dataset_batches.init_from_datastore() + self.verify_dataset_batches() + + def test_count_num_images(self): + self.dataset_batches.init_from_storage_write_to_datastore(batch_size=3) + self.assertEqual(5, self.dataset_batches.count_num_images()) + + +class AdversarialBatchesTest(unittest.TestCase): + def setUp(self): + # prepare dataset batches and submissions + storage_blobs = [ + "dataset/dev/img1.png", + "dataset/dev/img2.png", + "dataset/dev/img3.png", + "dataset/dev/img4.png", + "dataset/dev/img5.png", + "dataset/dev_dataset.csv", + ROUND_NAME + "/submissions/nontargeted/1.zip", + ROUND_NAME + "/submissions/nontargeted/baseline_nt.zip", + ROUND_NAME + "/submissions/targeted/1.zip", + ROUND_NAME + "/submissions/targeted/2.zip", + ROUND_NAME + "/submissions/defense/3.zip", + ROUND_NAME + "/submissions/defense/baseline_adv_train.zip", + ] + self.storage_client = fake_cloud_client.FakeStorageClient(storage_blobs) + self.datastore_client = fake_cloud_client.FakeDatastoreClient() + self.dataset_batches = image_batches.DatasetBatches( + datastore_client=self.datastore_client, + storage_client=self.storage_client, + dataset_name="dev", + ) + self.dataset_batches.init_from_storage_write_to_datastore(batch_size=3) + self.submissions = submissions.CompetitionSubmissions( + datastore_client=self.datastore_client, + storage_client=self.storage_client, + round_name=ROUND_NAME, + ) + self.submissions.init_from_storage_write_to_datastore() + + def verify_adversarial_batches_without_images(self, adv_batches): + attack_ids = list(self.submissions.attacks.keys()) + list( + self.submissions.targeted_attacks.keys() + ) + dataset_batch_ids = self.dataset_batches.data.keys() + expected_batches = [ + {"dataset_batch_id": b_id, "submission_id": a_id, "images": {}} + for (b_id, a_id) in itertools.product(dataset_batch_ids, attack_ids) + ] + assertCountEqual(self, expected_batches, adv_batches.data.values()) + + def test_init_from_dataset_and_submissions(self): + adv_batches = image_batches.AversarialBatches( + datastore_client=self.datastore_client + ) + adv_batches.init_from_dataset_and_submissions_write_to_datastore( + dataset_batches=self.dataset_batches, + attack_submission_ids=self.submissions.get_all_attack_ids(), + ) + self.verify_adversarial_batches_without_images(adv_batches) + + def test_init_from_datastore(self): + # populate datastore + adv_batches = image_batches.AversarialBatches( + datastore_client=self.datastore_client + ) + adv_batches.init_from_dataset_and_submissions_write_to_datastore( + dataset_batches=self.dataset_batches, + attack_submission_ids=self.submissions.get_all_attack_ids(), + ) + # init AversarialBatches from datastore + adv_batches = image_batches.AversarialBatches( + datastore_client=self.datastore_client + ) + adv_batches.init_from_datastore() + self.verify_adversarial_batches_without_images(adv_batches) + + def test_count_generated_adv_examples(self): + adv_batches = image_batches.AversarialBatches( + datastore_client=self.datastore_client + ) + adv_batches.init_from_dataset_and_submissions_write_to_datastore( + dataset_batches=self.dataset_batches, + attack_submission_ids=self.submissions.get_all_attack_ids(), + ) + self.assertDictEqual( + {"SUBA000": 0, "SUBA001": 0, "SUBT000": 0, "SUBT001": 0}, + adv_batches.count_generated_adv_examples(), + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/cleverhans_v3.1.0/examples/nips17_adversarial_competition/eval_infra/code/eval_lib/tests/submissions_test.py b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/eval_infra/code/eval_lib/tests/submissions_test.py new file mode 100644 index 000000000..5663f23d0 --- /dev/null +++ b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/eval_infra/code/eval_lib/tests/submissions_test.py @@ -0,0 +1,219 @@ +"""Tests for eval_lib.submissions.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import unittest + +from six import assertCountEqual + +from eval_lib import submissions +from eval_lib.tests import fake_cloud_client + + +ROUND_NAME = "round-name" + + +class ParticipantFromSubmissionPathTest(unittest.TestCase): + def test_team_id(self): + self.assertDictEqual( + {"team_id": 42}, submissions.participant_from_submission_path("path/42.zip") + ) + + def test_baseline_id(self): + self.assertDictEqual( + {"baseline_id": "a_1"}, + submissions.participant_from_submission_path("path/baseline_a_1.zip"), + ) + + def test_tar_extension(self): + self.assertDictEqual( + {"team_id": 42}, submissions.participant_from_submission_path("path/42.tar") + ) + + def test_tar_gz_extension(self): + self.assertDictEqual( + {"team_id": 42}, + submissions.participant_from_submission_path("path/42.tar.gz"), + ) + + +class SubmissionsTest(unittest.TestCase): + def setUp(self): + storage_blobs = [ + ROUND_NAME + "/submissions/nontargeted/1.zip", + ROUND_NAME + "/submissions/nontargeted/baseline_nt.zip", + ROUND_NAME + "/submissions/targeted/1.zip", + ROUND_NAME + "/submissions/targeted/2.zip", + ROUND_NAME + "/submissions/defense/3.zip", + ROUND_NAME + "/submissions/defense/baseline_adv_train.zip", + ] + self.storage_client = fake_cloud_client.FakeStorageClient(storage_blobs) + self.datastore_client = fake_cloud_client.FakeDatastoreClient() + self.submissions = submissions.CompetitionSubmissions( + datastore_client=self.datastore_client, + storage_client=self.storage_client, + round_name=ROUND_NAME, + ) + + def verify_submissions(self): + assertCountEqual( + self, + [ + submissions.SubmissionDescriptor( + path=(ROUND_NAME + "/submissions/nontargeted/1.zip"), + participant_id={"team_id": 1}, + ), + submissions.SubmissionDescriptor( + path=(ROUND_NAME + "/submissions/nontargeted/baseline_nt.zip"), + participant_id={"baseline_id": "nt"}, + ), + ], + self.submissions.attacks.values(), + ) + assertCountEqual( + self, + [ + submissions.SubmissionDescriptor( + path=(ROUND_NAME + "/submissions/targeted/1.zip"), + participant_id={"team_id": 1}, + ), + submissions.SubmissionDescriptor( + path=(ROUND_NAME + "/submissions/targeted/2.zip"), + participant_id={"team_id": 2}, + ), + ], + self.submissions.targeted_attacks.values(), + ) + assertCountEqual( + self, + [ + submissions.SubmissionDescriptor( + path=(ROUND_NAME + "/submissions/defense/3.zip"), + participant_id={"team_id": 3}, + ), + submissions.SubmissionDescriptor( + path=(ROUND_NAME + "/submissions/defense/baseline_adv_train.zip"), + participant_id={"baseline_id": "adv_train"}, + ), + ], + self.submissions.defenses.values(), + ) + self.assertEqual( + len(self.submissions.attacks) + + len(self.submissions.targeted_attacks) + + len(self.submissions.defenses), + len( + set(self.submissions.attacks.keys()) + | set(self.submissions.targeted_attacks.keys()) + | set(self.submissions.defenses.keys()) + ), + ) + + def verify_datastore_entities(self): + # Verify 'SubmissionType' entities + assertCountEqual( + self, + [ + self.datastore_client.entity( + fake_cloud_client.FakeDatastoreKey("SubmissionType", "Attacks") + ), + self.datastore_client.entity( + fake_cloud_client.FakeDatastoreKey( + "SubmissionType", "TargetedAttacks" + ) + ), + self.datastore_client.entity( + fake_cloud_client.FakeDatastoreKey("SubmissionType", "Defenses") + ), + ], + self.datastore_client.query_fetch(kind="SubmissionType"), + ) + # Verify 'Submission' entities + expected_submission_entities = [] + for key_prefix, submission_entries in [ + (("SubmissionType", "Attacks"), self.submissions.attacks), + (("SubmissionType", "TargetedAttacks"), self.submissions.targeted_attacks), + (("SubmissionType", "Defenses"), self.submissions.defenses), + ]: + for k, v in submission_entries.items(): + entity = self.datastore_client.entity( + fake_cloud_client.FakeDatastoreKey( + *(key_prefix + ("Submission", k)) + ) + ) + entity["submission_path"] = v.path + entity.update(v.participant_id) + expected_submission_entities.append(entity) + assertCountEqual( + self, + expected_submission_entities, + self.datastore_client.query_fetch(kind="Submission"), + ) + + def test_init_from_storage(self): + self.submissions.init_from_storage_write_to_datastore() + self.verify_submissions() + self.verify_datastore_entities() + + def test_init_from_datastore(self): + # first we need to populate datastore + self.submissions.init_from_storage_write_to_datastore() + # now reset submission class and load data from datastore + self.submissions = submissions.CompetitionSubmissions( + datastore_client=self.datastore_client, + storage_client=self.storage_client, + round_name=ROUND_NAME, + ) + self.assertFalse(self.submissions.attacks) + self.assertFalse(self.submissions.targeted_attacks) + self.assertFalse(self.submissions.defenses) + self.submissions.init_from_datastore() + self.verify_submissions() + + def test_get_all_attacks_ids(self): + self.submissions.init_from_storage_write_to_datastore() + # total will be two targeted and two not-targeted attacks, + # their IDs are generated sequentially + assertCountEqual( + self, + ["SUBA000", "SUBA001", "SUBT000", "SUBT001"], + self.submissions.get_all_attack_ids(), + ) + + def test_find_by_id(self): + self.submissions.init_from_storage_write_to_datastore() + self.assertEqual( + self.submissions.attacks["SUBA000"], self.submissions.find_by_id("SUBA000") + ) + self.assertEqual( + self.submissions.targeted_attacks["SUBT001"], + self.submissions.find_by_id("SUBT001"), + ) + self.assertEqual( + self.submissions.defenses["SUBD001"], self.submissions.find_by_id("SUBD001") + ) + + def test_get_external_id(self): + self.submissions.init_from_storage_write_to_datastore() + assertCountEqual( + self, + [3, "baseline_adv_train"], + [ + self.submissions.get_external_id("SUBD000"), + self.submissions.get_external_id("SUBD001"), + ], + ) + assertCountEqual( + self, + [1, "baseline_nt"], + [ + self.submissions.get_external_id("SUBA000"), + self.submissions.get_external_id("SUBA001"), + ], + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/cleverhans_v3.1.0/examples/nips17_adversarial_competition/eval_infra/code/eval_lib/tests/work_data_test.py b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/eval_infra/code/eval_lib/tests/work_data_test.py new file mode 100644 index 000000000..a1f8600f5 --- /dev/null +++ b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/eval_infra/code/eval_lib/tests/work_data_test.py @@ -0,0 +1,469 @@ +"""Tests for eval_lib.work_data.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import copy +import unittest + +from six import assertCountEqual +from six import itervalues + +from eval_lib import work_data +from eval_lib.tests import fake_cloud_client + + +TEST_WORK_TYPE_ENTITY_ID = "AllWork" + + +class WorkPiecesBaseTest(unittest.TestCase): + def setUp(self): + self.datastore_client = fake_cloud_client.FakeDatastoreClient() + self.work1 = { + "submission_id": "s1", + "output_adversarial_batch_id": "o1", + "claimed_worker_id": "worker9999", + "claimed_worker_start_time": -1, + "is_completed": True, + } + self.work2 = { + "submission_id": "s2", + "output_adversarial_batch_id": "o2", + "claimed_worker_id": None, + "claimed_worker_start_time": None, + "is_completed": False, + } + + def reset_work_pieces(self): + self.work_pieces = work_data.WorkPiecesBase( + self.datastore_client, TEST_WORK_TYPE_ENTITY_ID + ) + + def test_is_unclaimed(self): + # completed work considered claimed + self.assertFalse(work_data.is_unclaimed(self.work1)) + # not completed, not claimed work + self.assertTrue(work_data.is_unclaimed(self.work2)) + # claimed but not completed work + self.work2["claimed_worker_id"] = "some_worker" + self.work2["claimed_worker_start_time"] = work_data.get_integer_time() + self.assertFalse(work_data.is_unclaimed(self.work2)) + # work claimed too long ago considered unclaimed now + self.work2["claimed_worker_start_time"] = ( + work_data.get_integer_time() - work_data.MAX_PROCESSING_TIME - 1 + ) + self.assertTrue(work_data.is_unclaimed(self.work2)) + + def test_write_to_datastore(self): + self.reset_work_pieces() + self.work_pieces.work["w1"] = self.work1 + self.work_pieces.work["w2"] = self.work2 + self.work_pieces.write_all_to_datastore() + # verify content of the datastore + parent_key = fake_cloud_client.FakeDatastoreKey( + work_data.KIND_WORK_TYPE, TEST_WORK_TYPE_ENTITY_ID + ) + assertCountEqual( + self, + [fake_cloud_client.make_entity(parent_key)], + self.datastore_client.query_fetch(kind=work_data.KIND_WORK_TYPE), + ) + entity1 = fake_cloud_client.make_entity( + fake_cloud_client.FakeDatastoreKey( + work_data.KIND_WORK, "w1", parent=parent_key + ) + ) + entity1.update(self.work1) + entity2 = fake_cloud_client.make_entity( + fake_cloud_client.FakeDatastoreKey( + work_data.KIND_WORK, "w2", parent=parent_key + ) + ) + entity2.update(self.work2) + assertCountEqual( + self, + [entity1, entity2], + self.datastore_client.query_fetch(kind=work_data.KIND_WORK), + ) + + def test_read_from_datastore(self): + self.reset_work_pieces() + self.work_pieces.work["w10"] = self.work1 + self.work_pieces.work["w20"] = self.work2 + self.work_pieces.write_all_to_datastore() + self.reset_work_pieces() + self.work_pieces.read_all_from_datastore() + # verify data + self.assertDictEqual( + {"w10": self.work1, "w20": self.work2}, self.work_pieces.work + ) + + def test_is_all_work_completed(self): + self.reset_work_pieces() + # empty set of work is considered completed + self.assertTrue(self.work_pieces.is_all_work_competed()) + # one completed piece of work - all work completed + self.work_pieces.work["w11"] = copy.deepcopy(self.work1) + self.assertTrue(self.work_pieces.is_all_work_competed()) + # two completed pieces of work - all work completed + self.work_pieces.work["w12"] = copy.deepcopy(self.work1) + self.assertTrue(self.work_pieces.is_all_work_competed()) + # two completed and one incomplete pieces of work - work not completed + self.work_pieces.work["w2"] = copy.deepcopy(self.work2) + self.assertFalse(self.work_pieces.is_all_work_competed()) + + def test_read_undone_from_datastore(self): + self.reset_work_pieces() + self.work_pieces.work["w10"] = self.work1 + self.work_pieces.work["w20"] = self.work2 + self.work_pieces.write_all_to_datastore() + self.reset_work_pieces() + # return value is None because sharding is not used + self.assertIsNone(self.work_pieces.read_undone_from_datastore()) + # Only work with ID 'w20' is undone + self.assertDictEqual({"w20": self.work2}, self.work_pieces.work) + + def test_read_undone_from_datastore_same_shards(self): + self.reset_work_pieces() + self.work1["shard_id"] = 1 + self.work_pieces.work["w10"] = self.work1 + self.work2["shard_id"] = 2 + self.work_pieces.work["w20"] = self.work2 + self.work_pieces.write_all_to_datastore() + self.reset_work_pieces() + # return value is ID of the shard with undone work + self.assertEqual( + 2, self.work_pieces.read_undone_from_datastore(shard_id=2, num_shards=3) + ) + # Only work with ID 'w20' is undone + self.assertDictEqual({"w20": self.work2}, self.work_pieces.work) + + def test_read_undone_from_datastore_different_shards(self): + self.reset_work_pieces() + self.work1["shard_id"] = 1 + self.work_pieces.work["w10"] = self.work1 + self.work2["shard_id"] = 2 + self.work_pieces.work["w20"] = self.work2 + self.work_pieces.write_all_to_datastore() + self.reset_work_pieces() + # return value is ID of the shard with undone work + self.assertEqual( + 2, self.work_pieces.read_undone_from_datastore(shard_id=1, num_shards=3) + ) + # Only work with ID 'w20' is undone + self.assertDictEqual({"w20": self.work2}, self.work_pieces.work) + + def test_try_pick_piece_of_work_simple(self): + self.reset_work_pieces() + self.work_pieces.work["w10"] = self.work1 + self.work_pieces.work["w20"] = self.work2 + self.work_pieces.write_all_to_datastore() + work_id = self.work_pieces.try_pick_piece_of_work("worker0") + self.assertEqual("w20", work_id) + self.reset_work_pieces() + self.work_pieces.read_all_from_datastore() + self.assertEqual("worker0", self.work_pieces.work["w20"]["claimed_worker_id"]) + + def test_try_pick_piece_of_work_all_completed(self): + self.reset_work_pieces() + self.work_pieces.work["w10"] = self.work1 + self.work_pieces.work["w20"] = self.work2 + self.work_pieces.work["w20"]["is_completed"] = True + self.work_pieces.write_all_to_datastore() + work_id = self.work_pieces.try_pick_piece_of_work("worker0") + self.assertIsNone(work_id) + + def test_try_pick_piece_of_work_already_claimed(self): + self.reset_work_pieces() + self.work_pieces.work["w10"] = self.work1 + self.work2["claimed_worker_id"] = "other_worker" + self.work2["claimed_worker_start_time"] = work_data.get_integer_time() + self.work_pieces.work["w20"] = self.work2 + self.work_pieces.write_all_to_datastore() + work_id = self.work_pieces.try_pick_piece_of_work("worker0") + # if work is claimed by another worker then it won't be picked + self.assertIsNone(work_id) + + def test_try_pick_piece_of_work_claimed_long_ago(self): + self.reset_work_pieces() + self.work_pieces.work["w10"] = self.work1 + self.work2["claimed_worker_id"] = "other_worker" + self.work2["claimed_worker_start_time"] = ( + work_data.get_integer_time() - work_data.MAX_PROCESSING_TIME * 2 + ) + self.work_pieces.work["w20"] = self.work2 + self.work_pieces.write_all_to_datastore() + work_id = self.work_pieces.try_pick_piece_of_work("worker0") + # if work is claimed by another worker, but it happened some time ago + # then work will be claimed + self.assertEqual("w20", work_id) + + def test_try_pick_piece_of_work_concurrent_update(self): + self.reset_work_pieces() + self.work_pieces.work["w10"] = self.work1 + self.work_pieces.work["w20"] = self.work2 + self.work_pieces.write_all_to_datastore() + # any concurrent change in the entity will cause transaction to fail + + def transaction_hook(client): + key = client.key("WorkType", TEST_WORK_TYPE_ENTITY_ID, "Work", "w20") + client.entities[key]["output_adversarial_batch_id"] = "o3" + + self.datastore_client.set_transaction_hook(transaction_hook) + work_id = self.work_pieces.try_pick_piece_of_work("worker0") + self.assertIsNone(work_id) + + def test_try_pick_piece_of_work_concurrent_update_of_other(self): + self.reset_work_pieces() + self.work_pieces.work["w10"] = self.work1 + self.work_pieces.work["w20"] = self.work2 + self.work_pieces.write_all_to_datastore() + # concurrent change in entity which is not touched by the transaction + # won't prevent transaction from completing + + def transaction_hook(client): + key = client.key("WorkType", TEST_WORK_TYPE_ENTITY_ID, "Work", "w10") + client.entities[key]["output_adversarial_batch_id"] = "o3" + + self.datastore_client.set_transaction_hook(transaction_hook) + work_id = self.work_pieces.try_pick_piece_of_work("worker0") + self.assertEqual("w20", work_id) + + def test_update_work_as_completed(self): + self.reset_work_pieces() + self.work_pieces.work["w10"] = self.work1 + self.work_pieces.work["w20"] = self.work2 + self.work2["claimed_worker_id"] = "this_worker" + self.work2["claimed_worker_start_time"] = work_data.get_integer_time() + self.work_pieces.write_all_to_datastore() + self.assertTrue(self.work_pieces.update_work_as_completed("this_worker", "w20")) + self.reset_work_pieces() + self.work_pieces.read_all_from_datastore() + self.assertTrue(self.work_pieces.work["w20"]["is_completed"]) + self.assertNotIn("error", self.work_pieces.work["w20"]) + + def test_update_work_as_completed_other_values(self): + self.reset_work_pieces() + self.work_pieces.work["w10"] = self.work1 + self.work_pieces.work["w20"] = self.work2 + self.work2["claimed_worker_id"] = "this_worker" + self.work2["claimed_worker_start_time"] = work_data.get_integer_time() + self.work_pieces.write_all_to_datastore() + self.assertTrue( + self.work_pieces.update_work_as_completed( + "this_worker", "w20", other_values={"a": 123, "b": 456} + ) + ) + self.reset_work_pieces() + self.work_pieces.read_all_from_datastore() + self.assertTrue(self.work_pieces.work["w20"]["is_completed"]) + self.assertNotIn("error", self.work_pieces.work["w20"]) + self.assertEqual(123, self.work_pieces.work["w20"]["a"]) + self.assertEqual(456, self.work_pieces.work["w20"]["b"]) + + def test_update_work_as_completed_with_error(self): + self.reset_work_pieces() + self.work_pieces.work["w10"] = self.work1 + self.work_pieces.work["w20"] = self.work2 + self.work2["claimed_worker_id"] = "this_worker" + self.work2["claimed_worker_start_time"] = work_data.get_integer_time() + self.work_pieces.write_all_to_datastore() + self.assertTrue( + self.work_pieces.update_work_as_completed("this_worker", "w20", error="err") + ) + self.reset_work_pieces() + self.work_pieces.read_all_from_datastore() + self.assertTrue(self.work_pieces.work["w20"]["is_completed"]) + self.assertEqual("err", self.work_pieces.work["w20"]["error"]) + + def test_update_work_as_completed_wrong_claimed_worker(self): + self.reset_work_pieces() + self.work_pieces.work["w10"] = self.work1 + self.work_pieces.work["w20"] = self.work2 + self.work2["claimed_worker_id"] = "other_worker" + self.work2["claimed_worker_start_time"] = work_data.get_integer_time() + self.work_pieces.write_all_to_datastore() + self.assertFalse( + self.work_pieces.update_work_as_completed("this_worker", "w20") + ) + self.reset_work_pieces() + self.work_pieces.read_all_from_datastore() + self.assertFalse(self.work_pieces.work["w20"]["is_completed"]) + + def test_compute_work_stats(self): + self.reset_work_pieces() + self.work_pieces.work["w11"] = { + "submission_id": "s1", + "output_adversarial_batch_id": "o1", + "claimed_worker_id": "worker1", + "claimed_worker_start_time": -1, + "is_completed": True, + "elapsed_time": 1, + } + self.work_pieces.work["w12"] = { + "submission_id": "s1", + "output_adversarial_batch_id": "o2", + "claimed_worker_id": "worker2", + "claimed_worker_start_time": -1, + "is_completed": False, + } + self.work_pieces.work["w21"] = { + "submission_id": "s2", + "output_adversarial_batch_id": "o1", + "claimed_worker_id": "worker1", + "claimed_worker_start_time": -1, + "is_completed": True, + "elapsed_time": 5, + } + self.work_pieces.work["w22"] = { + "submission_id": "s2", + "output_adversarial_batch_id": "o2", + "claimed_worker_id": "worker2", + "claimed_worker_start_time": -1, + "is_completed": True, + "elapsed_time": 10, + "error": "err", + } + self.work_pieces.work["w23"] = { + "submission_id": "s2", + "output_adversarial_batch_id": "o1", + "claimed_worker_id": "worker1", + "claimed_worker_start_time": -1, + "is_completed": True, + "elapsed_time": 7, + } + stats = self.work_pieces.compute_work_statistics() + for v in itervalues(stats): + v["eval_times"] = sorted(v["eval_times"]) + self.assertDictEqual( + { + "s1": { + "completed": 1, + "num_errors": 0, + "error_messages": set(), + "eval_times": [1.0], + "min_eval_time": 1.0, + "max_eval_time": 1.0, + "mean_eval_time": 1.0, + "median_eval_time": 1.0, + }, + "s2": { + "completed": 3, + "num_errors": 1, + "error_messages": set(["err"]), + "eval_times": [5.0, 7.0], + "min_eval_time": 5.0, + "max_eval_time": 7.0, + "mean_eval_time": 6.0, + "median_eval_time": 6.0, + }, + }, + stats, + ) + + +class AttackWorkPiecesTest(unittest.TestCase): + def setUp(self): + self.datastore_client = fake_cloud_client.FakeDatastoreClient() + + def test_init_from_adversarial_batches(self): + adv_batches = { + "ADVBATCH000": {"submission_id": "s1"}, + "ADVBATCH001": {"submission_id": "s2"}, + "ADVBATCH002": {"submission_id": "s3"}, + } + expected_values = [ + { + "claimed_worker_id": None, + "claimed_worker_start_time": None, + "is_completed": False, + "error": None, + "elapsed_time": None, + "submission_id": "s1", + "shard_id": None, + "output_adversarial_batch_id": "ADVBATCH000", + }, + { + "claimed_worker_id": None, + "claimed_worker_start_time": None, + "is_completed": False, + "error": None, + "elapsed_time": None, + "submission_id": "s2", + "shard_id": None, + "output_adversarial_batch_id": "ADVBATCH001", + }, + { + "claimed_worker_id": None, + "claimed_worker_start_time": None, + "is_completed": False, + "error": None, + "elapsed_time": None, + "submission_id": "s3", + "shard_id": None, + "output_adversarial_batch_id": "ADVBATCH002", + }, + ] + attack_work = work_data.AttackWorkPieces(self.datastore_client) + attack_work.init_from_adversarial_batches(adv_batches) + assertCountEqual(self, expected_values, attack_work.work.values()) + attack_work.write_all_to_datastore() + attack_work = work_data.AttackWorkPieces(self.datastore_client) + attack_work.read_all_from_datastore() + assertCountEqual(self, expected_values, attack_work.work.values()) + + +class DefenseWorkPiecesTest(unittest.TestCase): + def setUp(self): + self.datastore_client = fake_cloud_client.FakeDatastoreClient() + + def test_init_from_classification_batches(self): + class_batches = { + "CBATCH000000": {"submission_id": "s1"}, + "CBATCH000001": {"submission_id": "s2"}, + "CBATCH000002": {"submission_id": "s3"}, + } + expected_values = [ + { + "claimed_worker_id": None, + "claimed_worker_start_time": None, + "is_completed": False, + "error": None, + "elapsed_time": None, + "submission_id": "s1", + "shard_id": None, + "output_classification_batch_id": "CBATCH000000", + }, + { + "claimed_worker_id": None, + "claimed_worker_start_time": None, + "is_completed": False, + "error": None, + "elapsed_time": None, + "submission_id": "s2", + "shard_id": None, + "output_classification_batch_id": "CBATCH000001", + }, + { + "claimed_worker_id": None, + "claimed_worker_start_time": None, + "is_completed": False, + "error": None, + "elapsed_time": None, + "submission_id": "s3", + "shard_id": None, + "output_classification_batch_id": "CBATCH000002", + }, + ] + defense_work = work_data.DefenseWorkPieces(self.datastore_client) + defense_work.init_from_class_batches(class_batches) + assertCountEqual(self, expected_values, defense_work.work.values()) + defense_work.write_all_to_datastore() + defense_work = work_data.DefenseWorkPieces(self.datastore_client) + defense_work.read_all_from_datastore() + assertCountEqual(self, expected_values, defense_work.work.values()) + + +if __name__ == "__main__": + unittest.main() diff --git a/cleverhans_v3.1.0/examples/nips17_adversarial_competition/eval_infra/code/eval_lib/work_data.py b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/eval_infra/code/eval_lib/work_data.py new file mode 100644 index 000000000..2ab7ab26a --- /dev/null +++ b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/eval_infra/code/eval_lib/work_data.py @@ -0,0 +1,418 @@ +"""Module with classes to read and store data about work entities. +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from builtins import int # long in python 2 + +from io import StringIO +import pickle +import random +import time + +import numpy as np + +from six import iteritems +from six import itervalues +from six import text_type + + +# Cloud Datastore constants +KIND_WORK_TYPE = u"WorkType" +KIND_WORK = u"Work" +ID_ATTACKS_WORK_ENTITY = u"AllAttacks" +ID_DEFENSES_WORK_ENTITY = u"AllDefenses" + +ATTACK_WORK_ID_PATTERN = u"WORKA{:03}" +DEFENSE_WORK_ID_PATTERN = u"WORKD{:05}" + +# Constants for __str__ +TO_STR_MAX_WORK = 20 + +# How long worker is allowed to process one piece of work, +# before considered failed +MAX_PROCESSING_TIME = 600 + +# Number of work records to read at once +MAX_WORK_RECORDS_READ = 1000 + + +def get_integer_time(): + """Returns current time in long integer format.""" + return int(time.time()) + + +def is_unclaimed(work): + """Returns True if work piece is unclaimed.""" + if work["is_completed"]: + return False + cutoff_time = time.time() - MAX_PROCESSING_TIME + if ( + work["claimed_worker_id"] + and work["claimed_worker_start_time"] is not None + and work["claimed_worker_start_time"] >= cutoff_time + ): + return False + return True + + +class WorkPiecesBase(object): + """Base class to store one piece of work. + + In adversarial competition, all work consists of the following: + - evaluation of all attacks on all images from dataset which results in + generation of adversarial images; + - evaluation of all defenses on all adversarial images which results in + storing classification labels. + + One piece of work is either evaluation of one attack on a subset of images or + evaluation of one defense on a subset of adversarial images. + This way all work is split into work pieces which could be computed + independently in parallel by different workers. + + Each work piece is identified by unique ID and has one of the following + statuses: + - Unclaimed. This means that no worker has started working on the work piece. + - Claimed by worker NN. This means that worker NN is working on this work + piece. After workpiece being claimed for too long (more than + MAX_PROCESSING_TIME seconds) it automatically considered unclaimed. This + is needed in case worker failed while processing the work piece. + - Completed. This means that computation of work piece is done. + + Additionally each work piece may be assigned to a shard. In such case + workers are also grouped into shards. Each time worker looking for a work + piece it first tries to find undone work from the shard worker is assigned to. + Only after all work from this shard is done, worker will try to claim + work pieces from other shards. + + The purpose of sharding is to reduce load on Google Cloud Datastore. + """ + + def __init__(self, datastore_client, work_type_entity_id): + """Initializes WorkPiecesBase class. + + Args: + datastore_client: instance of CompetitionDatastoreClient. + work_type_entity_id: ID of the WorkType parent entity + """ + self._datastore_client = datastore_client + self._work_type_entity_id = work_type_entity_id + # Dictionary: work_id -> dict with properties of the piece of work + # + # Common properties are following: + # - claimed_worker_id - worker id which claimed the work + # - claimed_worker_start_time - when work was claimed + # - is_completed - whether work is completed or not + # - error - if not None then work was completed with error + # - elapsed_time - time took to complete the work + # - shard_id - ID of the shard which run the work + # - submission_id - ID of the submission which should be executed + # + # Additionally piece of work will have property specific to work type: + # output_adversarial_batch_id for attack and output_classification_batch_id + # for defense. Also upon completion of the work, worker may write + # additional statistics field to the work. + self._work = {} + + def serialize(self, fobj): + """Serialize work pieces into file object.""" + pickle.dump(self._work, fobj) + + def deserialize(self, fobj): + """Deserialize work pieces from file object.""" + self._work = pickle.load(fobj) + + @property + def work(self): + """Dictionary with all work pieces.""" + return self._work + + def replace_work(self, value): + """Replaces work with provided value. + + Generally this method should be called only by master, that's why it + separated from the property self.work. + + Args: + value: dictionary with new work pieces + """ + assert isinstance(value, dict) + self._work = value + + def __len__(self): + return len(self._work) + + def is_all_work_competed(self): + """Returns whether all work pieces are completed or not.""" + return all([w["is_completed"] for w in itervalues(self.work)]) + + def write_all_to_datastore(self): + """Writes all work pieces into datastore. + + Each work piece is identified by ID. This method writes/updates only those + work pieces which IDs are stored in this class. For examples, if this class + has only work pieces with IDs '1' ... '100' and datastore already contains + work pieces with IDs '50' ... '200' then this method will create new + work pieces with IDs '1' ... '49', update work pieces with IDs + '50' ... '100' and keep unchanged work pieces with IDs '101' ... '200'. + """ + client = self._datastore_client + with client.no_transact_batch() as batch: + parent_key = client.key(KIND_WORK_TYPE, self._work_type_entity_id) + batch.put(client.entity(parent_key)) + for work_id, work_val in iteritems(self._work): + entity = client.entity( + client.key(KIND_WORK, work_id, parent=parent_key) + ) + entity.update(work_val) + batch.put(entity) + + def read_all_from_datastore(self): + """Reads all work pieces from the datastore.""" + self._work = {} + client = self._datastore_client + parent_key = client.key(KIND_WORK_TYPE, self._work_type_entity_id) + for entity in client.query_fetch(kind=KIND_WORK, ancestor=parent_key): + work_id = entity.key.flat_path[-1] + self.work[work_id] = dict(entity) + + def _read_undone_shard_from_datastore(self, shard_id=None): + """Reads undone worke pieces which are assigned to shard with given id.""" + self._work = {} + client = self._datastore_client + parent_key = client.key(KIND_WORK_TYPE, self._work_type_entity_id) + filters = [("is_completed", "=", False)] + if shard_id is not None: + filters.append(("shard_id", "=", shard_id)) + for entity in client.query_fetch( + kind=KIND_WORK, ancestor=parent_key, filters=filters + ): + work_id = entity.key.flat_path[-1] + self.work[work_id] = dict(entity) + if len(self._work) >= MAX_WORK_RECORDS_READ: + break + + def read_undone_from_datastore(self, shard_id=None, num_shards=None): + """Reads undone work from the datastore. + + If shard_id and num_shards are specified then this method will attempt + to read undone work for shard with id shard_id. If no undone work was found + then it will try to read shard (shard_id+1) and so on until either found + shard with undone work or all shards are read. + + Args: + shard_id: Id of the start shard + num_shards: total number of shards + + Returns: + id of the shard with undone work which was read. None means that work + from all datastore was read. + """ + if shard_id is not None: + shards_list = [(i + shard_id) % num_shards for i in range(num_shards)] + else: + shards_list = [] + shards_list.append(None) + for shard in shards_list: + self._read_undone_shard_from_datastore(shard) + if self._work: + return shard + return None + + def try_pick_piece_of_work(self, worker_id, submission_id=None): + """Tries pick next unclaimed piece of work to do. + + Attempt to claim work piece is done using Cloud Datastore transaction, so + only one worker can claim any work piece at a time. + + Args: + worker_id: ID of current worker + submission_id: if not None then this method will try to pick + piece of work for this submission + + Returns: + ID of the claimed work piece + """ + client = self._datastore_client + unclaimed_work_ids = None + if submission_id: + unclaimed_work_ids = [ + k + for k, v in iteritems(self.work) + if is_unclaimed(v) and (v["submission_id"] == submission_id) + ] + if not unclaimed_work_ids: + unclaimed_work_ids = [k for k, v in iteritems(self.work) if is_unclaimed(v)] + if unclaimed_work_ids: + next_work_id = random.choice(unclaimed_work_ids) + else: + return None + try: + with client.transaction() as transaction: + work_key = client.key( + KIND_WORK_TYPE, self._work_type_entity_id, KIND_WORK, next_work_id + ) + work_entity = client.get(work_key, transaction=transaction) + if not is_unclaimed(work_entity): + return None + work_entity["claimed_worker_id"] = worker_id + work_entity["claimed_worker_start_time"] = get_integer_time() + transaction.put(work_entity) + except Exception: + return None + return next_work_id + + def update_work_as_completed( + self, worker_id, work_id, other_values=None, error=None + ): + """Updates work piece in datastore as completed. + + Args: + worker_id: ID of the worker which did the work + work_id: ID of the work which was done + other_values: dictionary with additonal values which should be saved + with the work piece + error: if not None then error occurred during computation of the work + piece. In such case work will be marked as completed with error. + + Returns: + whether work was successfully updated + """ + client = self._datastore_client + try: + with client.transaction() as transaction: + work_key = client.key( + KIND_WORK_TYPE, self._work_type_entity_id, KIND_WORK, work_id + ) + work_entity = client.get(work_key, transaction=transaction) + if work_entity["claimed_worker_id"] != worker_id: + return False + work_entity["is_completed"] = True + if other_values: + work_entity.update(other_values) + if error: + work_entity["error"] = text_type(error) + transaction.put(work_entity) + except Exception: + return False + return True + + def compute_work_statistics(self): + """Computes statistics from all work pieces stored in this class.""" + result = {} + for v in itervalues(self.work): + submission_id = v["submission_id"] + if submission_id not in result: + result[submission_id] = { + "completed": 0, + "num_errors": 0, + "error_messages": set(), + "eval_times": [], + "min_eval_time": None, + "max_eval_time": None, + "mean_eval_time": None, + "median_eval_time": None, + } + if not v["is_completed"]: + continue + result[submission_id]["completed"] += 1 + if "error" in v and v["error"]: + result[submission_id]["num_errors"] += 1 + result[submission_id]["error_messages"].add(v["error"]) + else: + result[submission_id]["eval_times"].append(float(v["elapsed_time"])) + for v in itervalues(result): + if v["eval_times"]: + v["min_eval_time"] = np.min(v["eval_times"]) + v["max_eval_time"] = np.max(v["eval_times"]) + v["mean_eval_time"] = np.mean(v["eval_times"]) + v["median_eval_time"] = np.median(v["eval_times"]) + return result + + def __str__(self): + buf = StringIO() + buf.write(u'WorkType "{0}"\n'.format(self._work_type_entity_id)) + for idx, (work_id, work_val) in enumerate(iteritems(self.work)): + if idx >= TO_STR_MAX_WORK: + buf.write(u" ...\n") + break + buf.write(u' Work "{0}"\n'.format(work_id)) + buf.write(u" {0}\n".format(str(work_val))) + return buf.getvalue() + + +class AttackWorkPieces(WorkPiecesBase): + """Subclass which represents work pieces for adversarial attacks.""" + + def __init__(self, datastore_client): + """Initializes AttackWorkPieces.""" + super(AttackWorkPieces, self).__init__( + datastore_client=datastore_client, + work_type_entity_id=ID_ATTACKS_WORK_ENTITY, + ) + + def init_from_adversarial_batches(self, adv_batches): + """Initializes work pieces from adversarial batches. + + Args: + adv_batches: dict with adversarial batches, + could be obtained as AversarialBatches.data + """ + for idx, (adv_batch_id, adv_batch_val) in enumerate(iteritems(adv_batches)): + work_id = ATTACK_WORK_ID_PATTERN.format(idx) + self.work[work_id] = { + "claimed_worker_id": None, + "claimed_worker_start_time": None, + "is_completed": False, + "error": None, + "elapsed_time": None, + "submission_id": adv_batch_val["submission_id"], + "shard_id": None, + "output_adversarial_batch_id": adv_batch_id, + } + + +class DefenseWorkPieces(WorkPiecesBase): + """Subclass which represents work pieces for adversarial defenses.""" + + def __init__(self, datastore_client): + """Initializes DefenseWorkPieces.""" + super(DefenseWorkPieces, self).__init__( + datastore_client=datastore_client, + work_type_entity_id=ID_DEFENSES_WORK_ENTITY, + ) + + def init_from_class_batches(self, class_batches, num_shards=None): + """Initializes work pieces from classification batches. + + Args: + class_batches: dict with classification batches, could be obtained + as ClassificationBatches.data + num_shards: number of shards to split data into, + if None then no sharding is done. + """ + shards_for_submissions = {} + shard_idx = 0 + for idx, (batch_id, batch_val) in enumerate(iteritems(class_batches)): + work_id = DEFENSE_WORK_ID_PATTERN.format(idx) + submission_id = batch_val["submission_id"] + shard_id = None + if num_shards: + shard_id = shards_for_submissions.get(submission_id) + if shard_id is None: + shard_id = shard_idx % num_shards + shards_for_submissions[submission_id] = shard_id + shard_idx += 1 + # Note: defense also might have following fields populated by worker: + # stat_correct, stat_error, stat_target_class, stat_num_images + self.work[work_id] = { + "claimed_worker_id": None, + "claimed_worker_start_time": None, + "is_completed": False, + "error": None, + "elapsed_time": None, + "submission_id": submission_id, + "shard_id": shard_id, + "output_classification_batch_id": batch_id, + } diff --git a/cleverhans_v3.1.0/examples/nips17_adversarial_competition/eval_infra/code/master.py b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/eval_infra/code/master.py new file mode 100644 index 000000000..01cb4b589 --- /dev/null +++ b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/eval_infra/code/master.py @@ -0,0 +1,937 @@ +"""Master which prepares work for all workers. + +Evaluation of competition is split into work pieces. One work piece is a +either evaluation of an attack on a batch of images or evaluation of a +defense on a batch of adversarial images. +Work pieces are run by workers. Master prepares work pieces for workers and +writes them to the datastore. +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import argparse +import collections +from collections import defaultdict +import csv +from io import BytesIO +import logging +import os +import pickle +import random +import time + +from six import iteritems +from six import iterkeys +from six import itervalues +from six.moves import input as input_str + +import eval_lib + + +# List of allowed sizes of adversarial perturbation +ALLOWED_EPS = [4, 8, 12, 16] + +# Batch size +DEFAULT_BATCH_SIZE = 100 + + +def print_header(text): + """Prints header with given text and frame composed of '#' characters.""" + print() + print("#" * (len(text) + 4)) + print("# " + text + " #") + print("#" * (len(text) + 4)) + print() + + +def save_dict_to_file(filename, dictionary): + """Saves dictionary as CSV file.""" + with open(filename, "w") as f: + writer = csv.writer(f) + for k, v in iteritems(dictionary): + writer.writerow([str(k), str(v)]) + + +class EvaluationMaster(object): + """Class which encapsulates logit of the master.""" + + def __init__( + self, + storage_client, + datastore_client, + round_name, + dataset_name, + blacklisted_submissions="", + results_dir="", + num_defense_shards=None, + verbose=False, + batch_size=DEFAULT_BATCH_SIZE, + max_dataset_num_images=None, + ): + """Initializes EvaluationMaster. + + Args: + storage_client: instance of eval_lib.CompetitionStorageClient + datastore_client: instance of eval_lib.CompetitionDatastoreClient + round_name: name of the current round + dataset_name: name of the dataset, 'dev' or 'final' + blacklisted_submissions: optional list of blacklisted submissions which + should not be evaluated + results_dir: local directory where results and logs should be written + num_defense_shards: optional number of defense shards + verbose: whether output should be verbose on not. If True, then methods + of this class will print some additional information which is useful + for debugging. + batch_size: batch size to use + max_dataset_num_images: maximum number of images from the dataset to use + or None if entire dataset should be used. + """ + self.storage_client = storage_client + self.datastore_client = datastore_client + self.round_name = round_name + self.dataset_name = dataset_name + self.results_dir = results_dir + if num_defense_shards: + self.num_defense_shards = int(num_defense_shards) + else: + self.num_defense_shards = None + self.verbose = verbose + self.blacklisted_submissions = [ + s.strip() for s in blacklisted_submissions.split(",") + ] + self.batch_size = batch_size + self.max_dataset_num_images = max_dataset_num_images + # init client classes + self.submissions = eval_lib.CompetitionSubmissions( + datastore_client=self.datastore_client, + storage_client=self.storage_client, + round_name=self.round_name, + ) + self.dataset_batches = eval_lib.DatasetBatches( + datastore_client=self.datastore_client, + storage_client=self.storage_client, + dataset_name=self.dataset_name, + ) + self.adv_batches = eval_lib.AversarialBatches( + datastore_client=self.datastore_client + ) + self.class_batches = eval_lib.ClassificationBatches( + datastore_client=self.datastore_client, + storage_client=self.storage_client, + round_name=self.round_name, + ) + self.attack_work = eval_lib.AttackWorkPieces( + datastore_client=self.datastore_client + ) + self.defense_work = eval_lib.DefenseWorkPieces( + datastore_client=self.datastore_client + ) + + def ask_when_work_is_populated(self, work): + """When work is already populated asks whether we should continue. + + This method prints warning message that work is populated and asks + whether user wants to continue or not. + + Args: + work: instance of WorkPiecesBase + + Returns: + True if we should continue and populate datastore, False if we should stop + """ + work.read_all_from_datastore() + if work.work: + print( + "Work is already written to datastore.\n" + "If you continue these data will be overwritten and " + "possible corrupted." + ) + inp = input_str( + "Do you want to continue? " '(type "yes" without quotes to confirm): ' + ) + return inp == "yes" + else: + return True + + def prepare_attacks(self): + """Prepares all data needed for evaluation of attacks.""" + print_header("PREPARING ATTACKS DATA") + # verify that attacks data not written yet + if not self.ask_when_work_is_populated(self.attack_work): + return + self.attack_work = eval_lib.AttackWorkPieces( + datastore_client=self.datastore_client + ) + # prepare submissions + print_header("Initializing submissions") + self.submissions.init_from_storage_write_to_datastore() + if self.verbose: + print(self.submissions) + # prepare dataset batches + print_header("Initializing dataset batches") + self.dataset_batches.init_from_storage_write_to_datastore( + batch_size=self.batch_size, + allowed_epsilon=ALLOWED_EPS, + skip_image_ids=[], + max_num_images=self.max_dataset_num_images, + ) + if self.verbose: + print(self.dataset_batches) + # prepare adversarial batches + print_header("Initializing adversarial batches") + self.adv_batches.init_from_dataset_and_submissions_write_to_datastore( + dataset_batches=self.dataset_batches, + attack_submission_ids=self.submissions.get_all_attack_ids(), + ) + if self.verbose: + print(self.adv_batches) + # prepare work pieces + print_header("Preparing attack work pieces") + self.attack_work.init_from_adversarial_batches(self.adv_batches.data) + self.attack_work.write_all_to_datastore() + if self.verbose: + print(self.attack_work) + + def prepare_defenses(self): + """Prepares all data needed for evaluation of defenses.""" + print_header("PREPARING DEFENSE DATA") + # verify that defense data not written yet + if not self.ask_when_work_is_populated(self.defense_work): + return + self.defense_work = eval_lib.DefenseWorkPieces( + datastore_client=self.datastore_client + ) + # load results of attacks + self.submissions.init_from_datastore() + self.dataset_batches.init_from_datastore() + self.adv_batches.init_from_datastore() + self.attack_work.read_all_from_datastore() + # populate classification results + print_header("Initializing classification batches") + self.class_batches.init_from_adversarial_batches_write_to_datastore( + self.submissions, self.adv_batches + ) + if self.verbose: + print(self.class_batches) + # populate work pieces + print_header("Preparing defense work pieces") + self.defense_work.init_from_class_batches( + self.class_batches.data, num_shards=self.num_defense_shards + ) + self.defense_work.write_all_to_datastore() + if self.verbose: + print(self.defense_work) + + def _save_work_results(self, run_stats, scores, num_processed_images, filename): + """Saves statistics about each submission. + + Saved statistics include score; number of completed and failed batches; + min, max, average and median time needed to run one batch. + + Args: + run_stats: dictionary with runtime statistics for submissions, + can be generated by WorkPiecesBase.compute_work_statistics + scores: dictionary mapping submission ids to scores + num_processed_images: dictionary with number of successfully processed + images by each submission, one of the outputs of + ClassificationBatches.compute_classification_results + filename: output filename + """ + with open(filename, "w") as f: + writer = csv.writer(f) + writer.writerow( + [ + "SubmissionID", + "ExternalSubmissionId", + "Score", + "CompletedBatches", + "BatchesWithError", + "ProcessedImages", + "MinEvalTime", + "MaxEvalTime", + "MedianEvalTime", + "MeanEvalTime", + "ErrorMsg", + ] + ) + for submission_id in sorted(iterkeys(run_stats)): + stat = run_stats.get( + submission_id, collections.defaultdict(lambda: float("NaN")) + ) + external_id = self.submissions.get_external_id(submission_id) + error_msg = "" + while not error_msg and stat["error_messages"]: + error_msg = stat["error_messages"].pop() + if error_msg.startswith("Cant copy adversarial batch locally"): + error_msg = "" + writer.writerow( + [ + submission_id, + external_id, + scores.get(submission_id, None), + stat["completed"], + stat["num_errors"], + num_processed_images.get(submission_id, None), + stat["min_eval_time"], + stat["max_eval_time"], + stat["median_eval_time"], + stat["mean_eval_time"], + error_msg, + ] + ) + + def _save_sorted_results(self, run_stats, scores, image_count, filename): + """Saves sorted (by score) results of the evaluation. + + Args: + run_stats: dictionary with runtime statistics for submissions, + can be generated by WorkPiecesBase.compute_work_statistics + scores: dictionary mapping submission ids to scores + image_count: dictionary with number of images processed by submission + filename: output filename + """ + with open(filename, "w") as f: + writer = csv.writer(f) + writer.writerow( + ["SubmissionID", "ExternalTeamId", "Score", "MedianTime", "ImageCount"] + ) + + def get_second(x): + """Returns second entry of a list/tuple""" + return x[1] + + for s_id, score in sorted(iteritems(scores), key=get_second, reverse=True): + external_id = self.submissions.get_external_id(s_id) + stat = run_stats.get( + s_id, collections.defaultdict(lambda: float("NaN")) + ) + writer.writerow( + [ + s_id, + external_id, + score, + stat["median_eval_time"], + image_count[s_id], + ] + ) + + def _read_dataset_metadata(self): + """Reads dataset metadata. + + Returns: + instance of DatasetMetadata + """ + blob = self.storage_client.get_blob( + "dataset/" + self.dataset_name + "_dataset.csv" + ) + buf = BytesIO() + blob.download_to_file(buf) + buf.seek(0) + return eval_lib.DatasetMetadata(buf) + + def compute_results(self): + """Computes results (scores, stats, etc...) of competition evaluation. + + Results are saved into output directory (self.results_dir). + Also this method saves all intermediate data into output directory as well, + so it can resume computation if it was interrupted for some reason. + This is useful because computatin of resuls could take many minutes. + """ + # read all data + logging.info("Reading data from datastore") + dataset_meta = self._read_dataset_metadata() + self.submissions.init_from_datastore() + self.dataset_batches.init_from_datastore() + self.adv_batches.init_from_datastore() + self.attack_work.read_all_from_datastore() + + if os.path.exists(os.path.join(self.results_dir, "defense_work.dump")): + with open(os.path.join(self.results_dir, "defense_work.dump")) as f: + self.defense_work.deserialize(f) + else: + self.defense_work.read_all_from_datastore() + with open(os.path.join(self.results_dir, "defense_work.dump"), "w") as f: + self.defense_work.serialize(f) + + if os.path.exists(os.path.join(self.results_dir, "class_batches.dump")): + with open(os.path.join(self.results_dir, "class_batches.dump")) as f: + self.class_batches.deserialize(f) + else: + self.class_batches.init_from_datastore() + with open(os.path.join(self.results_dir, "class_batches.dump"), "w") as f: + self.class_batches.serialize(f) + + # process data + logging.info("Processing classification results") + count_adv_images = self.adv_batches.count_generated_adv_examples() + intermediate_files = [ + "acc_matrix.dump", + "error_matrix.dump", + "hit_tc_matrix.dump", + "classified_images_count.dump", + ] + if all( + [ + os.path.exists(os.path.join(self.results_dir, fname)) + for fname in intermediate_files + ] + ): + with open(os.path.join(self.results_dir, "acc_matrix.dump")) as f: + acc_matrix = pickle.load(f) + with open(os.path.join(self.results_dir, "error_matrix.dump")) as f: + error_matrix = pickle.load(f) + with open(os.path.join(self.results_dir, "hit_tc_matrix.dump")) as f: + hit_tc_matrix = pickle.load(f) + with open( + os.path.join(self.results_dir, "classified_images_count.dump") + ) as f: + classified_images_count = pickle.load(f) + else: + ( + acc_matrix, + error_matrix, + hit_tc_matrix, + classified_images_count, + ) = self.class_batches.compute_classification_results( + self.adv_batches, self.dataset_batches, dataset_meta, self.defense_work + ) + with open(os.path.join(self.results_dir, "acc_matrix.dump"), "w") as f: + pickle.dump(acc_matrix, f) + with open(os.path.join(self.results_dir, "error_matrix.dump"), "w") as f: + pickle.dump(error_matrix, f) + with open(os.path.join(self.results_dir, "hit_tc_matrix.dump"), "w") as f: + pickle.dump(hit_tc_matrix, f) + with open( + os.path.join(self.results_dir, "classified_images_count.dump"), "w" + ) as f: + pickle.dump(classified_images_count, f) + + # compute attacks and defenses which will be used for scoring + logging.info("Computing attacks and defenses which are used for scoring") + expected_num_adv_images = self.dataset_batches.count_num_images() + attacks_to_use = [ + k + for k, v in iteritems(count_adv_images) + if ( + (v == expected_num_adv_images) + and (k not in self.blacklisted_submissions) + ) + ] + + total_num_adversarial = sum(itervalues(count_adv_images)) + defenses_to_use = [ + k + for k, v in iteritems(classified_images_count) + if ( + (v == total_num_adversarial) and (k not in self.blacklisted_submissions) + ) + ] + + logging.info( + "Expected number of adversarial images: %d", expected_num_adv_images + ) + logging.info( + "Number of attacks to use to score defenses: %d", len(attacks_to_use) + ) + logging.info( + "Expected number of classification predictions: %d", total_num_adversarial + ) + logging.info( + "Number of defenses to use to score attacks: %d", len(defenses_to_use) + ) + + save_dict_to_file( + os.path.join(self.results_dir, "count_adv_images.csv"), count_adv_images + ) + save_dict_to_file( + os.path.join(self.results_dir, "classified_images_count.csv"), + classified_images_count, + ) + + # compute scores + logging.info("Computing scores") + attack_scores = defaultdict(lambda: 0) + targeted_attack_scores = defaultdict(lambda: 0) + defense_scores = defaultdict(lambda: 0) + for defense_id in acc_matrix.dim0: + for attack_id in acc_matrix.dim1: + if attack_id in attacks_to_use: + defense_scores[defense_id] += acc_matrix[defense_id, attack_id] + if defense_id in defenses_to_use: + if attack_id in self.submissions.targeted_attacks: + targeted_attack_scores[attack_id] += hit_tc_matrix[ + defense_id, attack_id + ] + else: + attack_scores[attack_id] += error_matrix[defense_id, attack_id] + # negate results of blacklisted submissions + for s_id in self.blacklisted_submissions: + if s_id in defense_scores: + defense_scores[s_id] = -defense_scores[s_id] + if s_id in attack_scores: + attack_scores[s_id] = -attack_scores[s_id] + if s_id in targeted_attack_scores: + targeted_attack_scores[s_id] = -targeted_attack_scores[s_id] + # save results + logging.info("Saving results") + all_attack_stats = self.attack_work.compute_work_statistics() + nontargeted_attack_stats = { + k: v + for k, v in iteritems(all_attack_stats) + if k in self.submissions.attacks + } + targeted_attack_stats = { + k: v + for k, v in iteritems(all_attack_stats) + if k in self.submissions.targeted_attacks + } + defense_stats = self.defense_work.compute_work_statistics() + self._save_work_results( + nontargeted_attack_stats, + attack_scores, + count_adv_images, + os.path.join(self.results_dir, "attack_results.csv"), + ) + self._save_work_results( + targeted_attack_stats, + targeted_attack_scores, + count_adv_images, + os.path.join(self.results_dir, "targeted_attack_results.csv"), + ) + self._save_work_results( + defense_stats, + defense_scores, + classified_images_count, + os.path.join(self.results_dir, "defense_results.csv"), + ) + + self._save_sorted_results( + nontargeted_attack_stats, + attack_scores, + count_adv_images, + os.path.join(self.results_dir, "sorted_attack_results.csv"), + ) + self._save_sorted_results( + targeted_attack_stats, + targeted_attack_scores, + count_adv_images, + os.path.join(self.results_dir, "sorted_target_attack_results.csv"), + ) + self._save_sorted_results( + defense_stats, + defense_scores, + classified_images_count, + os.path.join(self.results_dir, "sorted_defense_results.csv"), + ) + + defense_id_to_name = { + k: self.submissions.get_external_id(k) + for k in iterkeys(self.submissions.defenses) + } + attack_id_to_name = { + k: self.submissions.get_external_id(k) + for k in self.submissions.get_all_attack_ids() + } + acc_matrix.save_to_file( + os.path.join(self.results_dir, "accuracy_matrix.csv"), + remap_dim0=defense_id_to_name, + remap_dim1=attack_id_to_name, + ) + error_matrix.save_to_file( + os.path.join(self.results_dir, "error_matrix.csv"), + remap_dim0=defense_id_to_name, + remap_dim1=attack_id_to_name, + ) + hit_tc_matrix.save_to_file( + os.path.join(self.results_dir, "hit_target_class_matrix.csv"), + remap_dim0=defense_id_to_name, + remap_dim1=attack_id_to_name, + ) + + save_dict_to_file( + os.path.join(self.results_dir, "defense_id_to_name.csv"), defense_id_to_name + ) + save_dict_to_file( + os.path.join(self.results_dir, "attack_id_to_name.csv"), attack_id_to_name + ) + + def _show_status_for_work(self, work): + """Shows status for given work pieces. + + Args: + work: instance of either AttackWorkPieces or DefenseWorkPieces + """ + work_count = len(work.work) + work_completed = {} + work_completed_count = 0 + for v in itervalues(work.work): + if v["is_completed"]: + work_completed_count += 1 + worker_id = v["claimed_worker_id"] + if worker_id not in work_completed: + work_completed[worker_id] = { + "completed_count": 0, + "last_update": 0.0, + } + work_completed[worker_id]["completed_count"] += 1 + work_completed[worker_id]["last_update"] = max( + work_completed[worker_id]["last_update"], + v["claimed_worker_start_time"], + ) + print("Completed {0}/{1} work".format(work_completed_count, work_count)) + for k in sorted(iterkeys(work_completed)): + last_update_time = time.strftime( + "%Y-%m-%d %H:%M:%S", time.localtime(work_completed[k]["last_update"]) + ) + print( + "Worker {0}: completed {1} last claimed work at {2}".format( + k, work_completed[k]["completed_count"], last_update_time + ) + ) + + def _export_work_errors(self, work, output_file): + """Saves errors for given work pieces into file. + + Args: + work: instance of either AttackWorkPieces or DefenseWorkPieces + output_file: name of the output file + """ + errors = set() + for v in itervalues(work.work): + if v["is_completed"] and v["error"] is not None: + errors.add(v["error"]) + with open(output_file, "w") as f: + for e in sorted(errors): + f.write(e) + f.write("\n") + + def show_status(self): + """Shows current status of competition evaluation. + + Also this method saves error messages generated by attacks and defenses + into attack_errors.txt and defense_errors.txt. + """ + print_header("Attack work statistics") + self.attack_work.read_all_from_datastore() + self._show_status_for_work(self.attack_work) + self._export_work_errors( + self.attack_work, os.path.join(self.results_dir, "attack_errors.txt") + ) + print_header("Defense work statistics") + self.defense_work.read_all_from_datastore() + self._show_status_for_work(self.defense_work) + self._export_work_errors( + self.defense_work, os.path.join(self.results_dir, "defense_errors.txt") + ) + + def cleanup_failed_attacks(self): + """Cleans up data of failed attacks.""" + print_header("Cleaning up failed attacks") + attacks_to_replace = {} + self.attack_work.read_all_from_datastore() + failed_submissions = set() + error_msg = set() + for k, v in iteritems(self.attack_work.work): + if v["error"] is not None: + attacks_to_replace[k] = dict(v) + failed_submissions.add(v["submission_id"]) + error_msg.add(v["error"]) + attacks_to_replace[k].update( + { + "claimed_worker_id": None, + "claimed_worker_start_time": None, + "is_completed": False, + "error": None, + "elapsed_time": None, + } + ) + self.attack_work.replace_work(attacks_to_replace) + print("Affected submissions:") + print(" ".join(sorted(failed_submissions))) + print("Error messages:") + print(" ".join(sorted(error_msg))) + print("") + inp = input_str('Are you sure? (type "yes" without quotes to confirm): ') + if inp != "yes": + return + self.attack_work.write_all_to_datastore() + print("Work cleaned up") + + def cleanup_attacks_with_zero_images(self): + """Cleans up data about attacks which generated zero images.""" + print_header("Cleaning up attacks which generated 0 images.") + # find out attack work to cleanup + self.adv_batches.init_from_datastore() + self.attack_work.read_all_from_datastore() + new_attack_work = {} + affected_adversarial_batches = set() + for work_id, work in iteritems(self.attack_work.work): + adv_batch_id = work["output_adversarial_batch_id"] + img_count_adv_batch = len(self.adv_batches.data[adv_batch_id]["images"]) + if (img_count_adv_batch < 100) and (work["elapsed_time"] < 500): + affected_adversarial_batches.add(adv_batch_id) + new_attack_work[work_id] = dict(work) + new_attack_work[work_id].update( + { + "claimed_worker_id": None, + "claimed_worker_start_time": None, + "is_completed": False, + "error": None, + "elapsed_time": None, + } + ) + self.attack_work.replace_work(new_attack_work) + print_header("Changes in attack works:") + print(self.attack_work) + # build list of classification batches + self.class_batches.init_from_datastore() + affected_class_batches = set() + for k, v in iteritems(self.class_batches.data): + if v["adversarial_batch_id"] in affected_adversarial_batches: + affected_class_batches.add(k) + # cleanup defense work on affected batches + self.defense_work.read_all_from_datastore() + new_defense_work = {} + for k, v in iteritems(self.defense_work.work): + if v["output_classification_batch_id"] in affected_class_batches: + new_defense_work[k] = dict(v) + new_defense_work[k].update( + { + "claimed_worker_id": None, + "claimed_worker_start_time": None, + "is_completed": False, + "error": None, + "elapsed_time": None, + "stat_correct": None, + "stat_error": None, + "stat_target_class": None, + "stat_num_images": None, + } + ) + self.defense_work.replace_work(new_defense_work) + print_header("Changes in defense works:") + print(self.defense_work) + print("") + print("Total number of affected attack work: ", len(self.attack_work)) + print("Total number of affected defense work: ", len(self.defense_work)) + inp = input_str('Are you sure? (type "yes" without quotes to confirm): ') + if inp != "yes": + return + print("Writing attacks work") + self.attack_work.write_all_to_datastore() + print("Writing defenses work") + self.defense_work.write_all_to_datastore() + print("Done!") + + def _cleanup_keys_with_confirmation(self, keys_to_delete): + """Asks confirmation and then deletes entries with keys. + + Args: + keys_to_delete: list of datastore keys for which entries should be deleted + """ + print("Round name: ", self.round_name) + print("Number of entities to be deleted: ", len(keys_to_delete)) + if not keys_to_delete: + return + if self.verbose: + print("Entities to delete:") + idx = 0 + prev_key_prefix = None + dots_printed_after_same_prefix = False + for k in keys_to_delete: + if idx >= 20: + print(" ...") + print(" ...") + break + key_prefix = ( + k.flat_path[0:1] + if k.flat_path[0] in [u"SubmissionType", u"WorkType"] + else k.flat_path[0] + ) + if prev_key_prefix == key_prefix: + if not dots_printed_after_same_prefix: + print(" ...") + dots_printed_after_same_prefix = True + else: + print(" ", k) + dots_printed_after_same_prefix = False + idx += 1 + prev_key_prefix = key_prefix + print() + inp = input_str('Are you sure? (type "yes" without quotes to confirm): ') + if inp != "yes": + return + with self.datastore_client.no_transact_batch() as batch: + for k in keys_to_delete: + batch.delete(k) + print("Data deleted") + + def cleanup_defenses(self): + """Cleans up all data about defense work in current round.""" + print_header("CLEANING UP DEFENSES DATA") + work_ancestor_key = self.datastore_client.key("WorkType", "AllDefenses") + keys_to_delete = [ + e.key + for e in self.datastore_client.query_fetch(kind=u"ClassificationBatch") + ] + [ + e.key + for e in self.datastore_client.query_fetch( + kind=u"Work", ancestor=work_ancestor_key + ) + ] + self._cleanup_keys_with_confirmation(keys_to_delete) + + def cleanup_datastore(self): + """Cleans up datastore and deletes all information about current round.""" + print_header("CLEANING UP ENTIRE DATASTORE") + kinds_to_delete = [ + u"Submission", + u"SubmissionType", + u"DatasetImage", + u"DatasetBatch", + u"AdversarialImage", + u"AdversarialBatch", + u"Work", + u"WorkType", + u"ClassificationBatch", + ] + keys_to_delete = [ + e.key + for k in kinds_to_delete + for e in self.datastore_client.query_fetch(kind=k) + ] + self._cleanup_keys_with_confirmation(keys_to_delete) + + +USAGE = """Use one of the following commands to run master: + run_master.sh attack + run_master.sh defense + run_master.sh cleanup_defenses + run_master.sh results + run_master.sh status + run_master.sh cleanup_datastore +""" + + +def main(args): + """Main function which runs master.""" + if args.blacklisted_submissions: + logging.warning("BLACKLISTED SUBMISSIONS: %s", args.blacklisted_submissions) + if args.limited_dataset: + logging.info("Using limited dataset: 3 batches * 10 images") + max_dataset_num_images = 30 + batch_size = 10 + else: + logging.info("Using full dataset. Batch size: %d", DEFAULT_BATCH_SIZE) + max_dataset_num_images = None + batch_size = DEFAULT_BATCH_SIZE + random.seed() + print("\nRound: {0}\n".format(args.round_name)) + eval_master = EvaluationMaster( + storage_client=eval_lib.CompetitionStorageClient( + args.project_id, args.storage_bucket + ), + datastore_client=eval_lib.CompetitionDatastoreClient( + args.project_id, args.round_name + ), + round_name=args.round_name, + dataset_name=args.dataset_name, + blacklisted_submissions=args.blacklisted_submissions, + results_dir=args.results_dir, + num_defense_shards=args.num_defense_shards, + verbose=args.verbose, + batch_size=batch_size, + max_dataset_num_images=max_dataset_num_images, + ) + if args.command == "attack": + eval_master.prepare_attacks() + elif args.command == "defense": + eval_master.prepare_defenses() + elif args.command == "cleanup_defenses": + eval_master.cleanup_defenses() + elif args.command == "results": + eval_master.compute_results() + elif args.command == "status": + eval_master.show_status() + elif args.command == "cleanup_datastore": + eval_master.cleanup_datastore() + elif args.command == "cleanup_failed_attacks": + eval_master.cleanup_failed_attacks() + elif args.command == "cleanup_attacks_with_zero_images": + eval_master.cleanup_attacks_with_zero_images() + else: + print("Invalid command: ", args.command) + print("") + print(USAGE) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description="Master which coordinates all workers." + ) + parser.add_argument( + "command", + help="Command to run. Possible commands include " + '"attack", "defense", "scores", "status".', + ) + parser.add_argument( + "--project_id", required=True, help="Your Google Cloud project ID." + ) + parser.add_argument( + "--storage_bucket", + required=True, + help="Cloud Storage bucket to store competition data.", + ) + parser.add_argument( + "--round_name", + default="testing-round", + required=False, + help="Name of the round.", + ) + parser.add_argument( + "--dataset_name", + default="dev", + required=False, + help="Which dataset to use, either dev or final.", + ) + parser.add_argument( + "--blacklisted_submissions", + default="", + required=False, + help="Comma separated list of blacklisted submission " "IDs.", + ) + parser.add_argument( + "--results_dir", required=True, help="Directory where to save results." + ) + parser.add_argument( + "--num_defense_shards", + default=10, + required=False, + help="Number of defense shards", + ) + parser.add_argument( + "--limited_dataset", dest="limited_dataset", action="store_true" + ) + parser.add_argument( + "--nolimited_dataset", dest="limited_dataset", action="store_false" + ) + parser.set_defaults(limited_dataset=False) + parser.add_argument("--verbose", dest="verbose", action="store_true") + parser.add_argument("--noverbose", dest="verbose", action="store_false") + parser.set_defaults(verbose=False) + parser.add_argument( + "--log_file", default="", required=False, help="Location of the logfile." + ) + master_args = parser.parse_args() + logging_args = { + "format": "%(asctime)s - %(filename)s:%(lineno)d - %(levelname)s -- %(message)s", + "level": logging.INFO, + "datefmt": "%Y-%m-%d %H:%M:%S", + } + if master_args.log_file: + logging_args["filename"] = master_args.log_file + logging_args["filemode"] = "w" + logging.basicConfig(**logging_args) + main(master_args) diff --git a/examples/nips17_adversarial_competition/eval_infra/code/run_worker_locally.sh b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/eval_infra/code/run_worker_locally.sh similarity index 100% rename from examples/nips17_adversarial_competition/eval_infra/code/run_worker_locally.sh rename to cleverhans_v3.1.0/examples/nips17_adversarial_competition/eval_infra/code/run_worker_locally.sh diff --git a/examples/nips17_adversarial_competition/eval_infra/code/start_worker_in_tmux.sh b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/eval_infra/code/start_worker_in_tmux.sh similarity index 100% rename from examples/nips17_adversarial_competition/eval_infra/code/start_worker_in_tmux.sh rename to cleverhans_v3.1.0/examples/nips17_adversarial_competition/eval_infra/code/start_worker_in_tmux.sh diff --git a/cleverhans_v3.1.0/examples/nips17_adversarial_competition/eval_infra/code/worker.py b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/eval_infra/code/worker.py new file mode 100644 index 000000000..de2a58b14 --- /dev/null +++ b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/eval_infra/code/worker.py @@ -0,0 +1,1128 @@ +"""Worker which runs all computations on Cloud VMs. + +Evaluation of competition is split into work pieces. One work piece is a +either evaluation of an attack on a batch of images or evaluation of a +defense on a batch of adversarial images. +All pieces of attack work are independent from each other and could be run +in parallel. Same for pieces of defense work - they are independent from each +other and could be run in parallel. But defense work could be run only after +all attack work is completed. + +Worker first runs all attack pieces, by querying next piece of undone work +and running it. After all attack pieces are done, worker runs all defense pieces +in a similar way. + +Before workers could be started, datastore has to be populated by master +with description of work to be done. See master.py for details. + +NOTE: Worker is designed to run on linux machine with NVidia docker +installed. Worker generally needs administrative privilege to run properly. +Also worker relies on very specific directory structure created in home +directory. That's why it's highly recommended to run worker only in VM. +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from builtins import int # long in python 2 + +import argparse +import json +import logging +import os +import random +import shutil +import subprocess +import time +import uuid + +from six import iteritems + +import eval_lib + +from cleverhans.utils import shell_call + + +# Sleep time while waiting for next available piece of work +SLEEP_TIME = 30 +SLEEP_TIME_SHORT = 10 + +# Time limit to run one pice of work +SUBMISSION_TIME_LIMIT = 500 + +# Set of local temporary directories and files +LOCAL_EVAL_ROOT_DIR = os.path.expanduser("~/competition_eval") +LOCAL_DATASET_DIR = os.path.expanduser("~/competition_eval/dataset_images") +LOCAL_SUBMISSIONS_DIR = os.path.expanduser("~/competition_eval/submissions") +LOCAL_INPUT_DIR = os.path.expanduser("~/competition_eval/input") +LOCAL_OUTPUT_DIR = os.path.expanduser("~/competition_eval/output") +LOCAL_PROCESSED_OUTPUT_DIR = os.path.expanduser("~/competition_eval/processed_output") +LOCAL_ZIPPED_OUTPUT_DIR = os.path.expanduser("~/competition_eval/zipped_output") +LOCAL_DATASET_METADATA_FILE = os.path.expanduser("~/competition_eval/dataset_meta.csv") +LOCAL_DATASET_COPY = os.path.expanduser("~/competition_data/dataset") + +# Types of submissions +TYPE_TARGETED = "targeted" +TYPE_NONTARGETED = "nontargeted" +TYPE_DEFENSE = "defense" + +# Extraction commands for various types of archive +EXTRACT_COMMAND = { + ".zip": ["unzip", "${src}", "-d", "${dst}"], + ".tar": ["tar", "xvf", "${src}", "-C", "${dst}"], + ".tar.gz": ["tar", "xvzf", "${src}", "-C", "${dst}"], +} + +# Docker binary to use +DOCKER_BINARY = "docker" +DOCKER_NVIDIA_RUNTIME = "--runtime=nvidia" + +# Names of relevant fields in submission metadata file +METADATA_CONTAINER = "container_gpu" +METADATA_ENTRY_POINT = "entry_point" +METADATA_TYPE = "type" + +# Mapping from submission type in metadata to submission type used in worker +METADATA_JSON_TYPE_TO_TYPE = { + "attack": TYPE_NONTARGETED, + "targeted_attack": TYPE_TARGETED, + "defense": TYPE_DEFENSE, +} + + +def make_directory_writable(dirname): + """Makes directory readable and writable by everybody. + + If you run something inside Docker container and it writes files, then + these files will be written as root user with restricted permissions. + So to be able to read/modify these files outside of Docker you have to change + permissions to be world readable and writable. + + Args: + dirname: name of the directory + + Returns: + True if operation was successfull + """ + shell_call( + [ + "docker", + "run", + "-v", + "{0}:/output_dir".format(dirname), + "busybox:1.27.2", + "chmod", + "-R", + "a+rwx", + "/output_dir", + ] + ) + + +def sudo_remove_dirtree(dir_name): + """Removes directory tree as a superuser. + + Args: + dir_name: name of the directory to remove. + + This function is necessary to cleanup directories created from inside a + Docker, since they usually written as a root, thus have to be removed as a + root. + """ + try: + subprocess.check_output(["sudo", "rm", "-rf", dir_name]) + except subprocess.CalledProcessError as e: + raise WorkerError("Can" "t remove directory {0}".format(dir_name), e) + + +class WorkerError(Exception): + """Error which happen during evaluation of submission. + + To simplify error handling, worker only raises this type of exception. + Exceptions of different types raised by other modules encapsulated + into WorkerError by the worker. + """ + + def __init__(self, message, exc=None): + """Initializes WorkerError. + + Args: + message: error message + exc: optional underlying exception. + """ + super(WorkerError, self).__init__() + self.msg = message + self.exc = exc + + def __str__(self): + """Returns human readable string representation of the exception.""" + if self.exc: + return "{0}\nUnderlying exception:\n{1}".format(self.msg, self.exc) + else: + return self.msg + + +def get_id_of_running_docker(container_name): + """Returns ID of running docker container.""" + return shell_call( + [DOCKER_BINARY, "ps", "-q", "--filter=name={}".format(container_name)] + ).strip() + + +def is_docker_still_running(container_name): + """Returns whether given Docker container is still running.""" + return bool(get_id_of_running_docker(container_name)) + + +def kill_docker_container(container_name): + """Kills given docker container.""" + docker_id = get_id_of_running_docker(container_name) + shell_call([DOCKER_BINARY, "stop", docker_id]) + + +class ExecutableSubmission(object): + """Base class which is used to run submissions.""" + + def __init__(self, submission_id, submissions, storage_bucket): + """Initializes ExecutableSubmission. + + Args: + submission_id: ID of the submissions + submissions: instance of CompetitionSubmissions with all submissions + storage_bucket: storage bucket where all submissions are stored + + Raises: + WorkerError: if submission was not found + """ + self.submission_id = submission_id + self.storage_bucket = storage_bucket + self.type = None + self.submission = None + if submission_id in submissions.attacks: + self.type = TYPE_NONTARGETED + self.submission = submissions.attacks[submission_id] + elif submission_id in submissions.targeted_attacks: + self.type = TYPE_TARGETED + self.submission = submissions.targeted_attacks[submission_id] + elif submission_id in submissions.defenses: + self.type = TYPE_DEFENSE + self.submission = submissions.defenses[submission_id] + else: + raise WorkerError( + 'Submission with ID "{0}" not found'.format(submission_id) + ) + self.submission_dir = None + self.extracted_submission_dir = None + + def download(self): + """Method which downloads submission to local directory.""" + # Structure of the download directory: + # submission_dir=LOCAL_SUBMISSIONS_DIR/submission_id + # submission_dir/s.ext <-- archived submission + # submission_dir/extracted <-- extracted submission + + # Check whether submission is already there + if self.extracted_submission_dir: + return + self.submission_dir = os.path.join(LOCAL_SUBMISSIONS_DIR, self.submission_id) + if os.path.isdir(self.submission_dir) and os.path.isdir( + os.path.join(self.submission_dir, "extracted") + ): + # submission already there, just re-read metadata + self.extracted_submission_dir = os.path.join( + self.submission_dir, "extracted" + ) + with open( + os.path.join(self.extracted_submission_dir, "metadata.json"), "r" + ) as f: + meta_json = json.load(f) + self.container_name = str(meta_json[METADATA_CONTAINER]) + self.entry_point = str(meta_json[METADATA_ENTRY_POINT]) + return + # figure out submission location in the Cloud and determine extractor + submission_cloud_path = os.path.join( + "gs://", self.storage_bucket, self.submission.path + ) + extract_command_tmpl = None + extension = None + for k, v in iteritems(EXTRACT_COMMAND): + if submission_cloud_path.endswith(k): + extension = k + extract_command_tmpl = v + break + if not extract_command_tmpl: + raise WorkerError("Unsupported submission extension") + # download archive + try: + os.makedirs(self.submission_dir) + tmp_extract_dir = os.path.join(self.submission_dir, "tmp") + os.makedirs(tmp_extract_dir) + download_path = os.path.join(self.submission_dir, "s" + extension) + try: + logging.info( + "Downloading submission from %s to %s", + submission_cloud_path, + download_path, + ) + shell_call(["gsutil", "cp", submission_cloud_path, download_path]) + except subprocess.CalledProcessError as e: + raise WorkerError("Can" "t copy submission locally", e) + # extract archive + try: + shell_call(extract_command_tmpl, src=download_path, dst=tmp_extract_dir) + except subprocess.CalledProcessError as e: + # proceed even if extraction returned non zero error code, + # sometimes it's just warning + logging.warning( + "Submission extraction returned non-zero error code. " + "It may be just a warning, continuing execution. " + "Error: %s", + e, + ) + try: + make_directory_writable(tmp_extract_dir) + except subprocess.CalledProcessError as e: + raise WorkerError("Can" "t make submission directory writable", e) + # determine root of the submission + tmp_root_dir = tmp_extract_dir + root_dir_content = [d for d in os.listdir(tmp_root_dir) if d != "__MACOSX"] + if len(root_dir_content) == 1 and os.path.isdir( + os.path.join(tmp_root_dir, root_dir_content[0]) + ): + tmp_root_dir = os.path.join(tmp_root_dir, root_dir_content[0]) + # move files to extract subdirectory + self.extracted_submission_dir = os.path.join( + self.submission_dir, "extracted" + ) + try: + shell_call( + ["mv", os.path.join(tmp_root_dir), self.extracted_submission_dir] + ) + except subprocess.CalledProcessError as e: + raise WorkerError("Can" "t move submission files", e) + # read metadata file + try: + with open( + os.path.join(self.extracted_submission_dir, "metadata.json"), "r" + ) as f: + meta_json = json.load(f) + except IOError as e: + raise WorkerError( + "Can" + 't read metadata.json for submission "{0}"'.format( + self.submission_id + ), + e, + ) + try: + self.container_name = str(meta_json[METADATA_CONTAINER]) + self.entry_point = str(meta_json[METADATA_ENTRY_POINT]) + type_from_meta = METADATA_JSON_TYPE_TO_TYPE[meta_json[METADATA_TYPE]] + except KeyError as e: + raise WorkerError("Invalid metadata.json file", e) + if type_from_meta != self.type: + raise WorkerError( + "Inconsistent submission type in metadata: " + + type_from_meta + + " vs " + + self.type + ) + except WorkerError as e: + self.extracted_submission_dir = None + sudo_remove_dirtree(self.submission_dir) + raise + + def temp_copy_extracted_submission(self): + """Creates a temporary copy of extracted submission. + + When executed, submission is allowed to modify it's own directory. So + to ensure that submission does not pass any data between runs, new + copy of the submission is made before each run. After a run temporary copy + of submission is deleted. + + Returns: + directory where temporary copy is located + """ + tmp_copy_dir = os.path.join(self.submission_dir, "tmp_copy") + shell_call( + ["cp", "-R", os.path.join(self.extracted_submission_dir), tmp_copy_dir] + ) + return tmp_copy_dir + + def run_without_time_limit(self, cmd): + """Runs docker command without time limit. + + Args: + cmd: list with the command line arguments which are passed to docker + binary + + Returns: + how long it took to run submission in seconds + + Raises: + WorkerError: if error occurred during execution of the submission + """ + cmd = [DOCKER_BINARY, "run", DOCKER_NVIDIA_RUNTIME] + cmd + logging.info("Docker command: %s", " ".join(cmd)) + start_time = time.time() + retval = subprocess.call(cmd) + elapsed_time_sec = int(time.time() - start_time) + logging.info("Elapsed time of attack: %d", elapsed_time_sec) + logging.info("Docker retval: %d", retval) + if retval != 0: + logging.warning("Docker returned non-zero retval: %d", retval) + raise WorkerError("Docker returned non-zero retval " + str(retval)) + return elapsed_time_sec + + def run_with_time_limit(self, cmd, time_limit=SUBMISSION_TIME_LIMIT): + """Runs docker command and enforces time limit. + + Args: + cmd: list with the command line arguments which are passed to docker + binary after run + time_limit: time limit, in seconds. Negative value means no limit. + + Returns: + how long it took to run submission in seconds + + Raises: + WorkerError: if error occurred during execution of the submission + """ + if time_limit < 0: + return self.run_without_time_limit(cmd) + container_name = str(uuid.uuid4()) + cmd = [ + DOCKER_BINARY, + "run", + DOCKER_NVIDIA_RUNTIME, + "--detach", + "--name", + container_name, + ] + cmd + logging.info("Docker command: %s", " ".join(cmd)) + logging.info("Time limit %d seconds", time_limit) + retval = subprocess.call(cmd) + start_time = time.time() + elapsed_time_sec = 0 + while is_docker_still_running(container_name): + elapsed_time_sec = int(time.time() - start_time) + if elapsed_time_sec < time_limit: + time.sleep(1) + else: + kill_docker_container(container_name) + logging.warning("Submission was killed because run out of time") + logging.info("Elapsed time of submission: %d", elapsed_time_sec) + logging.info("Docker retval: %d", retval) + if retval != 0: + logging.warning("Docker returned non-zero retval: %d", retval) + raise WorkerError("Docker returned non-zero retval " + str(retval)) + return elapsed_time_sec + + +class AttackSubmission(ExecutableSubmission): + """Class to run attack submissions.""" + + def __init__(self, submission_id, submissions, storage_bucket): + """Initializes AttackSubmission. + + Args: + submission_id: ID of the submission + submissions: instance of CompetitionSubmissions with all submissions + storage_bucket: storage bucket where all submissions are stored + + Raises: + WorkerError: if submission has incorrect type + """ + super(AttackSubmission, self).__init__( + submission_id, submissions, storage_bucket + ) + if (self.type != TYPE_TARGETED) and (self.type != TYPE_NONTARGETED): + raise WorkerError( + 'Incorrect attack type for submission "{0}"'.format(submission_id) + ) + + def run(self, input_dir, output_dir, epsilon): + """Runs attack inside Docker. + + Args: + input_dir: directory with input (dataset). + output_dir: directory where output (adversarial images) should be written. + epsilon: maximum allowed size of adversarial perturbation, + should be in range [0, 255]. + + Returns: + how long it took to run submission in seconds + """ + logging.info("Running attack %s", self.submission_id) + tmp_run_dir = self.temp_copy_extracted_submission() + cmd = [ + "--network=none", + "-m=24g", + "--cpus=3.75", + "-v", + "{0}:/input_images:ro".format(input_dir), + "-v", + "{0}:/output_images".format(output_dir), + "-v", + "{0}:/code".format(tmp_run_dir), + "-w", + "/code", + self.container_name, + "./" + self.entry_point, + "/input_images", + "/output_images", + str(epsilon), + ] + elapsed_time_sec = self.run_with_time_limit(cmd) + sudo_remove_dirtree(tmp_run_dir) + return elapsed_time_sec + + +class DefenseSubmission(ExecutableSubmission): + """Helper class to run one defense submission.""" + + def __init__(self, submission_id, submissions, storage_bucket): + """Initializes DefenseSubmission. + + Args: + submission_id: ID of the submission + submissions: instance of CompetitionSubmissions with all submissions + storage_bucket: storage bucket where all submissions are stored + + Raises: + WorkerError: if submission has incorrect type + """ + super(DefenseSubmission, self).__init__( + submission_id, submissions, storage_bucket + ) + if self.type != TYPE_DEFENSE: + raise WorkerError( + 'Incorrect defense type for submission "{0}"'.format(submission_id) + ) + + def run(self, input_dir, output_file_path): + """Runs defense inside Docker. + + Args: + input_dir: directory with input (adversarial images). + output_file_path: path of the output file. + + Returns: + how long it took to run submission in seconds + """ + logging.info("Running defense %s", self.submission_id) + tmp_run_dir = self.temp_copy_extracted_submission() + output_dir = os.path.dirname(output_file_path) + output_filename = os.path.basename(output_file_path) + cmd = [ + "--network=none", + "-m=24g", + "--cpus=3.75", + "-v", + "{0}:/input_images:ro".format(input_dir), + "-v", + "{0}:/output_data".format(output_dir), + "-v", + "{0}:/code".format(tmp_run_dir), + "-w", + "/code", + self.container_name, + "./" + self.entry_point, + "/input_images", + "/output_data/" + output_filename, + ] + elapsed_time_sec = self.run_with_time_limit(cmd) + sudo_remove_dirtree(tmp_run_dir) + return elapsed_time_sec + + +class EvaluationWorker(object): + """Class which encapsulate logit of the worker. + + Main entry point of this class is EvaluationWorker.run_work method which + performs cleanup of temporary directories, then runs + EvaluationWorker.run_attacks and EvaluationWorker.run_defenses + """ + + def __init__( + self, + worker_id, + storage_client, + datastore_client, + storage_bucket, + round_name, + dataset_name, + blacklisted_submissions="", + num_defense_shards=None, + ): + """Initializes EvaluationWorker. + + Args: + worker_id: ID of the worker + storage_client: instance of eval_lib.CompetitionStorageClient + datastore_client: instance of eval_lib.CompetitionDatastoreClient + storage_bucket: name of the Google Cloud Storage bucket where all + competition data is stored + round_name: name of the competition round + dataset_name: name of the dataset to use, typically 'dev' of 'final' + blacklisted_submissions: optional list of blacklisted submissions which + are excluded from evaluation + num_defense_shards: optional number of shards to use for evaluation of + defenses + """ + self.worker_id = int(worker_id) + self.storage_client = storage_client + self.datastore_client = datastore_client + self.storage_bucket = storage_bucket + self.round_name = round_name + self.dataset_name = dataset_name + self.blacklisted_submissions = [ + s.strip() for s in blacklisted_submissions.split(",") + ] + if num_defense_shards: + self.num_defense_shards = int(num_defense_shards) + else: + self.num_defense_shards = None + logging.info("Number of defense shards: %s", str(self.num_defense_shards)) + # init client classes + self.submissions = eval_lib.CompetitionSubmissions( + datastore_client=self.datastore_client, + storage_client=self.storage_client, + round_name=self.round_name, + ) + self.dataset_batches = eval_lib.DatasetBatches( + datastore_client=self.datastore_client, + storage_client=self.storage_client, + dataset_name=self.dataset_name, + ) + self.adv_batches = eval_lib.AversarialBatches( + datastore_client=self.datastore_client + ) + self.attack_work = eval_lib.AttackWorkPieces( + datastore_client=self.datastore_client + ) + self.defense_work = eval_lib.DefenseWorkPieces( + datastore_client=self.datastore_client + ) + self.class_batches = eval_lib.ClassificationBatches( + datastore_client=self.datastore_client, + storage_client=self.storage_client, + round_name=self.round_name, + ) + # whether data was initialized + self.attacks_data_initialized = False + self.defenses_data_initialized = False + # dataset metadata + self.dataset_meta = None + + def read_dataset_metadata(self): + """Read `dataset_meta` field from bucket""" + if self.dataset_meta: + return + shell_call( + [ + "gsutil", + "cp", + "gs://" + + self.storage_client.bucket_name + + "/" + + "dataset/" + + self.dataset_name + + "_dataset.csv", + LOCAL_DATASET_METADATA_FILE, + ] + ) + with open(LOCAL_DATASET_METADATA_FILE, "r") as f: + self.dataset_meta = eval_lib.DatasetMetadata(f) + + def fetch_attacks_data(self): + """Initializes data necessary to execute attacks. + + This method could be called multiple times, only first call does + initialization, subsequent calls are noop. + """ + if self.attacks_data_initialized: + return + # init data from datastore + self.submissions.init_from_datastore() + self.dataset_batches.init_from_datastore() + self.adv_batches.init_from_datastore() + # copy dataset locally + if not os.path.exists(LOCAL_DATASET_DIR): + os.makedirs(LOCAL_DATASET_DIR) + eval_lib.download_dataset( + self.storage_client, + self.dataset_batches, + LOCAL_DATASET_DIR, + os.path.join(LOCAL_DATASET_COPY, self.dataset_name, "images"), + ) + # download dataset metadata + self.read_dataset_metadata() + # mark as initialized + self.attacks_data_initialized = True + + def run_attack_work(self, work_id): + """Runs one attack work. + + Args: + work_id: ID of the piece of work to run + + Returns: + elapsed_time_sec, submission_id - elapsed time and id of the submission + + Raises: + WorkerError: if error occurred during execution. + """ + adv_batch_id = self.attack_work.work[work_id]["output_adversarial_batch_id"] + adv_batch = self.adv_batches[adv_batch_id] + dataset_batch_id = adv_batch["dataset_batch_id"] + submission_id = adv_batch["submission_id"] + epsilon = self.dataset_batches[dataset_batch_id]["epsilon"] + logging.info( + "Attack work piece: " + 'dataset_batch_id="%s" submission_id="%s" ' + "epsilon=%d", + dataset_batch_id, + submission_id, + epsilon, + ) + if submission_id in self.blacklisted_submissions: + raise WorkerError("Blacklisted submission") + # get attack + attack = AttackSubmission(submission_id, self.submissions, self.storage_bucket) + attack.download() + # prepare input + input_dir = os.path.join(LOCAL_DATASET_DIR, dataset_batch_id) + if attack.type == TYPE_TARGETED: + # prepare file with target classes + target_class_filename = os.path.join(input_dir, "target_class.csv") + self.dataset_meta.save_target_classes_for_batch( + target_class_filename, self.dataset_batches, dataset_batch_id + ) + # prepare output directory + if os.path.exists(LOCAL_OUTPUT_DIR): + sudo_remove_dirtree(LOCAL_OUTPUT_DIR) + os.mkdir(LOCAL_OUTPUT_DIR) + if os.path.exists(LOCAL_PROCESSED_OUTPUT_DIR): + shutil.rmtree(LOCAL_PROCESSED_OUTPUT_DIR) + os.mkdir(LOCAL_PROCESSED_OUTPUT_DIR) + if os.path.exists(LOCAL_ZIPPED_OUTPUT_DIR): + shutil.rmtree(LOCAL_ZIPPED_OUTPUT_DIR) + os.mkdir(LOCAL_ZIPPED_OUTPUT_DIR) + # run attack + elapsed_time_sec = attack.run(input_dir, LOCAL_OUTPUT_DIR, epsilon) + if attack.type == TYPE_TARGETED: + # remove target class file + os.remove(target_class_filename) + # enforce epsilon and compute hashes + image_hashes = eval_lib.enforce_epsilon_and_compute_hash( + input_dir, LOCAL_OUTPUT_DIR, LOCAL_PROCESSED_OUTPUT_DIR, epsilon + ) + if not image_hashes: + logging.warning("No images saved by the attack.") + return elapsed_time_sec, submission_id + # write images back to datastore + # rename images and add information to adversarial batch + for clean_image_id, hash_val in iteritems(image_hashes): + # we will use concatenation of batch_id and image_id + # as adversarial image id and as a filename of adversarial images + adv_img_id = adv_batch_id + "_" + clean_image_id + # rename the image + os.rename( + os.path.join(LOCAL_PROCESSED_OUTPUT_DIR, clean_image_id + ".png"), + os.path.join(LOCAL_PROCESSED_OUTPUT_DIR, adv_img_id + ".png"), + ) + # populate values which will be written to datastore + image_path = "{0}/adversarial_images/{1}/{1}.zip/{2}.png".format( + self.round_name, adv_batch_id, adv_img_id + ) + # u'' + foo is a a python 2/3 compatible way of casting foo to unicode + adv_batch["images"][adv_img_id] = { + "clean_image_id": u"" + str(clean_image_id), + "image_path": u"" + str(image_path), + "image_hash": u"" + str(hash_val), + } + # archive all images and copy to storage + zipped_images_filename = os.path.join( + LOCAL_ZIPPED_OUTPUT_DIR, adv_batch_id + ".zip" + ) + try: + logging.debug( + "Compressing adversarial images to %s", zipped_images_filename + ) + shell_call( + ["zip", "-j", "-r", zipped_images_filename, LOCAL_PROCESSED_OUTPUT_DIR] + ) + except subprocess.CalledProcessError as e: + raise WorkerError("Can" "t make archive from adversarial iamges", e) + # upload archive to storage + dst_filename = "{0}/adversarial_images/{1}/{1}.zip".format( + self.round_name, adv_batch_id + ) + logging.debug("Copying archive with adversarial images to %s", dst_filename) + self.storage_client.new_blob(dst_filename).upload_from_filename( + zipped_images_filename + ) + # writing adv batch to datastore + logging.debug("Writing adversarial batch to datastore") + self.adv_batches.write_single_batch_images_to_datastore(adv_batch_id) + return elapsed_time_sec, submission_id + + def run_attacks(self): + """Method which evaluates all attack work. + + In a loop this method queries not completed attack work, picks one + attack work and runs it. + """ + logging.info("******** Start evaluation of attacks ********") + prev_submission_id = None + while True: + # wait until work is available + self.attack_work.read_all_from_datastore() + if not self.attack_work.work: + logging.info("Work is not populated, waiting...") + time.sleep(SLEEP_TIME) + continue + if self.attack_work.is_all_work_competed(): + logging.info("All attack work completed.") + break + # download all attacks data and dataset + self.fetch_attacks_data() + # pick piece of work + work_id = self.attack_work.try_pick_piece_of_work( + self.worker_id, submission_id=prev_submission_id + ) + if not work_id: + logging.info("Failed to pick work, waiting...") + time.sleep(SLEEP_TIME_SHORT) + continue + logging.info("Selected work_id: %s", work_id) + # execute work + try: + elapsed_time_sec, prev_submission_id = self.run_attack_work(work_id) + logging.info("Work %s is done", work_id) + # indicate that work is completed + is_work_update = self.attack_work.update_work_as_completed( + self.worker_id, + work_id, + other_values={"elapsed_time": elapsed_time_sec}, + ) + except WorkerError as e: + logging.info("Failed to run work:\n%s", str(e)) + is_work_update = self.attack_work.update_work_as_completed( + self.worker_id, work_id, error=str(e) + ) + if not is_work_update: + logging.warning( + "Can" 't update work "%s" as completed by worker %d', + work_id, + self.worker_id, + ) + logging.info("******** Finished evaluation of attacks ********") + + def fetch_defense_data(self): + """Lazy initialization of data necessary to execute defenses.""" + if self.defenses_data_initialized: + return + logging.info("Fetching defense data from datastore") + # init data from datastore + self.submissions.init_from_datastore() + self.dataset_batches.init_from_datastore() + self.adv_batches.init_from_datastore() + # read dataset metadata + self.read_dataset_metadata() + # mark as initialized + self.defenses_data_initialized = True + + def run_defense_work(self, work_id): + """Runs one defense work. + + Args: + work_id: ID of the piece of work to run + + Returns: + elapsed_time_sec, submission_id - elapsed time and id of the submission + + Raises: + WorkerError: if error occurred during execution. + """ + class_batch_id = self.defense_work.work[work_id][ + "output_classification_batch_id" + ] + class_batch = self.class_batches.read_batch_from_datastore(class_batch_id) + adversarial_batch_id = class_batch["adversarial_batch_id"] + submission_id = class_batch["submission_id"] + cloud_result_path = class_batch["result_path"] + logging.info( + "Defense work piece: " 'adversarial_batch_id="%s" submission_id="%s"', + adversarial_batch_id, + submission_id, + ) + if submission_id in self.blacklisted_submissions: + raise WorkerError("Blacklisted submission") + # get defense + defense = DefenseSubmission( + submission_id, self.submissions, self.storage_bucket + ) + defense.download() + # prepare input - copy adversarial batch locally + input_dir = os.path.join(LOCAL_INPUT_DIR, adversarial_batch_id) + if os.path.exists(input_dir): + sudo_remove_dirtree(input_dir) + os.makedirs(input_dir) + try: + shell_call( + [ + "gsutil", + "-m", + "cp", + # typical location of adv batch: + # testing-round/adversarial_images/ADVBATCH000/ + os.path.join( + "gs://", + self.storage_bucket, + self.round_name, + "adversarial_images", + adversarial_batch_id, + "*", + ), + input_dir, + ] + ) + adv_images_files = os.listdir(input_dir) + if (len(adv_images_files) == 1) and adv_images_files[0].endswith(".zip"): + logging.info( + "Adversarial batch is in zip archive %s", adv_images_files[0] + ) + shell_call( + [ + "unzip", + os.path.join(input_dir, adv_images_files[0]), + "-d", + input_dir, + ] + ) + os.remove(os.path.join(input_dir, adv_images_files[0])) + adv_images_files = os.listdir(input_dir) + logging.info("%d adversarial images copied", len(adv_images_files)) + except (subprocess.CalledProcessError, IOError) as e: + raise WorkerError("Can" "t copy adversarial batch locally", e) + # prepare output directory + if os.path.exists(LOCAL_OUTPUT_DIR): + sudo_remove_dirtree(LOCAL_OUTPUT_DIR) + os.mkdir(LOCAL_OUTPUT_DIR) + output_filname = os.path.join(LOCAL_OUTPUT_DIR, "result.csv") + # run defense + elapsed_time_sec = defense.run(input_dir, output_filname) + # evaluate defense result + batch_result = eval_lib.analyze_one_classification_result( + storage_client=None, + file_path=output_filname, + adv_batch=self.adv_batches.data[adversarial_batch_id], + dataset_batches=self.dataset_batches, + dataset_meta=self.dataset_meta, + ) + # copy result of the defense into storage + try: + shell_call( + [ + "gsutil", + "cp", + output_filname, + os.path.join("gs://", self.storage_bucket, cloud_result_path), + ] + ) + except subprocess.CalledProcessError as e: + raise WorkerError("Can" "t result to Cloud Storage", e) + return elapsed_time_sec, submission_id, batch_result + + def run_defenses(self): + """Method which evaluates all defense work. + + In a loop this method queries not completed defense work, + picks one defense work and runs it. + """ + logging.info("******** Start evaluation of defenses ********") + prev_submission_id = None + need_reload_work = True + while True: + # wait until work is available + if need_reload_work: + if self.num_defense_shards: + shard_with_work = self.defense_work.read_undone_from_datastore( + shard_id=(self.worker_id % self.num_defense_shards), + num_shards=self.num_defense_shards, + ) + else: + shard_with_work = self.defense_work.read_undone_from_datastore() + logging.info( + "Loaded %d records of undone work from shard %s", + len(self.defense_work), + str(shard_with_work), + ) + if not self.defense_work.work: + logging.info("Work is not populated, waiting...") + time.sleep(SLEEP_TIME) + continue + if self.defense_work.is_all_work_competed(): + logging.info("All defense work completed.") + break + # download all defense data and dataset + self.fetch_defense_data() + need_reload_work = False + # pick piece of work + work_id = self.defense_work.try_pick_piece_of_work( + self.worker_id, submission_id=prev_submission_id + ) + if not work_id: + need_reload_work = True + logging.info("Failed to pick work, waiting...") + time.sleep(SLEEP_TIME_SHORT) + continue + logging.info("Selected work_id: %s", work_id) + # execute work + try: + ( + elapsed_time_sec, + prev_submission_id, + batch_result, + ) = self.run_defense_work(work_id) + logging.info("Work %s is done", work_id) + # indicate that work is completed + is_work_update = self.defense_work.update_work_as_completed( + self.worker_id, + work_id, + other_values={ + "elapsed_time": elapsed_time_sec, + "stat_correct": batch_result[0], + "stat_error": batch_result[1], + "stat_target_class": batch_result[2], + "stat_num_images": batch_result[3], + }, + ) + except WorkerError as e: + logging.info("Failed to run work:\n%s", str(e)) + if str(e).startswith("Docker returned non-zero retval"): + logging.info("Running nvidia-docker to ensure that GPU works") + shell_call( + ["nvidia-docker", "run", "--rm", "nvidia/cuda", "nvidia-smi"] + ) + is_work_update = self.defense_work.update_work_as_completed( + self.worker_id, work_id, error=str(e) + ) + if not is_work_update: + logging.warning( + "Can" 't update work "%s" as completed by worker %d', + work_id, + self.worker_id, + ) + need_reload_work = True + logging.info("******** Finished evaluation of defenses ********") + + def run_work(self): + """Run attacks and defenses""" + if os.path.exists(LOCAL_EVAL_ROOT_DIR): + sudo_remove_dirtree(LOCAL_EVAL_ROOT_DIR) + self.run_attacks() + self.run_defenses() + + +def main(args): + """Main function which runs worker.""" + title = "## Starting evaluation of round {0} ##".format(args.round_name) + logging.info( + "\n" + + "#" * len(title) + + "\n" + + "#" * len(title) + + "\n" + + "##" + + " " * (len(title) - 2) + + "##" + + "\n" + + title + + "\n" + + "#" * len(title) + + "\n" + + "#" * len(title) + + "\n" + + "##" + + " " * (len(title) - 2) + + "##" + + "\n" + ) + if args.blacklisted_submissions: + logging.warning("BLACKLISTED SUBMISSIONS: %s", args.blacklisted_submissions) + random.seed() + logging.info("Running nvidia-docker to ensure that GPU works") + shell_call( + ["docker", "run", "--runtime=nvidia", "--rm", "nvidia/cuda", "nvidia-smi"] + ) + eval_worker = EvaluationWorker( + worker_id=args.worker_id, + storage_client=eval_lib.CompetitionStorageClient( + args.project_id, args.storage_bucket + ), + datastore_client=eval_lib.CompetitionDatastoreClient( + args.project_id, args.round_name + ), + storage_bucket=args.storage_bucket, + round_name=args.round_name, + dataset_name=args.dataset_name, + blacklisted_submissions=args.blacklisted_submissions, + num_defense_shards=args.num_defense_shards, + ) + eval_worker.run_work() + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Worker which executes work.") + parser.add_argument( + "--worker_id", required=True, type=int, help="Numerical ID of the worker." + ) + parser.add_argument( + "--project_id", required=True, help="Your Google Cloud project ID." + ) + parser.add_argument( + "--storage_bucket", + required=True, + help="Cloud Storage bucket to store competition data.", + ) + parser.add_argument( + "--round_name", + default="testing-round", + required=False, + help="Name of the round.", + ) + parser.add_argument( + "--dataset_name", + default="dev", + required=False, + help="Which dataset to use, either dev or final.", + ) + parser.add_argument( + "--blacklisted_submissions", + default="", + required=False, + help="Comma separated list of blacklisted submission " "IDs.", + ) + parser.add_argument( + "--num_defense_shards", + default=10, + required=False, + help="Number of defense shards", + ) + parser.add_argument( + "--log_file", default="", required=False, help="Location of the logfile." + ) + worker_args = parser.parse_args() + logging_args = { + "format": "%(asctime)s - %(filename)s:%(lineno)d - %(levelname)s -- %(message)s", + "level": logging.INFO, + "datefmt": "%Y-%m-%d %H:%M:%S", + } + if worker_args.log_file: + logging_args["filename"] = worker_args.log_file + logging_args["filemode"] = "a" + logging.basicConfig(**logging_args) + main(worker_args) diff --git a/examples/nips17_adversarial_competition/eval_infra/run_master.sh b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/eval_infra/run_master.sh similarity index 100% rename from examples/nips17_adversarial_competition/eval_infra/run_master.sh rename to cleverhans_v3.1.0/examples/nips17_adversarial_competition/eval_infra/run_master.sh diff --git a/examples/nips17_adversarial_competition/eval_infra/scripts/config.sh b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/eval_infra/scripts/config.sh similarity index 100% rename from examples/nips17_adversarial_competition/eval_infra/scripts/config.sh rename to cleverhans_v3.1.0/examples/nips17_adversarial_competition/eval_infra/scripts/config.sh diff --git a/examples/nips17_adversarial_competition/eval_infra/scripts/copy_baselines.sh b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/eval_infra/scripts/copy_baselines.sh similarity index 100% rename from examples/nips17_adversarial_competition/eval_infra/scripts/copy_baselines.sh rename to cleverhans_v3.1.0/examples/nips17_adversarial_competition/eval_infra/scripts/copy_baselines.sh diff --git a/examples/nips17_adversarial_competition/eval_infra/scripts/copy_dataset_to_vm.sh b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/eval_infra/scripts/copy_dataset_to_vm.sh similarity index 100% rename from examples/nips17_adversarial_competition/eval_infra/scripts/copy_dataset_to_vm.sh rename to cleverhans_v3.1.0/examples/nips17_adversarial_competition/eval_infra/scripts/copy_dataset_to_vm.sh diff --git a/examples/nips17_adversarial_competition/eval_infra/scripts/copy_eval_infra_to_vm.sh b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/eval_infra/scripts/copy_eval_infra_to_vm.sh similarity index 100% rename from examples/nips17_adversarial_competition/eval_infra/scripts/copy_eval_infra_to_vm.sh rename to cleverhans_v3.1.0/examples/nips17_adversarial_competition/eval_infra/scripts/copy_eval_infra_to_vm.sh diff --git a/examples/nips17_adversarial_competition/eval_infra/scripts/create_workers.sh b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/eval_infra/scripts/create_workers.sh similarity index 100% rename from examples/nips17_adversarial_competition/eval_infra/scripts/create_workers.sh rename to cleverhans_v3.1.0/examples/nips17_adversarial_competition/eval_infra/scripts/create_workers.sh diff --git a/examples/nips17_adversarial_competition/eval_infra/scripts/prepare_virtualenv.sh b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/eval_infra/scripts/prepare_virtualenv.sh similarity index 100% rename from examples/nips17_adversarial_competition/eval_infra/scripts/prepare_virtualenv.sh rename to cleverhans_v3.1.0/examples/nips17_adversarial_competition/eval_infra/scripts/prepare_virtualenv.sh diff --git a/examples/nips17_adversarial_competition/eval_infra/scripts/scp_cloud_vm.sh b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/eval_infra/scripts/scp_cloud_vm.sh similarity index 100% rename from examples/nips17_adversarial_competition/eval_infra/scripts/scp_cloud_vm.sh rename to cleverhans_v3.1.0/examples/nips17_adversarial_competition/eval_infra/scripts/scp_cloud_vm.sh diff --git a/examples/nips17_adversarial_competition/eval_infra/scripts/start_workers.sh b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/eval_infra/scripts/start_workers.sh similarity index 100% rename from examples/nips17_adversarial_competition/eval_infra/scripts/start_workers.sh rename to cleverhans_v3.1.0/examples/nips17_adversarial_competition/eval_infra/scripts/start_workers.sh diff --git a/cleverhans_v3.1.0/examples/nips17_adversarial_competition/eval_infra/validation_tool/validate_and_copy_submissions.py b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/eval_infra/validation_tool/validate_and_copy_submissions.py new file mode 100644 index 000000000..9e970e394 --- /dev/null +++ b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/eval_infra/validation_tool/validate_and_copy_submissions.py @@ -0,0 +1,289 @@ +r"""Tool to validate all submission and copy them to proper location. + +Usage: + python validate_and_copy_submissions.py \ + --source_dir=SOURCE \ + --target_dir=TARGET \ + [--containers_file=CONTAINER_FILE] \ + [--log_file=LOG_FILE] \ + [--nouse_gpu] \ + [--nocopy] + +Where: + SOURCE - Google Cloud Storage directory with all submissions to verify. + Submissions in the source directory could be structured any way, this tool + will go though all subdirectories and look for zip, tar and tar.gz archives. + TARGET - Target directory in Google Cloud Storage, typically it should be + gs://${GOOGLE_CLOUD_STORAGE_BUCKET}/${ROUND_NAME}/submissions + CONTAINER_FILE - optional name of the file where to save list of all Docker + containers used by all submissions. + LOG_FILE - optional filename of the logfile. + --nouse_gpu - if argument is provided then submission will be run on CPU, + otherwise will be run on GPU. + --nocopy - if argument is provided then submissions will be validated, + but no copy will be performed. + +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import argparse +import csv +import logging +import os +import random +import shutil +import subprocess +import tempfile +from six import iteritems +import validate_submission_lib + + +ALLOWED_EXTENSIONS = [".zip", ".tar", ".tar.gz"] + +TYPE_ATTACK = "attack" +TYPE_TARGETED_ATTACK = "targeted_attack" +TYPE_DEFENSE = "defense" + +TYPE_TO_DIR = { + "attack": "nontargeted", + "targeted_attack": "targeted", + "defense": "defense", +} + + +class ValidationStats(object): + """Class which stores statistics about validation of the submissions.""" + + def __init__(self): + # dictionary mapping submission_type to tuple (num_success, num_fail) + self.stats = {} + + def _update_stat(self, submission_type, increase_success, increase_fail): + """Common method to update submission statistics.""" + stat = self.stats.get(submission_type, (0, 0)) + stat = (stat[0] + increase_success, stat[1] + increase_fail) + self.stats[submission_type] = stat + + def add_success(self, submission_type): + """Add one successfull submission of given type.""" + self._update_stat(submission_type, 1, 0) + + def add_failure(self, submission_type="unknown"): + """Add one failed submission of given type.""" + self._update_stat(submission_type, 0, 1) + + def log_stats(self): + """Print statistics into log.""" + logging.info("Validation statistics: ") + for k, v in iteritems(self.stats): + logging.info( + "%s - %d valid out of %d total submissions", k, v[0], v[0] + v[1] + ) + + +class SubmissionValidator(object): + """Helper class which performs validation of all submissions.""" + + def __init__( + self, source_dir, target_dir, temp_dir, do_copy, use_gpu, containers_file=None + ): + """Initializes SubmissionValidator. + + Args: + source_dir: source Google Cloud Storage directory with all submissions + target_dir: target Google Cloud Storage directory where to copy + submissions + temp_dir: local temporary directory + do_copy: if True then validate and copy submissions, if False then only + validate + use_gpu: if True then use GPU for validation, otherwise use CPU + containers_file: optional name of the local text file where list of + Docker containes of all submissions will be saved. + """ + self.source_dir = source_dir + self.target_dir = target_dir + self.do_copy = do_copy + self.containers_file = containers_file + self.list_of_containers = set() + self.local_id_to_path_mapping_file = os.path.join( + temp_dir, "id_to_path_mapping.csv" + ) + self.validate_dir = os.path.join(temp_dir, "validate") + self.base_validator = validate_submission_lib.SubmissionValidator( + self.validate_dir, use_gpu + ) + self.stats = ValidationStats() + self.download_dir = os.path.join(temp_dir, "download") + self.cur_submission_idx = 0 + self.id_to_path_mapping = {} + + def copy_submission_locally(self, cloud_path): + """Copies submission from Google Cloud Storage to local directory. + + Args: + cloud_path: path of the submission in Google Cloud Storage + + Returns: + name of the local file where submission is copied to + """ + local_path = os.path.join(self.download_dir, os.path.basename(cloud_path)) + cmd = ["gsutil", "cp", cloud_path, local_path] + if subprocess.call(cmd) != 0: + logging.error("Can't copy submission locally") + return None + return local_path + + def copy_submission_to_destination(self, src_filename, dst_subdir, submission_id): + """Copies submission to target directory. + + Args: + src_filename: source filename of the submission + dst_subdir: subdirectory of the target directory where submission should + be copied to + submission_id: ID of the submission, will be used as a new + submission filename (before extension) + """ + + extension = [e for e in ALLOWED_EXTENSIONS if src_filename.endswith(e)] + if len(extension) != 1: + logging.error("Invalid submission extension: %s", src_filename) + return + dst_filename = os.path.join( + self.target_dir, dst_subdir, submission_id + extension[0] + ) + cmd = ["gsutil", "cp", src_filename, dst_filename] + if subprocess.call(cmd) != 0: + logging.error("Can't copy submission to destination") + else: + logging.info("Submission copied to: %s", dst_filename) + + def validate_and_copy_one_submission(self, submission_path): + """Validates one submission and copies it to target directory. + + Args: + submission_path: path in Google Cloud Storage of the submission file + """ + if os.path.exists(self.download_dir): + shutil.rmtree(self.download_dir) + os.makedirs(self.download_dir) + if os.path.exists(self.validate_dir): + shutil.rmtree(self.validate_dir) + os.makedirs(self.validate_dir) + logging.info( + "\n" + ("#" * 80) + "\n# Processing submission: %s\n" + "#" * 80, + submission_path, + ) + local_path = self.copy_submission_locally(submission_path) + metadata = self.base_validator.validate_submission(local_path) + if not metadata: + logging.error('Submission "%s" is INVALID', submission_path) + self.stats.add_failure() + return + submission_type = metadata["type"] + container_name = metadata["container_gpu"] + logging.info('Submission "%s" is VALID', submission_path) + self.list_of_containers.add(container_name) + self.stats.add_success(submission_type) + if self.do_copy: + submission_id = "{0:04}".format(self.cur_submission_idx) + self.cur_submission_idx += 1 + self.copy_submission_to_destination( + submission_path, TYPE_TO_DIR[submission_type], submission_id + ) + self.id_to_path_mapping[submission_id] = submission_path + + def save_id_to_path_mapping(self): + """Saves mapping from submission IDs to original filenames. + + This mapping is saved as CSV file into target directory. + """ + if not self.id_to_path_mapping: + return + with open(self.local_id_to_path_mapping_file, "w") as f: + writer = csv.writer(f) + writer.writerow(["id", "path"]) + for k, v in sorted(iteritems(self.id_to_path_mapping)): + writer.writerow([k, v]) + cmd = [ + "gsutil", + "cp", + self.local_id_to_path_mapping_file, + os.path.join(self.target_dir, "id_to_path_mapping.csv"), + ] + if subprocess.call(cmd) != 0: + logging.error("Can't copy id_to_path_mapping.csv to target directory") + + def run(self): + """Runs validation of all submissions.""" + cmd = ["gsutil", "ls", os.path.join(self.source_dir, "**")] + try: + files_list = subprocess.check_output(cmd).split("\n") + except subprocess.CalledProcessError: + logging.error("Can" "t read source directory") + all_submissions = [ + s + for s in files_list + if s.endswith(".zip") or s.endswith(".tar") or s.endswith(".tar.gz") + ] + for submission_path in all_submissions: + self.validate_and_copy_one_submission(submission_path) + self.stats.log_stats() + self.save_id_to_path_mapping() + if self.containers_file: + with open(self.containers_file, "w") as f: + f.write("\n".join(sorted(self.list_of_containers))) + + +def main(args): + """Validate all submissions and copy them into place""" + random.seed() + temp_dir = tempfile.mkdtemp() + logging.info("Created temporary directory: %s", temp_dir) + validator = SubmissionValidator( + source_dir=args.source_dir, + target_dir=args.target_dir, + temp_dir=temp_dir, + do_copy=args.copy, + use_gpu=args.use_gpu, + containers_file=args.containers_file, + ) + validator.run() + logging.info("Deleting temporary directory: %s", temp_dir) + subprocess.call(["rm", "-rf", temp_dir]) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Submission validation script.") + parser.add_argument("--source_dir", required=True, help="Source directory.") + parser.add_argument("--target_dir", required=True, help="Target directory.") + parser.add_argument( + "--log_file", default="", required=False, help="Location of the logfile." + ) + parser.add_argument( + "--containers_file", + default="", + required=False, + help="Local file with list of containers.", + ) + parser.add_argument("--copy", dest="copy", action="store_true") + parser.add_argument("--nocopy", dest="copy", action="store_false") + parser.set_defaults(copy=True) + parser.add_argument("--use_gpu", dest="use_gpu", action="store_true") + parser.add_argument("--nouse_gpu", dest="use_gpu", action="store_false") + parser.set_defaults(use_gpu=True) + command_line_args = parser.parse_args() + logging_args = { + "format": ( + "%(asctime)s - %(filename)s:%(lineno)d - %(levelname)s" " -- %(message)s" + ), + "level": logging.INFO, + "datefmt": "%Y-%m-%d %H:%M:%S", + } + if command_line_args.log_file: + logging_args["filename"] = command_line_args.log_file + logging_args["filemode"] = "w" + logging.basicConfig(**logging_args) + main(command_line_args) diff --git a/cleverhans_v3.1.0/examples/nips17_adversarial_competition/eval_infra/validation_tool/validate_submission_lib.py b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/eval_infra/validation_tool/validate_submission_lib.py new file mode 100644 index 000000000..19ab299cc --- /dev/null +++ b/cleverhans_v3.1.0/examples/nips17_adversarial_competition/eval_infra/validation_tool/validate_submission_lib.py @@ -0,0 +1,463 @@ +"""Helper library which performs validation of the submission.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from builtins import int + +import csv +import json +import logging +import os +import re +import subprocess +import sys + +import numpy as np +from PIL import Image + +from six import iteritems + + +EXTRACT_COMMAND = { + ".zip": ["unzip", "${src}", "-d", "${dst}"], + ".tar": ["tar", "xvf", "${src}", "-C", "${dst}"], + ".tar.gz": ["tar", "xvzf", "${src}", "-C", "${dst}"], +} + +ALLOWED_SUBMISSION_TYPES = ["attack", "targeted_attack", "defense"] + +REQUIRED_METADATA_JSON_FIELDS = ["entry_point", "container", "container_gpu", "type"] + +CMD_VARIABLE_RE = re.compile("^\\$\\{(\\w+)\\}$") + +BATCH_SIZE = 8 +IMAGE_NAME_PATTERN = "IMG{0:04}.png" + +ALLOWED_EPS = [4, 8, 12, 16] + +MAX_SUBMISSION_SIZE_ZIPPED = 8 * 1024 * 1024 * 1024 # 8 GiB +MAX_SUBMISSION_SIZE_UNPACKED = 16 * 1024 * 1024 * 1024 # 16 GiB +MAX_DOCKER_IMAGE_SIZE = 8 * 1024 * 1024 * 1024 # 8 GiB + + +def get_extract_command_template(filename): + """Returns extraction command based on the filename extension.""" + for k, v in iteritems(EXTRACT_COMMAND): + if filename.endswith(k): + return v + return None + + +def shell_call(command, **kwargs): + """Calls shell command with parameter substitution. + + Args: + command: command to run as a list of tokens + **kwargs: dirctionary with substitutions + + Returns: + whether command was successful, i.e. returned 0 status code + + Example of usage: + shell_call(['cp', '${A}', '${B}'], A='src_file', B='dst_file') + will call shell command: + cp src_file dst_file + """ + command = list(command) + for i in range(len(command)): + m = CMD_VARIABLE_RE.match(command[i]) + if m: + var_id = m.group(1) + if var_id in kwargs: + command[i] = kwargs[var_id] + return subprocess.call(command) == 0 + + +def make_directory_writable(dirname): + """Makes directory readable and writable by everybody. + + Args: + dirname: name of the directory + + Returns: + True if operation was successfull + + If you run something inside Docker container and it writes files, then + these files will be written as root user with restricted permissions. + So to be able to read/modify these files outside of Docker you have to change + permissions to be world readable and writable. + """ + retval = shell_call( + [ + "docker", + "run", + "-v", + "{0}:/output_dir".format(dirname), + "busybox:1.27.2", + "chmod", + "-R", + "a+rwx", + "/output_dir", + ] + ) + if not retval: + logging.error("Failed to change permissions on directory: %s", dirname) + return retval + + +def load_defense_output(filename): + """Loads output of defense from given file.""" + result = {} + with open(filename) as f: + for row in csv.reader(f): + try: + image_filename = row[0] + if not image_filename.endswith(".png"): + image_filename += ".png" + label = int(row[1]) + except (IndexError, ValueError): + continue + result[image_filename] = label + return result + + +class SubmissionValidator(object): + """Class which performs validation of the submission.""" + + def __init__(self, temp_dir, use_gpu): + """Initializes instance of SubmissionValidator. + + Args: + temp_dir: temporary working directory + use_gpu: whether to use GPU + """ + self._temp_dir = temp_dir + self._use_gpu = use_gpu + self._tmp_extracted_dir = os.path.join(self._temp_dir, "tmp_extracted") + self._extracted_submission_dir = os.path.join(self._temp_dir, "extracted") + self._sample_input_dir = os.path.join(self._temp_dir, "input") + self._sample_output_dir = os.path.join(self._temp_dir, "output") + + def _prepare_temp_dir(self): + """Cleans up and prepare temporary directory.""" + if not shell_call(["sudo", "rm", "-rf", os.path.join(self._temp_dir, "*")]): + logging.error("Failed to cleanup temporary directory.") + sys.exit(1) + # NOTE: we do not create self._extracted_submission_dir + # this is intentional because self._tmp_extracted_dir or it's subdir + # will be renames into self._extracted_submission_dir + os.mkdir(self._tmp_extracted_dir) + os.mkdir(self._sample_input_dir) + os.mkdir(self._sample_output_dir) + # make output dir world writable + shell_call(["chmod", "a+rwX", "-R", self._sample_output_dir]) + + def _extract_submission(self, filename): + """Extracts submission and moves it into self._extracted_submission_dir.""" + # verify filesize + file_size = os.path.getsize(filename) + if file_size > MAX_SUBMISSION_SIZE_ZIPPED: + logging.error( + "Submission archive size %d is exceeding limit %d", + file_size, + MAX_SUBMISSION_SIZE_ZIPPED, + ) + return False + # determime archive type + exctract_command_tmpl = get_extract_command_template(filename) + if not exctract_command_tmpl: + logging.error( + "Input file has to be zip, tar or tar.gz archive; however " "found: %s", + filename, + ) + return False + # extract archive + submission_dir = os.path.dirname(filename) + submission_basename = os.path.basename(filename) + logging.info("Extracting archive %s", filename) + retval = shell_call( + [ + "docker", + "run", + "--network=none", + "-v", + "{0}:/input_dir".format(submission_dir), + "-v", + "{0}:/output_dir".format(self._tmp_extracted_dir), + "busybox:1.27.2", + ] + + exctract_command_tmpl, + src=os.path.join("/input_dir", submission_basename), + dst="/output_dir", + ) + if not retval: + logging.error("Failed to extract submission from file %s", filename) + return False + if not make_directory_writable(self._tmp_extracted_dir): + return False + # find submission root + root_dir = self._tmp_extracted_dir + root_dir_content = [d for d in os.listdir(root_dir) if d != "__MACOSX"] + if len(root_dir_content) == 1 and os.path.isdir( + os.path.join(root_dir, root_dir_content[0]) + ): + logging.info( + 'Looks like submission root is in subdirectory "%s" of ' "the archive", + root_dir_content[0], + ) + root_dir = os.path.join(root_dir, root_dir_content[0]) + # Move files to self._extracted_submission_dir. + # At this point self._extracted_submission_dir does not exist, + # so following command will simply rename root_dir into + # self._extracted_submission_dir + if not shell_call(["mv", root_dir, self._extracted_submission_dir]): + logging.error("Can" "t move submission files from root directory") + return False + return True + + def _verify_submission_size(self): + submission_size = 0 + for dirname, _, filenames in os.walk(self._extracted_submission_dir): + for f in filenames: + submission_size += os.path.getsize(os.path.join(dirname, f)) + logging.info("Unpacked submission size: %d", submission_size) + if submission_size > MAX_SUBMISSION_SIZE_UNPACKED: + logging.error( + "Submission size exceeding limit %d", MAX_SUBMISSION_SIZE_UNPACKED + ) + return submission_size <= MAX_SUBMISSION_SIZE_UNPACKED + + def _load_and_verify_metadata(self): + """Loads and verifies metadata. + + Returns: + dictionaty with metadata or None if metadata not found or invalid + """ + metadata_filename = os.path.join( + self._extracted_submission_dir, "metadata.json" + ) + if not os.path.isfile(metadata_filename): + logging.error("metadata.json not found") + return None + try: + with open(metadata_filename, "r") as f: + metadata = json.load(f) + except IOError as e: + logging.error("Failed to load metadata: %s", e) + return None + for field_name in REQUIRED_METADATA_JSON_FIELDS: + if field_name not in metadata: + logging.error("Field %s not found in metadata", field_name) + return None + # Verify submission type + if metadata["type"] not in ALLOWED_SUBMISSION_TYPES: + logging.error("Invalid submission type in metadata: %s", metadata["type"]) + return None + # Check submission entry point + entry_point = metadata["entry_point"] + if not os.path.isfile( + os.path.join(self._extracted_submission_dir, entry_point) + ): + logging.error("Entry point not found: %s", entry_point) + return None + if not entry_point.endswith(".sh"): + logging.warning( + "Entry point is not an .sh script. " + "This is not necessarily a problem, but if submission " + "won" + "t run double check entry point first: %s", + entry_point, + ) + # Metadata verified + return metadata + + def _verify_docker_image_size(self, image_name): + """Verifies size of Docker image. + + Args: + image_name: name of the Docker image. + + Returns: + True if image size is within the limits, False otherwise. + """ + shell_call(["docker", "pull", image_name]) + try: + image_size = subprocess.check_output( + ["docker", "inspect", "--format={{.Size}}", image_name] + ).strip() + image_size = int(image_size) + except (ValueError, subprocess.CalledProcessError) as e: + logging.error("Failed to determine docker image size: %s", e) + return False + logging.info("Size of docker image %s is %d", image_name, image_size) + if image_size > MAX_DOCKER_IMAGE_SIZE: + logging.error("Image size exceeds limit %d", MAX_DOCKER_IMAGE_SIZE) + return image_size <= MAX_DOCKER_IMAGE_SIZE + + def _prepare_sample_data(self, submission_type): + """Prepares sample data for the submission. + + Args: + submission_type: type of the submission. + """ + # write images + images = np.random.randint( + 0, 256, size=[BATCH_SIZE, 299, 299, 3], dtype=np.uint8 + ) + for i in range(BATCH_SIZE): + Image.fromarray(images[i, :, :, :]).save( + os.path.join(self._sample_input_dir, IMAGE_NAME_PATTERN.format(i)) + ) + # write target class for targeted attacks + if submission_type == "targeted_attack": + target_classes = np.random.randint(1, 1001, size=[BATCH_SIZE]) + target_class_filename = os.path.join( + self._sample_input_dir, "target_class.csv" + ) + with open(target_class_filename, "w") as f: + for i in range(BATCH_SIZE): + f.write( + (IMAGE_NAME_PATTERN + ",{1}\n").format(i, target_classes[i]) + ) + + def _run_submission(self, metadata): + """Runs submission inside Docker container. + + Args: + metadata: dictionary with submission metadata + + Returns: + True if status code of Docker command was success (i.e. zero), + False otherwise. + """ + container_name = ( + metadata["container_gpu"] if self._use_gpu else metadata["container"] + ) + if metadata["type"] == "defense": + cmd = [ + "--network=none", + "-m=24g", + "-v", + "{0}:/input_images:ro".format(self._sample_input_dir), + "-v", + "{0}:/output_data".format(self._sample_output_dir), + "-v", + "{0}:/code".format(self._extracted_submission_dir), + "-w", + "/code", + container_name, + "./" + metadata["entry_point"], + "/input_images", + "/output_data/result.csv", + ] + else: + epsilon = np.random.choice(ALLOWED_EPS) + cmd = [ + "--network=none", + "-m=24g", + "-v", + "{0}:/input_images:ro".format(self._sample_input_dir), + "-v", + "{0}:/output_images".format(self._sample_output_dir), + "-v", + "{0}:/code".format(self._extracted_submission_dir), + "-w", + "/code", + container_name, + "./" + metadata["entry_point"], + "/input_images", + "/output_images", + str(epsilon), + ] + if self._use_gpu: + cmd = ["docker", "run", "--runtime=nvidia"] + cmd + else: + cmd = ["docker", "run"] + cmd + logging.info("Command to run submission: %s", " ".join(cmd)) + result = shell_call(cmd) + make_directory_writable(self._extracted_submission_dir) + make_directory_writable(self._sample_output_dir) + return result + + def _verify_output(self, submission_type): + """Verifies correctness of the submission output. + + Args: + submission_type: type of the submission + + Returns: + True if output looks valid + """ + result = True + if submission_type == "defense": + try: + image_classification = load_defense_output( + os.path.join(self._sample_output_dir, "result.csv") + ) + expected_keys = [ + IMAGE_NAME_PATTERN.format(i) for i in range(BATCH_SIZE) + ] + if set(image_classification.keys()) != set(expected_keys): + logging.error("Classification results are not saved for all images") + result = False + except IOError as e: + logging.error("Failed to read defense output file: %s", e) + result = False + else: + for i in range(BATCH_SIZE): + image_filename = os.path.join( + self._sample_output_dir, IMAGE_NAME_PATTERN.format(i) + ) + try: + img = np.array(Image.open(image_filename).convert("RGB")) + if list(img.shape) != [299, 299, 3]: + logging.error( + "Invalid image size %s for image %s", + str(img.shape), + image_filename, + ) + result = False + except IOError as e: + result = False + return result + + def validate_submission(self, filename): + """Validates submission. + + Args: + filename: submission filename + + Returns: + submission metadata or None if submission is invalid + """ + self._prepare_temp_dir() + # Convert filename to be absolute path, relative path might cause problems + # with mounting directory in Docker + filename = os.path.abspath(filename) + # extract submission + if not self._extract_submission(filename): + return None + # verify submission size + if not self._verify_submission_size(): + return None + # Load metadata + metadata = self._load_and_verify_metadata() + if not metadata: + return None + submission_type = metadata["type"] + # verify docker container size + if not self._verify_docker_image_size(metadata["container_gpu"]): + return None + # Try to run submission on sample data + self._prepare_sample_data(submission_type) + if not self._run_submission(metadata): + logging.error("Failure while running submission") + return None + if not self._verify_output(submission_type): + logging.warning( + "Some of the outputs of your submission are invalid or " + "missing. You submission still will be evaluation " + "but you might get lower score." + ) + return metadata diff --git a/examples/robust_vision_benchmark/README.md b/cleverhans_v3.1.0/examples/robust_vision_benchmark/README.md similarity index 100% rename from examples/robust_vision_benchmark/README.md rename to cleverhans_v3.1.0/examples/robust_vision_benchmark/README.md diff --git a/examples/robust_vision_benchmark/cleverhans_attack_example/Dockerfile b/cleverhans_v3.1.0/examples/robust_vision_benchmark/cleverhans_attack_example/Dockerfile similarity index 100% rename from examples/robust_vision_benchmark/cleverhans_attack_example/Dockerfile rename to cleverhans_v3.1.0/examples/robust_vision_benchmark/cleverhans_attack_example/Dockerfile diff --git a/examples/robust_vision_benchmark/cleverhans_attack_example/main.py b/cleverhans_v3.1.0/examples/robust_vision_benchmark/cleverhans_attack_example/main.py similarity index 68% rename from examples/robust_vision_benchmark/cleverhans_attack_example/main.py rename to cleverhans_v3.1.0/examples/robust_vision_benchmark/cleverhans_attack_example/main.py index 9d598da45..f901187dc 100755 --- a/examples/robust_vision_benchmark/cleverhans_attack_example/main.py +++ b/cleverhans_v3.1.0/examples/robust_vision_benchmark/cleverhans_attack_example/main.py @@ -7,9 +7,9 @@ def attack(model, session, a): - fgsm = FastGradientMethod(model, sess=session) - image = a.original_image[np.newaxis] - return fgsm.generate_np(image) + fgsm = FastGradientMethod(model, sess=session) + image = a.original_image[np.newaxis] + return fgsm.generate_np(image) attack_server(cleverhans_attack_wrapper(attack)) diff --git a/examples/robust_vision_benchmark/cleverhans_attack_example/requirements.txt b/cleverhans_v3.1.0/examples/robust_vision_benchmark/cleverhans_attack_example/requirements.txt similarity index 100% rename from examples/robust_vision_benchmark/cleverhans_attack_example/requirements.txt rename to cleverhans_v3.1.0/examples/robust_vision_benchmark/cleverhans_attack_example/requirements.txt diff --git a/cleverhans_v3.1.0/examples/robust_vision_benchmark/cleverhans_attack_example/utils.py b/cleverhans_v3.1.0/examples/robust_vision_benchmark/cleverhans_attack_example/utils.py new file mode 100644 index 000000000..01fb51296 --- /dev/null +++ b/cleverhans_v3.1.0/examples/robust_vision_benchmark/cleverhans_attack_example/utils.py @@ -0,0 +1,80 @@ +import cleverhans.model +import tensorflow as tf +import numpy as np + + +def cleverhans_attack_wrapper(cleverhans_attack_fn, reset=True): + def attack(a): + session = tf.Session() + with session.as_default(): + model = RVBCleverhansModel(a) + adversarial_image = cleverhans_attack_fn(model, session, a) + adversarial_image = np.squeeze(adversarial_image, axis=0) + if reset: + # optionally, reset to ignore other adversarials + # found during the search + a._reset() + # run predictions to make sure the returned adversarial + # is taken into account + min_, max_ = a.bounds() + adversarial_image = np.clip(adversarial_image, min_, max_) + a.predictions(adversarial_image) + + return attack + + +def py_func_grad(func, inp, Tout, stateful=True, name=None, grad=None): + """Custom py_func with gradient support""" + # Need to generate a unique name to avoid duplicates: + rnd_name = "PyFuncGrad" + str(np.random.randint(0, 1e8)) + + tf.RegisterGradient(rnd_name)(grad) + g = tf.get_default_graph() + with g.gradient_override_map({"PyFunc": rnd_name, "PyFuncStateless": rnd_name}): + return tf.py_func(func, inp, Tout, stateful=stateful, name=name) + + +class RVBCleverhansModel(cleverhans.model.Model): + """This is a cleverhans model that wraps a robust vision benchmark model.""" + + def __init__(self, adversarial, **kwargs): + del kwargs + cleverhans.model.Model.__init__( + self, "model", self.adversarial.nb_classes(), locals() + ) + self.adversarial = adversarial + + def fprop(self, x): + return {self.O_LOGITS: self._logits_op(x)} + + def _logits_op(self, x, name=None): + with tf.name_scope(name, "logits", [x]) as name: + + nb_classes = self.adversarial.nb_classes() + + def _backward_py(gradient_y, x): + x = np.squeeze(x, axis=0) + gradient_y = np.squeeze(gradient_y, axis=0) + gradient_x = self.adversarial.backward(gradient_y, x) + gradient_x = gradient_x.astype(np.float32) + return gradient_x[np.newaxis] + + def _backward_tf(op, grad): + images = op.inputs[0] + gradient_x = tf.py_func(_backward_py, [grad, images], tf.float32) + gradient_x.set_shape(images.shape) + return gradient_x + + def _forward_py(x): + predictions = self.adversarial.batch_predictions(x, strict=False)[0] + predictions = predictions.astype(np.float32) + return predictions + + op = py_func_grad( + _forward_py, [x], [tf.float32], name=name, grad=_backward_tf + ) + + logits = op[0] + logits.set_shape((x.shape[0], nb_classes)) + + return logits diff --git a/cleverhans_v3.1.0/examples/test_imagenet_attacks.py b/cleverhans_v3.1.0/examples/test_imagenet_attacks.py new file mode 100644 index 000000000..936e8d857 --- /dev/null +++ b/cleverhans_v3.1.0/examples/test_imagenet_attacks.py @@ -0,0 +1,273 @@ +"""Test attack success against ImageNet models for a few images. + +Many of the tests require using flags to specify a pre-trained ImageNet model, +as well as image data. The easiest way to provide these is using the data from +cleverhans/examples/nips17_adversarial_competition, and then the default flag +values will just work. + +Setup: see SETUP_INSTRUCTIONS +""" +# pylint: disable=missing-docstring +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import csv +import os +import unittest + +import numpy as np +from six.moves import xrange +import tensorflow as tf +from tensorflow.contrib import slim + +# The following line is affected by a pylint bug when using python3 and tf 1.12 +from tensorflow.contrib.slim.nets import inception # pylint: disable=no-name-in-module +from PIL import Image +from cleverhans.attacks import SPSA +from cleverhans.devtools.checks import CleverHansTest +from cleverhans.model import Model +from cleverhans.utils import CLEVERHANS_ROOT + +SETUP_INSTRUCTIONS = """ +$ ./examples/nips17_adversarial_competition/dev_toolkit/download_data.sh +""" + +DEFAULT_INCEPTION_PATH = os.path.join( + CLEVERHANS_ROOT, + ( + "examples/nips17_adversarial_competition/dev_toolkit/sample_attacks/fgsm/" + "inception_v3.ckpt" + ), +) + +tf.flags.DEFINE_string("master", "", "The address of the TensorFlow master to use.") + +tf.flags.DEFINE_string( + "checkpoint_path", + DEFAULT_INCEPTION_PATH, + "Path to checkpoint for inception network.", +) + +tf.flags.DEFINE_string( + "input_image_dir", + os.path.join( + CLEVERHANS_ROOT, "examples/nips17_adversarial_competition/dataset/images" + ), + "Path to image directory.", +) + +tf.flags.DEFINE_string( + "metadata_file_path", + os.path.join( + CLEVERHANS_ROOT, + "examples/nips17_adversarial_competition/dataset/dev_dataset.csv", + ), + "Path to metadata file.", +) + +FLAGS = tf.flags.FLAGS + + +def load_images(input_dir, metadata_file_path, batch_shape): + """Retrieve numpy arrays of images and labels, read from a directory.""" + num_images = batch_shape[0] + with open(metadata_file_path) as input_file: + reader = csv.reader(input_file) + header_row = next(reader) + rows = list(reader) + + row_idx_image_id = header_row.index("ImageId") + row_idx_true_label = header_row.index("TrueLabel") + images = np.zeros(batch_shape) + labels = np.zeros(num_images, dtype=np.int32) + for idx in xrange(num_images): + row = rows[idx] + filepath = os.path.join(input_dir, row[row_idx_image_id] + ".png") + + with tf.gfile.Open(filepath, "rb") as f: + image = np.array(Image.open(f).convert("RGB")).astype(np.float) / 255.0 + images[idx, :, :, :] = image + labels[idx] = int(row[row_idx_true_label]) + return images, labels + + +class InceptionModel(Model): + """Model class for CleverHans library.""" + + def __init__(self, nb_classes): + super(InceptionModel, self).__init__( + nb_classes=nb_classes, needs_dummy_fprop=True + ) + self.built = False + + def __call__(self, x_input, return_logits=False): + """Constructs model and return probabilities for given input.""" + reuse = True if self.built else None + with slim.arg_scope(inception.inception_v3_arg_scope()): + # Inception preprocessing uses [-1, 1]-scaled input. + x_input = x_input * 2.0 - 1.0 + _, end_points = inception.inception_v3( + x_input, num_classes=self.nb_classes, is_training=False, reuse=reuse + ) + self.built = True + self.logits = end_points["Logits"] + # Strip off the extra reshape op at the output + self.probs = end_points["Predictions"].op.inputs[0] + if return_logits: + return self.logits + else: + return self.probs + + def get_logits(self, x_input): + return self(x_input, return_logits=True) + + def get_probs(self, x_input): + return self(x_input) + + +def _top_1_accuracy(logits, labels): + return tf.reduce_mean(tf.cast(tf.nn.in_top_k(logits, labels, 1), tf.float32)) + + +class TestInception(CleverHansTest): + def test_clean_accuracy(self): + """Check model is accurate on unperturbed images.""" + input_dir = FLAGS.input_image_dir + metadata_file_path = FLAGS.metadata_file_path + num_images = 16 + batch_shape = (num_images, 299, 299, 3) + images, labels = load_images(input_dir, metadata_file_path, batch_shape) + nb_classes = 1001 + + tf.logging.set_verbosity(tf.logging.INFO) + with tf.Graph().as_default(): + # Prepare graph + x_input = tf.placeholder(tf.float32, shape=batch_shape) + y_label = tf.placeholder(tf.int32, shape=(num_images,)) + model = InceptionModel(nb_classes) + logits = model.get_logits(x_input) + acc = _top_1_accuracy(logits, y_label) + + # Run computation + saver = tf.train.Saver(slim.get_model_variables()) + + session_creator = tf.train.ChiefSessionCreator( + scaffold=tf.train.Scaffold(saver=saver), + checkpoint_filename_with_path=FLAGS.checkpoint_path, + master=FLAGS.master, + ) + + with tf.train.MonitoredSession(session_creator=session_creator) as sess: + acc_val = sess.run(acc, feed_dict={x_input: images, y_label: labels}) + tf.logging.info("Accuracy: %s", acc_val) + assert acc_val > 0.8 + + +class TestSPSA(CleverHansTest): + def test_attack_bounds(self): + """Check SPSA respects perturbation limits.""" + epsilon = 4.0 / 255 + input_dir = FLAGS.input_image_dir + metadata_file_path = FLAGS.metadata_file_path + num_images = 8 + batch_shape = (num_images, 299, 299, 3) + images, labels = load_images(input_dir, metadata_file_path, batch_shape) + nb_classes = 1001 + + tf.logging.set_verbosity(tf.logging.INFO) + with tf.Graph().as_default(): + # Prepare graph + x_input = tf.placeholder(tf.float32, shape=(1,) + batch_shape[1:]) + y_label = tf.placeholder(tf.int32, shape=(1,)) + model = InceptionModel(nb_classes) + + attack = SPSA(model) + x_adv = attack.generate( + x_input, + y=y_label, + epsilon=epsilon, + num_steps=10, + early_stop_loss_threshold=-1.0, + spsa_samples=32, + spsa_iters=1, + is_debug=True, + ) + + # Run computation + saver = tf.train.Saver(slim.get_model_variables()) + session_creator = tf.train.ChiefSessionCreator( + scaffold=tf.train.Scaffold(saver=saver), + checkpoint_filename_with_path=FLAGS.checkpoint_path, + master=FLAGS.master, + ) + + with tf.train.MonitoredSession(session_creator=session_creator) as sess: + for i in xrange(num_images): + x_expanded = np.expand_dims(images[i], axis=0) + y_expanded = np.expand_dims(labels[i], axis=0) + + adv_image = sess.run( + x_adv, feed_dict={x_input: x_expanded, y_label: y_expanded} + ) + diff = adv_image - images[i] + assert np.max(np.abs(diff)) < epsilon + 1e-4 + assert np.max(adv_image < 1.0 + 1e-4) + assert np.min(adv_image > -1e-4) + + def test_attack_success(self): + """Check SPSA creates misclassified images.""" + epsilon = 4.0 / 255 + input_dir = FLAGS.input_image_dir + metadata_file_path = FLAGS.metadata_file_path + num_images = 8 + batch_shape = (num_images, 299, 299, 3) + images, labels = load_images(input_dir, metadata_file_path, batch_shape) + nb_classes = 1001 + + tf.logging.set_verbosity(tf.logging.INFO) + with tf.Graph().as_default(): + # Prepare graph + x_input = tf.placeholder(tf.float32, shape=(1,) + batch_shape[1:]) + y_label = tf.placeholder(tf.int32, shape=(1,)) + model = InceptionModel(nb_classes) + + attack = SPSA(model) + x_adv = attack.generate( + x_input, + y=y_label, + epsilon=epsilon, + num_steps=30, + early_stop_loss_threshold=-1.0, + spsa_samples=32, + spsa_iters=16, + is_debug=True, + ) + + logits = model.get_logits(x_adv) + acc = _top_1_accuracy(logits, y_label) + + # Run computation + saver = tf.train.Saver(slim.get_model_variables()) + session_creator = tf.train.ChiefSessionCreator( + scaffold=tf.train.Scaffold(saver=saver), + checkpoint_filename_with_path=FLAGS.checkpoint_path, + master=FLAGS.master, + ) + + num_correct = 0.0 + with tf.train.MonitoredSession(session_creator=session_creator) as sess: + for i in xrange(num_images): + feed_dict_i = { + x_input: np.expand_dims(images[i], axis=0), + y_label: np.expand_dims(labels[i], axis=0), + } + acc_val = sess.run(acc, feed_dict=feed_dict_i) + tf.logging.info("Accuracy: %s", acc_val) + num_correct += acc_val + assert (num_correct / num_images) < 0.1 + + +if __name__ == "__main__": + unittest.main() diff --git a/cleverhans_v3.1.0/scripts/compute_accuracy.py b/cleverhans_v3.1.0/scripts/compute_accuracy.py new file mode 100755 index 000000000..8fd5e7845 --- /dev/null +++ b/cleverhans_v3.1.0/scripts/compute_accuracy.py @@ -0,0 +1,235 @@ +#!/usr/bin/env python3 +""" +compute_accuracy.py +Usage: + compute_accuracy.py model.joblib + + where model.joblib is a file created by cleverhans.serial.save containing + a picklable cleverhans.model.Model instance. + +This script will run the model on a variety of types of data and print out +the accuracy for each data type. + clean : Clean data + semantic : Semantic adversarial examples + pgd: PGD adversarial examples + +This script works by running a single attack on each example. +This is useful for quick evaluation during development, but for final +publication it would be better to use attack bundling. +""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import logging +import time + +import tensorflow as tf + +from cleverhans.attacks import ProjectedGradientDescent, Semantic +from cleverhans.compat import flags +from cleverhans.evaluation import accuracy +from cleverhans.serial import load +from cleverhans.utils import set_log_level +from cleverhans.utils_tf import infer_devices +from cleverhans.utils_tf import silence + +silence() +devices = infer_devices() +num_devices = len(devices) +BATCH_SIZE = 128 +TRAIN_START = 0 +TRAIN_END = 60000 +TEST_START = 0 +TEST_END = 10000 +WHICH_SET = "test" +NB_ITER = 40 +BASE_EPS_ITER = None # Differs by dataset + + +FLAGS = flags.FLAGS + + +def print_accuracies( + filepath, + train_start=TRAIN_START, + train_end=TRAIN_END, + test_start=TEST_START, + test_end=TEST_END, + batch_size=BATCH_SIZE, + which_set=WHICH_SET, + base_eps_iter=BASE_EPS_ITER, + nb_iter=NB_ITER, +): + """ + Load a saved model and print out its accuracy on different data distributions + + This function works by running a single attack on each example. + This provides a reasonable estimate of the true failure rate quickly, so + long as the model does not suffer from gradient masking. + However, this estimate is mostly intended for development work and not + for publication. A more accurate estimate may be obtained by running + an attack bundler instead. + + :param filepath: path to model to evaluate + :param train_start: index of first training set example to use + :param train_end: index of last training set example to use + :param test_start: index of first test set example to use + :param test_end: index of last test set example to use + :param batch_size: size of evaluation batches + :param which_set: 'train' or 'test' + :param base_eps_iter: step size if the data were in [0,1] + (Step size will be rescaled proportional to the actual data range) + :param nb_iter: Number of iterations of PGD to run per class + """ + + # Set TF random seed to improve reproducibility + tf.set_random_seed(20181014) + set_log_level(logging.INFO) + sess = tf.Session() + + with sess.as_default(): + model = load(filepath) + assert len(model.get_params()) > 0 + factory = model.dataset_factory + factory.kwargs["train_start"] = train_start + factory.kwargs["train_end"] = train_end + factory.kwargs["test_start"] = test_start + factory.kwargs["test_end"] = test_end + dataset = factory() + + x_data, y_data = dataset.get_set(which_set) + + impl(sess, model, dataset, factory, x_data, y_data, base_eps_iter, nb_iter) + + +def impl( + sess, + model, + dataset, + factory, + x_data, + y_data, + base_eps_iter=BASE_EPS_ITER, + nb_iter=NB_ITER, + batch_size=BATCH_SIZE, +): + """ + The actual implementation of the evaluation. + :param sess: tf.Session + :param model: cleverhans.model.Model + :param dataset: cleverhans.dataset.Dataset + :param factory: the dataset factory corresponding to `dataset` + :param x_data: numpy array of input examples + :param y_data: numpy array of class labels + :param base_eps_iter: step size for PGD if data were in [0, 1] + :param nb_iter: number of PGD iterations + :returns: dict mapping string adversarial example names to accuracies + """ + + center = dataset.kwargs["center"] + max_val = dataset.kwargs["max_val"] + value_range = max_val * (1.0 + center) + min_value = 0.0 - center * max_val + + if "CIFAR" in str(factory.cls): + base_eps = 8.0 / 255.0 + if base_eps_iter is None: + base_eps_iter = 2.0 / 255.0 + elif "MNIST" in str(factory.cls): + base_eps = 0.3 + if base_eps_iter is None: + base_eps_iter = 0.1 + else: + raise NotImplementedError(str(factory.cls)) + + pgd_params = { + "eps": base_eps * value_range, + "eps_iter": base_eps_iter * value_range, + "nb_iter": nb_iter, + "clip_min": min_value, + "clip_max": max_val, + } + + semantic = Semantic(model, center, max_val, sess) + pgd = ProjectedGradientDescent(model, sess=sess) + + jobs = [ + ("clean", None, None, None), + ("Semantic", semantic, None, None), + ("pgd", pgd, pgd_params, None), + ] + + out = {} + + for job in jobs: + name, attack, attack_params, job_batch_size = job + if job_batch_size is None: + job_batch_size = batch_size + t1 = time.time() + acc = accuracy( + sess, + model, + x_data, + y_data, + batch_size=job_batch_size, + devices=devices, + attack=attack, + attack_params=attack_params, + ) + t2 = time.time() + out[name] = acc + print("Accuracy on " + name + " examples: ", acc) + print("Evaluation took", t2 - t1, "seconds") + + return out + + +def main(argv=None): + """ + Print accuracies + """ + try: + _name_of_script, filepath = argv + except ValueError: + raise ValueError(argv) + print_accuracies( + filepath=filepath, + test_start=FLAGS.test_start, + test_end=FLAGS.test_end, + which_set=FLAGS.which_set, + nb_iter=FLAGS.nb_iter, + base_eps_iter=FLAGS.base_eps_iter, + batch_size=FLAGS.batch_size, + ) + + +if __name__ == "__main__": + flags.DEFINE_integer( + "train_start", + TRAIN_START, + "Starting point (inclusive)" "of range of train examples to use", + ) + flags.DEFINE_integer( + "train_end", + TRAIN_END, + "Ending point (non-inclusive) " "of range of train examples to use", + ) + flags.DEFINE_integer( + "test_start", + TEST_START, + "Starting point (inclusive) " "of range of test examples to use", + ) + flags.DEFINE_integer( + "test_end", + TEST_END, + "End point (non-inclusive) of " "range of test examples to use", + ) + flags.DEFINE_integer("nb_iter", NB_ITER, "Number of iterations of PGD") + flags.DEFINE_string("which_set", WHICH_SET, '"train" or "test"') + flags.DEFINE_integer("batch_size", BATCH_SIZE, "Batch size for most jobs") + flags.DEFINE_float( + "base_eps_iter", BASE_EPS_ITER, "epsilon per iteration, if data were in [0, 1]" + ) + tf.app.run() diff --git a/scripts/make_confidence_report.py b/cleverhans_v3.1.0/scripts/make_confidence_report.py similarity index 52% rename from scripts/make_confidence_report.py rename to cleverhans_v3.1.0/scripts/make_confidence_report.py index 59d9a5de3..eef439c84 100755 --- a/scripts/make_confidence_report.py +++ b/cleverhans_v3.1.0/scripts/make_confidence_report.py @@ -32,6 +32,7 @@ import tensorflow as tf from cleverhans.utils_tf import silence + silence() # silence call must precede this imports. pylint doesn't like that # pylint: disable=C0413 @@ -54,42 +55,59 @@ def main(argv=None): - """ - Make a confidence report and save it to disk. - """ - try: - _name_of_script, filepath = argv - except ValueError: - raise ValueError(argv) - make_confidence_report(filepath=filepath, test_start=FLAGS.test_start, - test_end=FLAGS.test_end, which_set=FLAGS.which_set, - report_path=FLAGS.report_path, - mc_batch_size=FLAGS.mc_batch_size, - nb_iter=FLAGS.nb_iter, - base_eps_iter=FLAGS.base_eps_iter, - batch_size=FLAGS.batch_size, - save_advx=FLAGS.save_advx) + """ + Make a confidence report and save it to disk. + """ + try: + _name_of_script, filepath = argv + except ValueError: + raise ValueError(argv) + make_confidence_report( + filepath=filepath, + test_start=FLAGS.test_start, + test_end=FLAGS.test_end, + which_set=FLAGS.which_set, + report_path=FLAGS.report_path, + mc_batch_size=FLAGS.mc_batch_size, + nb_iter=FLAGS.nb_iter, + base_eps_iter=FLAGS.base_eps_iter, + batch_size=FLAGS.batch_size, + save_advx=FLAGS.save_advx, + ) -if __name__ == '__main__': - flags.DEFINE_integer('train_start', TRAIN_START, 'Starting point (inclusive)' - 'of range of train examples to use') - flags.DEFINE_integer('train_end', TRAIN_END, 'Ending point (non-inclusive) ' - 'of range of train examples to use') - flags.DEFINE_integer('test_start', TEST_START, 'Starting point (inclusive) ' - 'of range of test examples to use') - flags.DEFINE_integer('test_end', TEST_END, 'End point (non-inclusive) of ' - 'range of test examples to use') - flags.DEFINE_integer('nb_iter', NB_ITER, 'Number of iterations of PGD') - flags.DEFINE_string('which_set', WHICH_SET, '"train" or "test"') - flags.DEFINE_string('report_path', REPORT_PATH, 'Path to save to') - flags.DEFINE_integer('mc_batch_size', MC_BATCH_SIZE, - 'Batch size for MaxConfidence') - flags.DEFINE_integer('batch_size', BATCH_SIZE, - 'Batch size for most jobs') - flags.DEFINE_float('base_eps_iter', BASE_EPS_ITER, - 'epsilon per iteration, if data were in [0, 1]') - flags.DEFINE_integer('save_advx', SAVE_ADVX, - 'If True, saves the adversarial examples to the ' - 'filesystem.') - tf.app.run() +if __name__ == "__main__": + flags.DEFINE_integer( + "train_start", + TRAIN_START, + "Starting point (inclusive)" "of range of train examples to use", + ) + flags.DEFINE_integer( + "train_end", + TRAIN_END, + "Ending point (non-inclusive) " "of range of train examples to use", + ) + flags.DEFINE_integer( + "test_start", + TEST_START, + "Starting point (inclusive) " "of range of test examples to use", + ) + flags.DEFINE_integer( + "test_end", + TEST_END, + "End point (non-inclusive) of " "range of test examples to use", + ) + flags.DEFINE_integer("nb_iter", NB_ITER, "Number of iterations of PGD") + flags.DEFINE_string("which_set", WHICH_SET, '"train" or "test"') + flags.DEFINE_string("report_path", REPORT_PATH, "Path to save to") + flags.DEFINE_integer("mc_batch_size", MC_BATCH_SIZE, "Batch size for MaxConfidence") + flags.DEFINE_integer("batch_size", BATCH_SIZE, "Batch size for most jobs") + flags.DEFINE_float( + "base_eps_iter", BASE_EPS_ITER, "epsilon per iteration, if data were in [0, 1]" + ) + flags.DEFINE_integer( + "save_advx", + SAVE_ADVX, + "If True, saves the adversarial examples to the " "filesystem.", + ) + tf.app.run() diff --git a/cleverhans_v3.1.0/scripts/make_confidence_report_bundle_examples.py b/cleverhans_v3.1.0/scripts/make_confidence_report_bundle_examples.py new file mode 100755 index 000000000..182cbad95 --- /dev/null +++ b/cleverhans_v3.1.0/scripts/make_confidence_report_bundle_examples.py @@ -0,0 +1,126 @@ +#!/usr/bin/env python3 +""" +make_confidence_report_bundle_examples.py +Usage: + make_confidence_report_bundle_examples.py model.joblib a.npy + make_confidence_report_bundle_examples.py model.joblib a.npy b.npy c.npy + + where model.joblib is a file created by cleverhans.serial.save containing + a picklable cleverhans.model.Model instance and each examples_i.npy is + a saved numpy array containing adversarial examples for a whole dataset. + Usually example_i.npy is the output of make_confidence_report.py or + make_confidence_report_bundled.py. + +This script uses max-confidence attack bundling +( https://openreview.net/forum?id=H1g0piA9tQ ) +to combine adversarial example datasets that were created earlier. +It will save a ConfidenceReport to to model_bundled_examples_report.joblib. +The report can be later loaded by another +script using cleverhans.serial.load. +""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import warnings + +import numpy as np +import tensorflow as tf + +from cleverhans.utils_tf import silence + +# We need to disable pylint's complaints about import order because `silence` +# works only if it is called before the other imports. +# pylint: disable=C0413 +silence() +from cleverhans.attack_bundling import bundle_examples_with_goal, MaxConfidence +from cleverhans import serial +from cleverhans.compat import flags +from cleverhans.confidence_report import BATCH_SIZE +from cleverhans.confidence_report import TRAIN_START, TRAIN_END +from cleverhans.confidence_report import TEST_START, TEST_END +from cleverhans.confidence_report import WHICH_SET + + +FLAGS = flags.FLAGS + + +def main(argv=None): + """ + Make a confidence report and save it to disk. + """ + assert len(argv) >= 3 + _name_of_script = argv[0] + model_filepath = argv[1] + adv_x_filepaths = argv[2:] + + sess = tf.Session() + with sess.as_default(): + model = serial.load(model_filepath) + + factory = model.dataset_factory + factory.kwargs["train_start"] = FLAGS.train_start + factory.kwargs["train_end"] = FLAGS.train_end + factory.kwargs["test_start"] = FLAGS.test_start + factory.kwargs["test_end"] = FLAGS.test_end + dataset = factory() + + adv_x_list = [np.load(filepath) for filepath in adv_x_filepaths] + x, y = dataset.get_set(FLAGS.which_set) + for adv_x in adv_x_list: + assert adv_x.shape == x.shape, (adv_x.shape, x.shape) + # Make sure these were made for the right dataset with right scaling + # arguments, etc. + assert adv_x.min() >= 0.0 - dataset.kwargs["center"] * dataset.max_val + assert adv_x.max() <= dataset.max_val + data_range = dataset.max_val * (1.0 + dataset.kwargs["center"]) + + if adv_x.max() - adv_x.min() <= 0.8 * data_range: + warnings.warn( + "Something is weird. Your adversarial examples use " + "less than 80% of the data range." + "This might mean you generated them for a model with " + "inputs in [0, 1] and are now using them for a model " + "with inputs in [0, 255] or something like that. " + "Or it could be OK if you're evaluating on a very small " + "batch." + ) + + report_path = FLAGS.report_path + if report_path is None: + suffix = "_bundled_examples_report.joblib" + assert model_filepath.endswith(".joblib") + report_path = model_filepath[: -len(".joblib")] + suffix + + goal = MaxConfidence() + bundle_examples_with_goal( + sess, model, adv_x_list, y, goal, report_path, batch_size=FLAGS.batch_size + ) + + +if __name__ == "__main__": + flags.DEFINE_string("report_path", None, "Report path") + flags.DEFINE_integer( + "train_start", + TRAIN_START, + "Starting point (inclusive)" "of range of train examples to use", + ) + flags.DEFINE_integer( + "train_end", + TRAIN_END, + "Ending point (non-inclusive) " "of range of train examples to use", + ) + flags.DEFINE_integer( + "test_start", + TEST_START, + "Starting point " "(inclusive) of range of test examples to use", + ) + flags.DEFINE_integer( + "test_end", + TEST_END, + "End point (non-inclusive) of " "range of test examples to use", + ) + flags.DEFINE_string("which_set", WHICH_SET, '"train" or "test"') + flags.DEFINE_integer("batch_size", BATCH_SIZE, "batch size") + tf.app.run() diff --git a/scripts/make_confidence_report_bundled.py b/cleverhans_v3.1.0/scripts/make_confidence_report_bundled.py similarity index 50% rename from scripts/make_confidence_report_bundled.py rename to cleverhans_v3.1.0/scripts/make_confidence_report_bundled.py index 18317f758..a7bf63cb6 100755 --- a/scripts/make_confidence_report_bundled.py +++ b/cleverhans_v3.1.0/scripts/make_confidence_report_bundled.py @@ -22,6 +22,7 @@ import tensorflow as tf from cleverhans.utils_tf import silence + # The silence() call must precede other imports in order to silence them. # pylint does not like it but that's how it has to be. # pylint: disable=C0413 @@ -40,34 +41,50 @@ def main(argv=None): - """ - Make a confidence report and save it to disk. - """ - try: - _name_of_script, filepath = argv - except ValueError: - raise ValueError(argv) - print(filepath) - make_confidence_report_bundled(filepath=filepath, - test_start=FLAGS.test_start, - test_end=FLAGS.test_end, - which_set=FLAGS.which_set, - recipe=FLAGS.recipe, - report_path=FLAGS.report_path, batch_size=FLAGS.batch_size) + """ + Make a confidence report and save it to disk. + """ + try: + _name_of_script, filepath = argv + except ValueError: + raise ValueError(argv) + print(filepath) + make_confidence_report_bundled( + filepath=filepath, + test_start=FLAGS.test_start, + test_end=FLAGS.test_end, + which_set=FLAGS.which_set, + recipe=FLAGS.recipe, + report_path=FLAGS.report_path, + batch_size=FLAGS.batch_size, + ) -if __name__ == '__main__': - flags.DEFINE_integer('train_start', TRAIN_START, 'Starting point (inclusive)' - 'of range of train examples to use') - flags.DEFINE_integer('train_end', TRAIN_END, 'Ending point (non-inclusive) ' - 'of range of train examples to use') - flags.DEFINE_integer('test_start', TEST_START, 'Starting point (inclusive) ' - 'of range of test examples to use') - flags.DEFINE_integer('test_end', TEST_END, 'End point (non-inclusive) of ' - 'range of test examples to use') - flags.DEFINE_string('recipe', RECIPE, 'Name of function from attack_bundling' - ' to run') - flags.DEFINE_string('which_set', WHICH_SET, '"train" or "test"') - flags.DEFINE_string('report_path', REPORT_PATH, 'Report path') - flags.DEFINE_integer('batch_size', BATCH_SIZE, 'Batch size') - tf.app.run() +if __name__ == "__main__": + flags.DEFINE_integer( + "train_start", + TRAIN_START, + "Starting point (inclusive)" "of range of train examples to use", + ) + flags.DEFINE_integer( + "train_end", + TRAIN_END, + "Ending point (non-inclusive) " "of range of train examples to use", + ) + flags.DEFINE_integer( + "test_start", + TEST_START, + "Starting point (inclusive) " "of range of test examples to use", + ) + flags.DEFINE_integer( + "test_end", + TEST_END, + "End point (non-inclusive) of " "range of test examples to use", + ) + flags.DEFINE_string( + "recipe", RECIPE, "Name of function from attack_bundling" " to run" + ) + flags.DEFINE_string("which_set", WHICH_SET, '"train" or "test"') + flags.DEFINE_string("report_path", REPORT_PATH, "Report path") + flags.DEFINE_integer("batch_size", BATCH_SIZE, "Batch size") + tf.app.run() diff --git a/cleverhans_v3.1.0/scripts/make_confidence_report_spsa.py b/cleverhans_v3.1.0/scripts/make_confidence_report_spsa.py new file mode 100755 index 000000000..ed3508ef5 --- /dev/null +++ b/cleverhans_v3.1.0/scripts/make_confidence_report_spsa.py @@ -0,0 +1,204 @@ +#!/usr/bin/env python3 +""" +make_confidence_report.py +Usage: + python make_confidence_report_spsa.py model.joblib + + where model.joblib is a file created by cleverhans.serial.save containing + a picklable cleverhans.model.Model instance. + +This script will run the model on a variety of types of data and save a +ConfidenceReport to model_report.joblib. +The report can be later loaded by another script using cleverhans.serial.load. +This script puts the following entries in the report: + clean : Clean data + mc: MaxConfidence SPSA adversarial examples + +This script works by running a single MaxConfidence attack on each example. +( https://openreview.net/forum?id=H1g0piA9tQ ) +The MaxConfidence attack uses the SPSA optimizer. +This is not intended to be a generic strong attack; rather it is intended +to be a test for gradient masking. +""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import logging + +import numpy as np +import tensorflow as tf + +from cleverhans.utils_tf import silence + +silence() +# The call to silence() must precede the other imports or they will log. +# pylint does not like this. +# pylint: disable=C0413 +from cleverhans.attacks import SPSA +from cleverhans.attack_bundling import spsa_max_confidence_recipe +from cleverhans.serial import load +from cleverhans.utils import set_log_level +from cleverhans.compat import flags +from cleverhans.confidence_report import BATCH_SIZE +from cleverhans.confidence_report import TRAIN_START +from cleverhans.confidence_report import TRAIN_END +from cleverhans.confidence_report import TEST_START +from cleverhans.confidence_report import TEST_END +from cleverhans.confidence_report import WHICH_SET +from cleverhans.confidence_report import REPORT_PATH + +NB_ITER_SPSA = 80 +SPSA_SAMPLES = SPSA.DEFAULT_SPSA_SAMPLES + + +FLAGS = flags.FLAGS + + +def make_confidence_report_spsa( + filepath, + train_start=TRAIN_START, + train_end=TRAIN_END, + test_start=TEST_START, + test_end=TEST_END, + batch_size=BATCH_SIZE, + which_set=WHICH_SET, + report_path=REPORT_PATH, + nb_iter=NB_ITER_SPSA, + spsa_samples=SPSA_SAMPLES, + spsa_iters=SPSA.DEFAULT_SPSA_ITERS, +): + """ + Load a saved model, gather its predictions, and save a confidence report. + + + This function works by running a single MaxConfidence attack on each example, + using SPSA as the underyling optimizer. + This is not intended to be a strong generic attack. + It is intended to be a test to uncover gradient masking. + + :param filepath: path to model to evaluate + :param train_start: index of first training set example to use + :param train_end: index of last training set example to use + :param test_start: index of first test set example to use + :param test_end: index of last test set example to use + :param batch_size: size of evaluation batches + :param which_set: 'train' or 'test' + :param nb_iter: Number of iterations of PGD to run per class + :param spsa_samples: Number of samples for SPSA + """ + + # Set TF random seed to improve reproducibility + tf.set_random_seed(1234) + + # Set logging level to see debug information + set_log_level(logging.INFO) + + # Create TF session + sess = tf.Session() + + if report_path is None: + assert filepath.endswith(".joblib") + report_path = filepath[: -len(".joblib")] + "_spsa_report.joblib" + + with sess.as_default(): + model = load(filepath) + assert len(model.get_params()) > 0 + factory = model.dataset_factory + factory.kwargs["train_start"] = train_start + factory.kwargs["train_end"] = train_end + factory.kwargs["test_start"] = test_start + factory.kwargs["test_end"] = test_end + dataset = factory() + + center = dataset.kwargs["center"] + center = np.float32(center) + max_val = dataset.kwargs["max_val"] + max_val = np.float32(max_val) + value_range = max_val * (1.0 + center) + min_value = np.float32(0.0 - center * max_val) + + if "CIFAR" in str(factory.cls): + base_eps = 8.0 / 255.0 + elif "MNIST" in str(factory.cls): + base_eps = 0.3 + else: + raise NotImplementedError(str(factory.cls)) + + eps = np.float32(base_eps * value_range) + clip_min = min_value + clip_max = max_val + + x_data, y_data = dataset.get_set(which_set) + + nb_classes = dataset.NB_CLASSES + + spsa_max_confidence_recipe( + sess, + model, + x_data, + y_data, + nb_classes, + eps, + clip_min, + clip_max, + nb_iter, + report_path, + spsa_samples=spsa_samples, + spsa_iters=spsa_iters, + eval_batch_size=batch_size, + ) + + +def main(argv=None): + """ + Make a confidence report and save it to disk. + """ + try: + _name_of_script, filepath = argv + except ValueError: + raise ValueError(argv) + make_confidence_report_spsa( + filepath=filepath, + test_start=FLAGS.test_start, + test_end=FLAGS.test_end, + which_set=FLAGS.which_set, + report_path=FLAGS.report_path, + nb_iter=FLAGS.nb_iter, + batch_size=FLAGS.batch_size, + spsa_samples=FLAGS.spsa_samples, + spsa_iters=FLAGS.spsa_iters, + ) + + +if __name__ == "__main__": + flags.DEFINE_integer("spsa_samples", SPSA_SAMPLES, "Number samples for SPSA") + flags.DEFINE_integer( + "spsa_iters", SPSA.DEFAULT_SPSA_ITERS, "Passed to SPSA.generate" + ) + flags.DEFINE_integer( + "train_start", + TRAIN_START, + "Starting point (inclusive)" "of range of train examples to use", + ) + flags.DEFINE_integer( + "train_end", + TRAIN_END, + "Ending point (non-inclusive) " "of range of train examples to use", + ) + flags.DEFINE_integer( + "test_start", + TEST_START, + "Starting point (inclusive) of range" " of test examples to use", + ) + flags.DEFINE_integer( + "test_end", + TEST_END, + "End point (non-inclusive) of range" " of test examples to use", + ) + flags.DEFINE_integer("nb_iter", NB_ITER_SPSA, "Number of iterations of SPSA") + flags.DEFINE_string("which_set", WHICH_SET, '"train" or "test"') + flags.DEFINE_string("report_path", REPORT_PATH, "Path to save to") + flags.DEFINE_integer("batch_size", BATCH_SIZE, "Batch size for most jobs") + tf.app.run() diff --git a/scripts/plot_success_fail_curve.py b/cleverhans_v3.1.0/scripts/plot_success_fail_curve.py similarity index 61% rename from scripts/plot_success_fail_curve.py rename to cleverhans_v3.1.0/scripts/plot_success_fail_curve.py index 03355c2e5..69b757eb1 100755 --- a/scripts/plot_success_fail_curve.py +++ b/cleverhans_v3.1.0/scripts/plot_success_fail_curve.py @@ -14,29 +14,37 @@ from matplotlib import pyplot import tensorflow as tf from cleverhans.utils_tf import silence + silence() # silence call must precede this imports. pylint doesn't like that # pylint: disable=C0413 from cleverhans.compat import flags from cleverhans.plot.success_fail import DEFAULT_FAIL_NAMES from cleverhans.plot.success_fail import plot_report_from_path + FLAGS = flags.FLAGS + def main(argv=None): - """Takes the path to a directory with reports and renders success fail plots.""" - report_paths = argv[1:] + """Takes the path to a directory with reports and renders success fail plots.""" + report_paths = argv[1:] + + fail_names = FLAGS.fail_names.split(",") - fail_names = FLAGS.fail_names.split(',') + for report_path in report_paths: + plot_report_from_path(report_path, label=report_path, fail_names=fail_names) + pyplot.legend() - for report_path in report_paths: - plot_report_from_path(report_path, label=report_path, fail_names=fail_names) - pyplot.legend() + pyplot.xlim(-0.01, 1.0) + pyplot.ylim(0.0, 1.0) - pyplot.xlim(-.01, 1.) - pyplot.ylim(0., 1.) + pyplot.show() - pyplot.show() -if __name__ == '__main__': - flags.DEFINE_string('fail_names', ','.join(DEFAULT_FAIL_NAMES), 'Names of adversarial datasets for failure rate') - tf.app.run() +if __name__ == "__main__": + flags.DEFINE_string( + "fail_names", + ",".join(DEFAULT_FAIL_NAMES), + "Names of adversarial datasets for failure rate", + ) + tf.app.run() diff --git a/cleverhans_v3.1.0/scripts/print_report.py b/cleverhans_v3.1.0/scripts/print_report.py new file mode 100755 index 000000000..950a9d469 --- /dev/null +++ b/cleverhans_v3.1.0/scripts/print_report.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python3 +""" +print_report.py +Usage: + print_report.py model_report.joblib +Prints out some basic statistics stored in a pickled ConfidenceReport +""" +import sys +import warnings + +from cleverhans.confidence_report import ConfidenceReport +from cleverhans.serial import load + + +if len(sys.argv) == 2: + # pylint doesn't realize that sys.argv will change at runtime + # pylint:disable=unbalanced-tuple-unpacking + _, path = sys.argv +else: + raise ValueError("Wrong number of arguments") +the_report = load(path) + + +def current(report): + """ + The current implementation of report printing. + :param report: ConfidenceReport + """ + if hasattr(report, "completed"): + if report.completed: + print("Report completed") + else: + print("REPORT NOT COMPLETED") + else: + warnings.warn( + "This report does not indicate whether it is completed. Support for reports without a `completed`" + "field may be dropped on or after 2019-05-11." + ) + for key in report: + covered = report[key].confidence > 0.5 + wrong = 1.0 - report[key].correctness + failure_rate = (covered * wrong).mean() + print(key, "failure rate at t=.5", failure_rate) + print(key, "accuracy at t=0", report[key].correctness.mean()) + + +def deprecated(report): + """ + The deprecated implementation of report printing. + :param report: dict + """ + warnings.warn( + "Printing dict-based reports is deprecated. This function " + "is included only to support a private development branch " + "and may be removed without warning." + ) + + for key in report: + confidence_name = "confidence" + correctness_name = "correctness" + if confidence_name not in report[key]: + confidence_name = "all_probs" + correctness_name = "correctness_mask" + warnings.warn( + "'all_probs' is used only to temporarily support " + "the private development branch. This name can be " + "removed at any time without warning." + ) + covered = report[key][confidence_name] > 0.5 + wrong = 1.0 - report[key][correctness_name] + failure_rate = (covered * wrong).mean() + print(key, "failure rate at t=.5", failure_rate) + print(key, "accuracy at t=0", report[key][correctness_name].mean()) + + +if isinstance(the_report, ConfidenceReport): + current(the_report) +else: + deprecated(the_report) diff --git a/scripts/show_images.py b/cleverhans_v3.1.0/scripts/show_images.py similarity index 87% rename from scripts/show_images.py rename to cleverhans_v3.1.0/scripts/show_images.py index d4b9ad235..b8e35c84b 100755 --- a/scripts/show_images.py +++ b/cleverhans_v3.1.0/scripts/show_images.py @@ -7,8 +7,9 @@ import sys import numpy as np from cleverhans.plot.image import show, make_grid + # pylint has a bug here, thinks sys.argv is empty -_, path = sys.argv # pylint: disable=E0632 +_, path = sys.argv # pylint: disable=E0632 image_batch = np.load(path) diff --git a/cleverhans_v3.1.0/setup.py b/cleverhans_v3.1.0/setup.py new file mode 100644 index 000000000..f35a0b211 --- /dev/null +++ b/cleverhans_v3.1.0/setup.py @@ -0,0 +1,27 @@ +from setuptools import find_packages +from setuptools import setup + +setup( + name="cleverhans", + version="3.0.1", + url="https://github.com/tensorflow/cleverhans", + license="MIT", + install_requires=[ + "nose", + "pycodestyle", + "scipy", + "matplotlib", + "mnist ~= 0.2", + "numpy", + "tensorflow-probability", + "joblib", + ], + # Explicit dependence on TensorFlow is not supported. + # See https://github.com/tensorflow/tensorflow/issues/7166 + extras_require={ + "tf": ["tensorflow>=1.0.0"], + "tf_gpu": ["tensorflow-gpu>=1.0.0"], + "pytorch": ["torch>=1.1.0", "torchvision==0.3.0"], + }, + packages=find_packages(), +) diff --git a/cleverhans_v3.1.0/tests_tf/test_attack_bundling.py b/cleverhans_v3.1.0/tests_tf/test_attack_bundling.py new file mode 100644 index 000000000..ac9625063 --- /dev/null +++ b/cleverhans_v3.1.0/tests_tf/test_attack_bundling.py @@ -0,0 +1,72 @@ +""" +Tests for cleverhans.attack_bundling +""" +import numpy as np +from cleverhans.attack_bundling import AttackConfig +from cleverhans.attack_bundling import Misclassify +from cleverhans.attack_bundling import unfinished_attack_configs + + +def test_unfinished_attack_configs(): + """ + Test that tracking of which attack configs are unfinished is correct + """ + + new_work_goal = {} + work_before = {} + run_counts = {} + + expected_unfinished = [] + expected_finished = [] + + easy_finished = AttackConfig(None, None) + new_work_goal[easy_finished] = 1 + work_before[easy_finished] = np.array([0, 0]) + run_counts[easy_finished] = np.array([1, 1]) + expected_finished.append(easy_finished) + + easy_unfinished = AttackConfig(None, None) + new_work_goal[easy_unfinished] = 1 + work_before[easy_unfinished] = np.array([0, 0]) + run_counts[easy_unfinished] = np.array([0, 0]) + expected_unfinished.append(easy_unfinished) + + only_partly_finished = AttackConfig(None, None) + new_work_goal[only_partly_finished] = 1 + work_before[only_partly_finished] = np.array([0, 0]) + run_counts[only_partly_finished] = np.array([1, 0]) + expected_unfinished.append(only_partly_finished) + + work_not_new = AttackConfig(None, None) + new_work_goal[work_not_new] = 1 + work_before[work_not_new] = np.array([1, 1]) + run_counts[work_not_new] = np.array([1, 1]) + expected_unfinished.append(work_not_new) + + actual_unfinished = unfinished_attack_configs( + new_work_goal, work_before, run_counts + ) + + assert all(e in actual_unfinished for e in expected_unfinished) + assert all(e not in actual_unfinished for e in expected_finished) + + +def test_misclassify_request_examples(): + """ + Test Misclassify.request_examples + """ + cfg = AttackConfig(None, None) + goal = Misclassify(new_work_goal={cfg: 1}) + correctness = np.array([0, 1, 0, 1, 0, 1, 0, 1, 0, 1], dtype=np.bool) + run_counts = np.array([1, 1, 1, 0, 0, 0, 1, 1, 1, 0], dtype=np.int64) + criteria = {"correctness": correctness} + batch_size = 3 + idxs = goal.request_examples(cfg, criteria, {cfg: run_counts}, batch_size) + assert idxs.shape == (batch_size,) + idxs = list(idxs) + for already_misclassified in [0, 2, 4, 6, 8]: + assert already_misclassified not in idxs + for already_run in [1, 7]: + assert already_run not in idxs + for needed in [3, 5, 9]: + assert needed in idxs diff --git a/cleverhans_v3.1.0/tests_tf/test_attacks.py b/cleverhans_v3.1.0/tests_tf/test_attacks.py new file mode 100644 index 000000000..236524823 --- /dev/null +++ b/cleverhans_v3.1.0/tests_tf/test_attacks.py @@ -0,0 +1,1906 @@ +# pylint: disable=missing-docstring +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import functools +import unittest + +import numpy as np +from nose.plugins.skip import SkipTest +import tensorflow as tf + +# pylint bug on next line +import tensorflow.contrib.slim as slim # pylint: disable=no-name-in-module + +from cleverhans.devtools.checks import CleverHansTest +from cleverhans import attacks +from cleverhans.attacks import Attack, SPSA +from cleverhans.attacks import FastGradientMethod +from cleverhans.attacks import BasicIterativeMethod +from cleverhans.attacks import MomentumIterativeMethod +from cleverhans.attacks import VirtualAdversarialMethod +from cleverhans.attacks import SaliencyMapMethod +from cleverhans.attacks import CarliniWagnerL2 +from cleverhans.attacks import ElasticNetMethod +from cleverhans.attacks import DeepFool +from cleverhans.attacks import MadryEtAl +from cleverhans.attacks import ProjectedGradientDescent +from cleverhans.attacks import FastFeatureAdversaries +from cleverhans.attacks import LBFGS +from cleverhans.attacks import SpatialTransformationMethod +from cleverhans.attacks import HopSkipJumpAttack +from cleverhans.attacks import SparseL1Descent +from cleverhans.initializers import HeReLuNormalInitializer +from cleverhans.model import Model + + +class SimpleModel(Model): + """ + A very simple neural network + """ + + def __init__(self, scope="simple", nb_classes=2, **kwargs): + del kwargs + Model.__init__(self, scope, nb_classes, locals()) + + def fprop(self, x, **kwargs): + del kwargs + with tf.variable_scope(self.scope, reuse=tf.AUTO_REUSE): + w1 = tf.constant([[1.5, 0.3], [-2, 0.3]], dtype=tf.as_dtype(x.dtype)) + w2 = tf.constant([[-2.4, 1.2], [0.5, -2.3]], dtype=tf.as_dtype(x.dtype)) + h1 = tf.nn.sigmoid(tf.matmul(x, w1)) + res = tf.matmul(h1, w2) + return {self.O_LOGITS: res, self.O_PROBS: tf.nn.softmax(res)} + + +class TrivialModel(Model): + """ + A linear model with two weights + """ + + def __init__(self, scope="trivial", nb_classes=2, **kwargs): + del kwargs + Model.__init__(self, scope, nb_classes, locals()) + + def fprop(self, x, **kwargs): + del kwargs + with tf.variable_scope(self.scope, reuse=tf.AUTO_REUSE): + w1 = tf.constant([[1, -1]], dtype=tf.float32) + res = tf.matmul(x, w1) + return {self.O_LOGITS: res, self.O_PROBS: tf.nn.softmax(res)} + + +class DummyModel(Model): + """ + A simple model based on slim + """ + + def __init__(self, scope="dummy_model", nb_classes=10, **kwargs): + del kwargs + Model.__init__(self, scope, nb_classes, locals()) + + def fprop(self, x, **kwargs): + del kwargs + with tf.variable_scope(self.scope, reuse=tf.AUTO_REUSE): + net = slim.flatten(x) + net = slim.fully_connected(net, 60) + logits = slim.fully_connected(net, 10, activation_fn=None) + return {self.O_LOGITS: logits, self.O_PROBS: tf.nn.softmax(logits)} + + +class TestAttackClassInitArguments(CleverHansTest): + def test_model(self): + sess = tf.Session() + + # Exception is thrown when model does not have __call__ attribute + with self.assertRaises(Exception) as context: + model = tf.placeholder(tf.float32, shape=(None, 10)) + Attack(model, sess=sess) + self.assertTrue(context.exception) + + def test_sess(self): + # Test that it is permitted to provide no session. + # The session still needs to be created prior to running the attack. + # TODO: does anyone know why we need to make an unused session and put it in a with statement? + with tf.Session(): + Attack(Model("model", 10, {}), sess=None) + + def test_sess_generate_np(self): + model = Model("model", 10, {}) + + class DummyAttack(Attack): + def generate(self, x, **kwargs): + return x + + # Test that generate_np is NOT permitted without a session. + # The session still needs to be created prior to running the attack. + # TODO: does anyone know why we need to make an unused session and put it in a with statement? + with tf.Session(): + attack = DummyAttack(model, sess=None) + with self.assertRaises(Exception) as context: + attack.generate_np(0.0) + self.assertTrue(context.exception) + + +class TestParseParams(CleverHansTest): + def test_parse(self): + sess = tf.Session() + + test_attack = Attack(Model("model", 10, {}), sess=sess) + self.assertTrue(test_attack.parse_params({})) + + +class TestVirtualAdversarialMethod(CleverHansTest): + def setUp(self): + super(TestVirtualAdversarialMethod, self).setUp() + + self.sess = tf.Session() + self.sess.as_default() + self.model = DummyModel("virtual_adv_dummy_model") + self.attack = VirtualAdversarialMethod(self.model, sess=self.sess) + + # initialize model + with tf.name_scope("virtual_adv_dummy_model"): + self.model.get_probs(tf.placeholder(tf.float32, shape=(None, 1000))) + self.sess.run(tf.global_variables_initializer()) + + def test_parse_params(self): + self.attack.parse_params() + # test default values + self.assertEqual(self.attack.eps, 2.0) + self.assertEqual(self.attack.num_iterations, 1) + self.assertEqual(self.attack.xi, 1e-6) + self.assertEqual(self.attack.clip_min, None) + self.assertEqual(self.attack.clip_max, None) + + def test_generate_np(self): + x_val = np.random.rand(100, 1000) + perturbation = self.attack.generate_np(x_val) - x_val + perturbation_norm = np.sqrt(np.sum(perturbation ** 2, axis=1)) + # test perturbation norm + self.assertClose(perturbation_norm, self.attack.eps) + + +class CommonAttackProperties(CleverHansTest): + """ + Abstract base class shared by the tests for many attacks that want + to check the same properties. + """ + + def setUp(self): + # Inheritance doesn't really work with tests. + # nosetests always wants to run this class because it is a + # CleverHansTest subclass, but this class is meant to just + # be abstract. + # Before this class was the tests for FastGradientMethod but + # people kept inheriting from it for other attacks so it was + # impossible to write tests specifically for FastGradientMethod. + # pylint: disable=unidiomatic-typecheck + if type(self) is CommonAttackProperties: + raise SkipTest() + + super(CommonAttackProperties, self).setUp() + self.sess = tf.Session() + self.model = SimpleModel() + + def generate_adversarial_examples_np(self, ord, eps, **kwargs): + x_val = np.random.rand(100, 2) + x_val = np.array(x_val, dtype=np.float32) + + x_adv = self.attack.generate_np( + x_val, eps=eps, ord=ord, clip_min=-5, clip_max=5, **kwargs + ) + if ord == np.inf: + delta = np.max(np.abs(x_adv - x_val), axis=1) + elif ord == 1: + delta = np.sum(np.abs(x_adv - x_val), axis=1) + elif ord == 2: + delta = np.sum(np.square(x_adv - x_val), axis=1) ** 0.5 + + return x_val, x_adv, delta + + def help_generate_np_gives_adversarial_example(self, ord, eps=0.5, **kwargs): + x_val, x_adv, delta = self.generate_adversarial_examples_np(ord, eps, **kwargs) + self.assertLess(np.max(np.abs(delta - eps)), 1e-3) + orig_labs = np.argmax(self.sess.run(self.model.get_logits(x_val)), axis=1) + new_labs = np.argmax(self.sess.run(self.model.get_logits(x_adv)), axis=1) + self.assertLess(np.max(np.mean(orig_labs == new_labs)), 0.5) + + def test_invalid_input(self): + x_val = -np.ones((2, 2), dtype="float32") + with self.assertRaises(tf.errors.InvalidArgumentError) as context: + self.attack.generate_np(x_val, eps=1.0, clip_min=0.0, clip_max=1.0) + self.assertTrue(context.exception) + + def test_generate_np_gives_adversarial_example_linfinity(self): + self.help_generate_np_gives_adversarial_example(np.infty) + + def test_generate_np_gives_adversarial_example_l1(self): + self.help_generate_np_gives_adversarial_example(1) + + def test_generate_np_gives_adversarial_example_l2(self): + self.help_generate_np_gives_adversarial_example(2) + + def test_generate_respects_dtype(self): + self.attack = FastGradientMethod(self.model, sess=self.sess, dtypestr="float64") + x = tf.placeholder(dtype=tf.float64, shape=(100, 2)) + x_adv = self.attack.generate(x) + self.assertEqual(x_adv.dtype, tf.float64) + + def test_targeted_generate_np_gives_adversarial_example(self): + random_labs = np.random.random_integers(0, 1, 100) + random_labs_one_hot = np.zeros((100, 2)) + random_labs_one_hot[np.arange(100), random_labs] = 1 + + try: + _, x_adv, delta = self.generate_adversarial_examples_np( + eps=0.5, ord=np.inf, y_target=random_labs_one_hot + ) + except NotImplementedError: + raise SkipTest() + + self.assertLessEqual(np.max(delta), 0.5001) + + new_labs = np.argmax(self.sess.run(self.model.get_logits(x_adv)), axis=1) + self.assertTrue(np.mean(random_labs == new_labs) > 0.7) + + def test_generate_np_can_be_called_with_different_eps(self): + x_val = np.random.rand(100, 2) + x_val = np.array(x_val, dtype=np.float32) + + for eps in [0.1, 0.2, 0.3, 0.4]: + x_adv = self.attack.generate_np( + x_val, eps=eps, ord=np.inf, clip_min=-5.0, clip_max=5.0 + ) + + delta = np.max(np.abs(x_adv - x_val), axis=1) + self.assertLessEqual(np.max(delta), eps + 1e-4) + + def test_generate_can_be_called_with_different_eps(self): + # It is crtical that this test uses generate and not generate_np. + # All the other tests use generate_np. Even though generate_np calls + # generate, it does so in a very standardized way, e.g. with eps + # always converted to a tensorflow placeholder, so the other tests + # based on generate_np do not exercise the generate API very well. + x_val = np.random.rand(100, 2) + x_val = np.array(x_val, dtype=np.float32) + x = tf.placeholder(tf.float32, x_val.shape) + + for eps in [0.1, 0.2, 0.3, 0.4]: + x_adv = self.attack.generate( + x, eps=eps, ord=np.inf, clip_min=-5.0, clip_max=5.0 + ) + x_adv = self.sess.run(x_adv, feed_dict={x: x_val}) + + delta = np.max(np.abs(x_adv - x_val), axis=1) + self.assertLessEqual(np.max(delta), eps + 1e-4) + + def test_generate_np_clip_works_as_expected(self): + x_val = np.random.rand(100, 2) + x_val = np.array(x_val, dtype=np.float32) + + x_adv = self.attack.generate_np( + x_val, eps=0.5, ord=np.inf, clip_min=-0.2, clip_max=0.1, sanity_checks=False + ) + + self.assertClose(np.min(x_adv), -0.2) + self.assertClose(np.max(x_adv), 0.1) + + +class TestFastGradientMethod(CommonAttackProperties): + def setUp(self): + super(TestFastGradientMethod, self).setUp() + + self.attack = FastGradientMethod(self.model, sess=self.sess) + + +class TestOptimizeLinear(CleverHansTest): + """ + Tests for the `optimize_linear` function + """ + + def setUp(self): + super(TestOptimizeLinear, self).setUp() + self.sess = tf.Session() + self.model = SimpleModel() + + def test_optimize_linear_linf(self): + + grad = tf.placeholder(tf.float32, shape=[1, 2]) + + # Build the graph for the attack + eta = attacks.optimize_linear(grad, eps=1.0, ord=np.inf) + objective = tf.reduce_sum(grad * eta) + + grad_val = np.array([[1.0, -2.0]]) + eta, objective = self.sess.run([eta, objective], feed_dict={grad: grad_val}) + + # Make sure the objective is optimal. + # This is the solution obtained by doing the algebra by hand. + self.assertClose(objective, np.abs(grad_val).sum()) + # Make sure the constraint is respected. + # Also, for a linear function, the constraint will always be tight. + self.assertClose(np.abs(eta), 1.0) + + def test_optimize_linear_l2(self): + + grad = tf.placeholder(tf.float32, shape=[1, 2]) + + # Build the graph for the attack + eta = attacks.optimize_linear(grad, eps=1.0, ord=2) + objective = tf.reduce_sum(grad * eta) + + grad_val = np.array([[np.sqrt(0.5), -np.sqrt(0.5)]]) + eta, objective = self.sess.run([eta, objective], feed_dict={grad: grad_val}) + + # Make sure the objective is optimal. + # This is the solution obtained by doing the algebra by hand. + self.assertClose(objective, 1.0) + # Make sure the constraint is respected. + # Also, for a linear function, the constraint will always be tight. + self.assertClose(np.sqrt(np.square(eta).sum()), 1.0) + + def test_optimize_linear_l1(self): + + # This test makes sure that `optimize_linear` actually finds the optimal + # perturbation for ord=1. + # A common misconcpetion is that FGM for ord=1 consists of dividing + # the gradient by its L1 norm. + # If you do that for the problem in this unit test, you'll get an + # objective function value of ~1.667. The optimal result is 2. + + # We need just one example in the batch and two features to show the + # common misconception is suboptimal. + grad = tf.placeholder(tf.float32, shape=[1, 2]) + + # Build the graph for the attack + eta = attacks.optimize_linear(grad, eps=1.0, ord=1) + objective = tf.reduce_sum(grad * eta) + + # Make sure the largest entry of the gradient for the test case is + # negative, to catch + # the potential bug where we forget to handle the sign of the gradient + eta, objective = self.sess.run( + [eta, objective], feed_dict={grad: np.array([[1.0, -2.0]])} + ) + + # Make sure the objective is optimal. + # This is the solution obtained by doing the algebra by hand. + self.assertClose(objective, 2.0) + # Make sure the constraint is respected. + # Also, for a linear function, the constraint will always be tight. + self.assertClose(np.abs(eta).sum(), 1.0) + + def test_optimize_linear_l1_ties(self): + + # This test makes sure that `optimize_linear` handles ties in gradient + # magnitude correctly when ord=1. + + # We need just one example in the batch and two features to construct + # a tie. + grad = tf.placeholder(tf.float32, shape=[1, 2]) + + # Build the graph for the attack + eta = attacks.optimize_linear(grad, eps=1.0, ord=1) + objective = tf.reduce_sum(grad * eta) + + # Run a test case with a tie for largest absolute value. + # Make one feature negative to make sure we're checking for ties in + # absolute value, not raw value. + eta, objective = self.sess.run( + [eta, objective], feed_dict={grad: np.array([[2.0, -2.0]])} + ) + + # Make sure the objective is optimal. + # This is the solution obtained by doing the algebra by hand. + self.assertClose(objective, 2.0) + # Make sure the constraint is respected. + # Also, for a linear function, the constraint will always be tight. + self.assertClose(np.abs(eta).sum(), 1.0) + + +class TestSPSA(CleverHansTest): + def setUp(self): + super(TestSPSA, self).setUp() + + self.sess = tf.Session() + self.model = SimpleModel() + self.attack = SPSA(self.model, sess=self.sess) + + def test_attack_strength(self): + n_samples = 10 + x_val = np.random.rand(n_samples, 2) + x_val = np.array(x_val, dtype=np.float32) + + # The SPSA attack currently uses non-one-hot labels + # TODO: change this to use standard cleverhans label conventions + feed_labs = np.random.randint(0, 2, n_samples) + + x_input = tf.placeholder(tf.float32, shape=(1, 2)) + y_label = tf.placeholder(tf.int32, shape=(1,)) + + x_adv_op = self.attack.generate( + x_input, + y=y_label, + epsilon=0.5, + num_steps=100, + batch_size=64, + spsa_iters=1, + clip_min=0.0, + clip_max=1.0, + ) + + all_x_adv = [] + for i in range(n_samples): + x_adv_np = self.sess.run( + x_adv_op, + feed_dict={ + x_input: np.expand_dims(x_val[i], axis=0), + y_label: np.expand_dims(feed_labs[i], axis=0), + }, + ) + all_x_adv.append(x_adv_np[0]) + + x_adv = np.vstack(all_x_adv) + new_labs = np.argmax(self.sess.run(self.model.get_logits(x_adv)), axis=1) + self.assertTrue(np.mean(feed_labs == new_labs) < 0.1) + + def test_attack_strength_np(self): + # Same test as test_attack_strength, but uses generate_np interface + n_samples = 10 + x_val = np.random.rand(n_samples, 2) + x_val = np.array(x_val, dtype=np.float32) + + feed_labs = np.random.randint(0, 2, n_samples, dtype="int32") + + all_x_adv = [] + for i in range(n_samples): + x_adv_np = self.attack.generate_np( + np.expand_dims(x_val[i], axis=0), + y=np.expand_dims(feed_labs[i], axis=0), + eps=0.5, + nb_iter=100, + spsa_samples=64, + spsa_iters=1, + clip_min=0.0, + clip_max=1.0, + ) + all_x_adv.append(x_adv_np[0]) + + x_adv = np.vstack(all_x_adv) + new_labs = np.argmax(self.sess.run(self.model.get_logits(x_adv)), axis=1) + self.assertLess(np.mean(feed_labs == new_labs), 0.1) + + def test_attack_strength_np_batched(self): + # Same test as test_attack_strength_np, but batched + n_samples = 10 + x_val = np.random.rand(n_samples, 2) + x_val = np.array(x_val, dtype=np.float32) + + feed_labs = np.random.randint(0, 2, n_samples, dtype="int32") + x_adv = self.attack.generate_np( + x_val, + y=feed_labs, + eps=0.5, + nb_iter=100, + spsa_samples=64, + spsa_iters=1, + clip_min=0.0, + clip_max=1.0, + ) + + new_labs = np.argmax(self.sess.run(self.model.get_logits(x_adv)), axis=1) + self.assertLess(np.mean(feed_labs == new_labs), 0.1) + + def test_label_argument_int64(self): + x_val = np.random.rand(1, 2) + x_val = np.array(x_val, dtype=np.float32) + + # Try calling alternating with int32 and int64 and ensure it works + for dtype in [np.int32, np.int64, np.int32, np.int64]: + self.attack.generate_np( + x_val, + y=np.zeros(1, dtype=dtype), + eps=0.5, + nb_iter=5, + spsa_samples=64, + spsa_iters=1, + clip_min=0.0, + clip_max=1.0, + ) + + +class TestProjectedGradientDescent(CommonAttackProperties): + def setUp(self): + super(TestProjectedGradientDescent, self).setUp() + + self.sess = tf.Session() + self.model = SimpleModel() + self.attack = ProjectedGradientDescent(self.model, sess=self.sess) + + def test_generate_np_gives_adversarial_example_linfinity(self): + self.help_generate_np_gives_adversarial_example( + ord=np.infty, eps=0.5, nb_iter=20 + ) + + def test_generate_np_gives_adversarial_example_l1(self): + try: + self.help_generate_np_gives_adversarial_example(ord=1, eps=0.5, nb_iter=20) + except NotImplementedError: + raise SkipTest() + + def test_generate_np_gives_adversarial_example_l2(self): + self.help_generate_np_gives_adversarial_example(ord=2, eps=0.5, nb_iter=20) + + def test_do_not_reach_lp_boundary(self): + """ + Make sure that iterative attack don't reach boundary of Lp + neighbourhood if nb_iter * eps_iter is relatively small compared to + epsilon. + """ + for ord in [1, 2, np.infty]: + try: + _, _, delta = self.generate_adversarial_examples_np( + ord=ord, eps=0.5, nb_iter=10, eps_iter=0.01 + ) + except NotImplementedError: + # Don't raise SkipTest because it will skip the rest of the for loop + continue + self.assertTrue(np.max(0.5 - delta) > 0.25) + + def test_attack_strength_linf(self): + """ + If clipping is not done at each iteration (not passing clip_min and + clip_max to fgm), this attack fails by + np.mean(orig_labels == new_labels) == .39. + """ + x_val = np.random.rand(100, 2) + x_val = np.array(x_val, dtype=np.float32) + + # sanity checks turned off because this test initializes outside + # the valid range. + x_adv = self.attack.generate_np( + x_val, + eps=1.0, + ord=np.inf, + clip_min=0.5, + clip_max=0.7, + nb_iter=5, + sanity_checks=False, + ) + + orig_labs = np.argmax(self.sess.run(self.model.get_logits(x_val)), axis=1) + new_labs = np.argmax(self.sess.run(self.model.get_logits(x_adv)), axis=1) + self.assertLess(np.mean(orig_labs == new_labs), 0.1) + + def test_attack_strength_l2(self): + x_val = np.random.rand(100, 2) + x_val = np.array(x_val, dtype=np.float32) + + # sanity checks turned off because this test initializes outside + # the valid range. + x_adv = self.attack.generate_np( + x_val, + eps=1, + ord=2, + clip_min=0.5, + clip_max=0.7, + nb_iter=5, + sanity_checks=False, + ) + + orig_labs = np.argmax(self.sess.run(self.model.get_logits(x_val)), axis=1) + new_labs = np.argmax(self.sess.run(self.model.get_logits(x_adv)), axis=1) + self.assertLess(np.mean(orig_labs == new_labs), 0.1) + + def test_grad_clip_l2(self): + x_val = np.random.rand(100, 2) + x_val = np.array(x_val, dtype=np.float32) + + # sanity checks turned off because this test initializes outside + # the valid range. + x_adv = self.attack.generate_np( + x_val, + eps=1, + ord=2, + clip_min=0.5, + clip_max=0.7, + clip_grad=True, + nb_iter=10, + sanity_checks=False, + ) + + orig_labs = np.argmax(self.sess.run(self.model.get_logits(x_val)), axis=1) + new_labs = np.argmax(self.sess.run(self.model.get_logits(x_adv)), axis=1) + self.assertLess(np.mean(orig_labs == new_labs), 0.1) + + def test_clip_eta_linf(self): + x_val = np.random.rand(100, 2) + x_val = np.array(x_val, dtype=np.float32) + + x_adv = self.attack.generate_np( + x_val, ord=np.inf, eps=1.0, eps_iter=0.1, nb_iter=5 + ) + + delta = np.max(np.abs(x_adv - x_val), axis=1) + self.assertLessEqual(np.max(delta), 1.0) + + def test_clip_eta_l2(self): + x_val = np.random.rand(100, 2) + x_val = np.array(x_val, dtype=np.float32) + + x_adv = self.attack.generate_np(x_val, ord=2, eps=1.0, eps_iter=0.1, nb_iter=5) + + delta = np.sqrt(np.sum(np.square(x_adv - x_val), axis=1)) + # this projection is less numerically stable so give it some slack + self.assertLessEqual(np.max(delta), 1.0 + 1e-6) + + def test_generate_np_gives_clipped_adversarial_examples(self): + x_val = np.random.rand(100, 2) + x_val = np.array(x_val, dtype=np.float32) + + for ord in [1, 2, np.infty]: + try: + x_adv = self.attack.generate_np( + x_val, + ord=ord, + eps=1.0, + eps_iter=0.1, + nb_iter=5, + clip_min=-0.2, + clip_max=0.3, + sanity_checks=False, + ) + + self.assertLess(-0.201, np.min(x_adv)) + self.assertLess(np.max(x_adv), 0.301) + except NotImplementedError: + # Don't raise SkipTest because it will skip the rest of the for loop + continue + + def test_generate_np_does_not_cache_graph_computation_for_nb_iter(self): + x_val = np.random.rand(100, 2) + x_val = np.array(x_val, dtype=np.float32) + + # Call it once + x_adv = self.attack.generate_np( + x_val, eps=1.0, ord=np.inf, clip_min=-5.0, clip_max=5.0, nb_iter=10 + ) + + # original labels + np.argmax(self.sess.run(self.model.get_logits(x_val)), axis=1) + # new labels + np.argmax(self.sess.run(self.model.get_logits(x_adv)), axis=1) + + # Call it again + ok = [False] + old_grads = tf.gradients + try: + + def fn(*x, **y): + ok[0] = True + return old_grads(*x, **y) + + tf.gradients = fn + + x_adv = self.attack.generate_np( + x_val, eps=1.0, ord=np.inf, clip_min=-5.0, clip_max=5.0, nb_iter=11 + ) + finally: + tf.gradients = old_grads + + # original labels + np.argmax(self.sess.run(self.model.get_logits(x_val)), axis=1) + # new labels + np.argmax(self.sess.run(self.model.get_logits(x_adv)), axis=1) + + self.assertTrue(ok[0]) + + def test_multiple_initial_random_step(self): + """ + This test generates multiple adversarial examples until an adversarial + example is generated with a different label compared to the original + label. This is the procedure suggested in Madry et al. (2017). + + This test will fail if an initial random step is not taken (error>0.5). + """ + x_val = np.array(np.random.rand(100, 2), dtype=np.float32) + + orig_labs = np.argmax(self.sess.run(self.model.get_logits(x_val)), axis=1) + new_labs_multi = orig_labs.copy() + + # Generate multiple adversarial examples + for _ in range(10): + x_adv = self.attack.generate_np( + x_val, + eps=0.5, + eps_iter=0.05, + clip_min=0.5, + clip_max=0.7, + nb_iter=2, + sanity_checks=False, + ) + new_labs = np.argmax(self.sess.run(self.model.get_logits(x_adv)), axis=1) + + # Examples for which we have not found adversarial examples + I = orig_labs == new_labs_multi + new_labs_multi[I] = new_labs[I] + + self.assertLess(np.mean(orig_labs == new_labs_multi), 0.5) + + +class TestSparseL1Descent(CleverHansTest): + def setUp(self): + super(TestSparseL1Descent, self).setUp() + + self.sess = tf.Session() + self.model = SimpleModel() + self.attack = SparseL1Descent(self.model, sess=self.sess) + + def generate_adversarial_examples_np(self, eps, **kwargs): + x_val = np.random.rand(100, 2) + x_val = np.array(x_val, dtype=np.float32) + + x_adv = self.attack.generate_np( + x_val, eps=eps, clip_min=-5, clip_max=5, **kwargs + ) + delta = np.sum(np.abs(x_adv - x_val), axis=1) + + return x_val, x_adv, delta + + def help_generate_np_gives_adversarial_example(self, eps=2.0, **kwargs): + x_val, x_adv, delta = self.generate_adversarial_examples_np(eps, **kwargs) + self.assertLess(np.max(np.abs(delta - eps)), 1e-3) + orig_labs = np.argmax(self.sess.run(self.model.get_logits(x_val)), axis=1) + new_labs = np.argmax(self.sess.run(self.model.get_logits(x_adv)), axis=1) + self.assertLess(np.max(np.mean(orig_labs == new_labs)), 0.5) + + def test_invalid_input(self): + x_val = -np.ones((2, 2), dtype="float32") + with self.assertRaises(tf.errors.InvalidArgumentError) as context: + self.attack.generate_np(x_val, eps=10.0, clip_min=0.0, clip_max=1.0) + self.assertTrue(context.exception) + + def test_generate_np_gives_adversarial_example(self): + self.help_generate_np_gives_adversarial_example() + + def test_targeted_generate_np_gives_adversarial_example(self): + random_labs = np.random.random_integers(0, 1, 100) + random_labs_one_hot = np.zeros((100, 2)) + random_labs_one_hot[np.arange(100), random_labs] = 1 + + _, x_adv, delta = self.generate_adversarial_examples_np( + eps=10, y_target=random_labs_one_hot + ) + + self.assertLessEqual(np.max(delta), 10.001) + + new_labs = np.argmax(self.sess.run(self.model.get_logits(x_adv)), axis=1) + self.assertTrue(np.mean(random_labs == new_labs) > 0.7) + + def test_generate_np_can_be_called_with_different_eps(self): + x_val = np.random.rand(100, 2) + x_val = np.array(x_val, dtype=np.float32) + + for eps in [10, 20, 30, 40]: + x_adv = self.attack.generate_np(x_val, eps=eps, clip_min=-5.0, clip_max=5.0) + + delta = np.max(np.abs(x_adv - x_val), axis=1) + self.assertLessEqual(np.max(delta), eps + 1e-4) + + def test_generate_can_be_called_with_different_eps(self): + # It is crtical that this test uses generate and not generate_np. + # All the other tests use generate_np. Even though generate_np calls + # generate, it does so in a very standardized way, e.g. with eps + # always converted to a tensorflow placeholder, so the other tests + # based on generate_np do not exercise the generate API very well. + x_val = np.random.rand(100, 2) + x_val = np.array(x_val, dtype=np.float32) + x = tf.placeholder(tf.float32, x_val.shape) + + for eps in [10, 20, 30, 40]: + x_adv = self.attack.generate(x, eps=eps, clip_min=-5.0, clip_max=5.0) + x_adv = self.sess.run(x_adv, feed_dict={x: x_val}) + + delta = np.max(np.abs(x_adv - x_val), axis=1) + self.assertLessEqual(np.max(delta), eps + 1e-4) + + def test_generate_np_clip_works_as_expected(self): + x_val = np.random.rand(100, 2) + x_val = np.array(x_val, dtype=np.float32) + + x_adv = self.attack.generate_np( + x_val, + eps=10, + nb_iter=20, + rand_init=True, + clip_min=-0.2, + clip_max=0.1, + sanity_checks=False, + ) + + self.assertClose(np.min(x_adv), -0.2) + self.assertClose(np.max(x_adv), 0.1) + + def test_do_not_reach_lp_boundary(self): + """ + Make sure that iterative attack don't reach boundary of Lp + neighbourhood if nb_iter * eps_iter is relatively small compared to + epsilon. + """ + + _, _, delta = self.generate_adversarial_examples_np( + eps=0.5, nb_iter=10, eps_iter=0.01 + ) + + self.assertTrue(np.max(0.5 - delta) > 0.25) + + def test_generate_np_gives_clipped_adversarial_examples(self): + x_val = np.random.rand(100, 2) + x_val = np.array(x_val, dtype=np.float32) + + x_adv = self.attack.generate_np( + x_val, + eps=1.0, + eps_iter=0.1, + nb_iter=5, + clip_min=-0.2, + clip_max=0.3, + sanity_checks=False, + ) + + self.assertLess(-0.201, np.min(x_adv)) + self.assertLess(np.max(x_adv), 0.301) + + def test_generate_np_does_not_cache_graph_computation_for_nb_iter(self): + x_val = np.random.rand(100, 2) + x_val = np.array(x_val, dtype=np.float32) + + # Call it once + x_adv = self.attack.generate_np( + x_val, eps=1.0, clip_min=-5.0, clip_max=5.0, nb_iter=10 + ) + + # original labels + np.argmax(self.sess.run(self.model.get_logits(x_val)), axis=1) + # new labels + np.argmax(self.sess.run(self.model.get_logits(x_adv)), axis=1) + + # Call it again + ok = [False] + old_grads = tf.gradients + try: + + def fn(*x, **y): + ok[0] = True + return old_grads(*x, **y) + + tf.gradients = fn + + x_adv = self.attack.generate_np( + x_val, eps=1.0, clip_min=-5.0, clip_max=5.0, nb_iter=11 + ) + finally: + tf.gradients = old_grads + + # original labels + np.argmax(self.sess.run(self.model.get_logits(x_val)), axis=1) + # new labels + np.argmax(self.sess.run(self.model.get_logits(x_adv)), axis=1) + + self.assertTrue(ok[0]) + + def test_clip_eta(self): + x_val = np.random.rand(100, 2) + x_val = np.array(x_val, dtype=np.float32) + + x_adv = self.attack.generate_np(x_val, eps=1.0, eps_iter=0.1, nb_iter=5) + + delta = np.sum(np.abs(x_adv - x_val), axis=1) + # this projection is less numerically stable so give it some slack + self.assertLessEqual(np.max(delta), 1.0 + 1e-6) + + def test_attack_strength(self): + """ + Without clipped gradients, we achieve + np.mean(orig_labels == new_labels) == 0.31. + """ + x_val = np.random.rand(100, 2) + x_val = np.array(x_val, dtype=np.float32) + + # sanity checks turned off because this test initializes outside + # the valid range. + x_adv = self.attack.generate_np( + x_val, + eps=10.0, + rand_init=True, + clip_min=0.5, + clip_max=0.7, + nb_iter=10, + sanity_checks=False, + ) + + orig_labs = np.argmax(self.sess.run(self.model.get_logits(x_val)), axis=1) + new_labs = np.argmax(self.sess.run(self.model.get_logits(x_adv)), axis=1) + self.assertLess(np.mean(orig_labs == new_labs), 0.5) + self.assertGreater(np.mean(orig_labs == new_labs), 0.2) + + def test_grad_clip(self): + """ + With clipped gradients, we achieve + np.mean(orig_labels == new_labels) == 0.0 + """ + x_val = np.random.rand(100, 2) + x_val = np.array(x_val, dtype=np.float32) + + # sanity checks turned off because this test initializes outside + # the valid range. + x_adv = self.attack.generate_np( + x_val, + eps=10.0, + rand_init=True, + clip_min=0.5, + clip_max=0.7, + clip_grad=True, + nb_iter=10, + sanity_checks=False, + ) + + orig_labs = np.argmax(self.sess.run(self.model.get_logits(x_val)), axis=1) + new_labs = np.argmax(self.sess.run(self.model.get_logits(x_adv)), axis=1) + self.assertLess(np.mean(orig_labs == new_labs), 0.1) + + def test_sparsity(self): + + # use a model with larger input dimensionality for this test. + self.model = DummyModel("sparse_l1_descent_dummy_model") + self.attack = SparseL1Descent(self.model, sess=self.sess) + + # initialize model + with tf.name_scope("sparse_l1_descent_dummy_model"): + self.model.get_probs(tf.placeholder(tf.float32, shape=(None, 1000))) + self.sess.run(tf.global_variables_initializer()) + + x_val = np.random.rand(100, 1000) + x_val = np.array(x_val, dtype=np.float32) + + for q in [1, 9, 25.8, 50, 75.4, 90.2, 99, 99.9]: + x_adv = self.attack.generate_np( + x_val, eps=5.0, grad_sparsity=q, nb_iter=1, sanity_checks=False + ) + + numzero = np.sum(x_adv - x_val == 0, axis=-1) + self.assertAlmostEqual(q * 1000.0 / 100.0, np.mean(numzero), delta=1) + + def test_grad_sparsity_checks(self): + # test that the attacks allows `grad_sparsity` to be specified as a scalar + # in (0, 100) or as a vector. + + x_val = np.random.rand(100, 2) + x_val = np.array(x_val, dtype=np.float32) + + # scalar values out of range + with self.assertRaises(ValueError) as context: + self.attack.generate(x_val, sanity_checks=False, grad_sparsity=0) + self.assertTrue(context.exception) + + with self.assertRaises(ValueError) as context: + self.attack.generate(x_val, sanity_checks=False, grad_sparsity=100) + self.assertTrue(context.exception) + + # sparsity as 2D array should fail + with self.assertRaises(ValueError) as context: + gs = tf.random.uniform(shape=(100, 2), minval=90, maxval=99) + self.attack.generate(x_val, sanity_checks=False, grad_sparsity=gs) + self.assertTrue(context.exception) + + # sparsity as 1D array should succeed + gs = tf.random.uniform(shape=(100,), minval=90, maxval=99) + x_adv = self.attack.generate(x_val, sanity_checks=False, grad_sparsity=gs) + self.assertTrue(np.array_equal(x_adv.get_shape().as_list(), [100, 2])) + + # sparsity vector of wrong size should fail + with self.assertRaises(ValueError) as context: + gs = tf.random.uniform(shape=(101,), minval=90, maxval=99) + x_adv = self.attack.generate(x_val, sanity_checks=False, grad_sparsity=gs) + self.assertTrue(context.exception) + + +class TestCarliniWagnerL2(CleverHansTest): + def setUp(self): + super(TestCarliniWagnerL2, self).setUp() + + self.sess = tf.Session() + self.model = SimpleModel() + self.attack = CarliniWagnerL2(self.model, sess=self.sess) + + def test_generate_np_untargeted_gives_adversarial_example(self): + x_val = np.random.rand(100, 2) + x_val = np.array(x_val, dtype=np.float32) + + x_adv = self.attack.generate_np( + x_val, + max_iterations=100, + binary_search_steps=3, + initial_const=1, + clip_min=-5, + clip_max=5, + batch_size=10, + ) + + orig_labs = np.argmax(self.sess.run(self.model.get_logits(x_val)), axis=1) + new_labs = np.argmax(self.sess.run(self.model.get_logits(x_adv)), axis=1) + + self.assertTrue(np.mean(orig_labs == new_labs) < 0.1) + + def test_generate_np_targeted_gives_adversarial_example(self): + x_val = np.random.rand(100, 2) + x_val = np.array(x_val, dtype=np.float32) + + feed_labs = np.zeros((100, 2)) + feed_labs[np.arange(100), np.random.randint(0, 1, 100)] = 1 + x_adv = self.attack.generate_np( + x_val, + max_iterations=100, + binary_search_steps=3, + initial_const=1, + clip_min=-5, + clip_max=5, + batch_size=100, + y_target=feed_labs, + ) + + new_labs = np.argmax(self.sess.run(self.model.get_logits(x_adv)), axis=1) + + self.assertTrue(np.mean(np.argmax(feed_labs, axis=1) == new_labs) > 0.9) + + def test_generate_gives_adversarial_example(self): + + x_val = np.random.rand(100, 2) + x_val = np.array(x_val, dtype=np.float32) + + orig_labs = np.argmax(self.sess.run(self.model.get_logits(x_val)), axis=1) + feed_labs = np.zeros((100, 2)) + feed_labs[np.arange(100), orig_labs] = 1 + x = tf.placeholder(tf.float32, x_val.shape) + y = tf.placeholder(tf.float32, feed_labs.shape) + + x_adv_p = self.attack.generate( + x, + max_iterations=100, + binary_search_steps=3, + initial_const=1, + clip_min=-5, + clip_max=5, + batch_size=100, + y=y, + ) + self.assertEqual(x_val.shape, x_adv_p.shape) + x_adv = self.sess.run(x_adv_p, {x: x_val, y: feed_labs}) + + new_labs = np.argmax(self.sess.run(self.model.get_logits(x_adv)), axis=1) + + self.assertTrue(np.mean(orig_labs == new_labs) < 0.1) + + def test_generate_np_gives_clipped_adversarial_examples(self): + x_val = np.random.rand(100, 2) + x_val = np.array(x_val, dtype=np.float32) + + x_adv = self.attack.generate_np( + x_val, + max_iterations=10, + binary_search_steps=1, + learning_rate=1e-3, + initial_const=1, + clip_min=-0.2, + clip_max=0.3, + batch_size=100, + ) + + self.assertTrue(-0.201 < np.min(x_adv)) + self.assertTrue(np.max(x_adv) < 0.301) + + def test_generate_np_high_confidence_targeted_examples(self): + + trivial_model = TrivialModel() + + for CONFIDENCE in [0, 2.3]: + x_val = np.random.rand(10, 1) - 0.5 + x_val = np.array(x_val, dtype=np.float32) + + feed_labs = np.zeros((10, 2)) + feed_labs[np.arange(10), np.random.randint(0, 2, 10)] = 1 + attack = CarliniWagnerL2(trivial_model, sess=self.sess) + x_adv = attack.generate_np( + x_val, + max_iterations=100, + binary_search_steps=2, + learning_rate=1e-2, + initial_const=1, + clip_min=-10, + clip_max=10, + confidence=CONFIDENCE, + y_target=feed_labs, + batch_size=10, + ) + + new_labs = self.sess.run(trivial_model.get_logits(x_adv)) + + good_labs = new_labs[np.arange(10), np.argmax(feed_labs, axis=1)] + bad_labs = new_labs[np.arange(10), 1 - np.argmax(feed_labs, axis=1)] + + self.assertClose(CONFIDENCE, np.min(good_labs - bad_labs), atol=1e-1) + self.assertTrue( + np.mean(np.argmax(new_labs, axis=1) == np.argmax(feed_labs, axis=1)) + > 0.9 + ) + + def test_generate_np_high_confidence_untargeted_examples(self): + + trivial_model = TrivialModel() + + for CONFIDENCE in [0, 2.3]: + x_val = np.random.rand(10, 1) - 0.5 + x_val = np.array(x_val, dtype=np.float32) + + orig_labs = np.argmax( + self.sess.run(trivial_model.get_logits(x_val)), axis=1 + ) + attack = CarliniWagnerL2(trivial_model, sess=self.sess) + x_adv = attack.generate_np( + x_val, + max_iterations=100, + binary_search_steps=2, + learning_rate=1e-2, + initial_const=1, + clip_min=-10, + clip_max=10, + confidence=CONFIDENCE, + batch_size=10, + ) + + new_labs = self.sess.run(trivial_model.get_logits(x_adv)) + + good_labs = new_labs[np.arange(10), 1 - orig_labs] + bad_labs = new_labs[np.arange(10), orig_labs] + + self.assertTrue(np.mean(np.argmax(new_labs, axis=1) == orig_labs) == 0) + self.assertTrue( + np.isclose(0, np.min(good_labs - (bad_labs + CONFIDENCE)), atol=1e-1) + ) + + +class TestElasticNetMethod(CleverHansTest): + def setUp(self): + super(TestElasticNetMethod, self).setUp() + + self.sess = tf.Session() + self.model = SimpleModel() + self.attack = ElasticNetMethod(self.model, sess=self.sess) + + def test_generate_np_untargeted_gives_adversarial_example(self): + x_val = np.random.rand(100, 2) + x_val = np.array(x_val, dtype=np.float32) + + x_adv = self.attack.generate_np( + x_val, + max_iterations=100, + binary_search_steps=3, + initial_const=1, + clip_min=-5, + clip_max=5, + batch_size=10, + ) + + orig_labs = np.argmax(self.sess.run(self.model.get_logits(x_val)), axis=1) + new_labs = np.argmax(self.sess.run(self.model.get_logits(x_adv)), axis=1) + + self.assertTrue(np.mean(orig_labs == new_labs) < 0.1) + + def test_generate_np_targeted_gives_adversarial_example(self): + x_val = np.random.rand(100, 2) + x_val = np.array(x_val, dtype=np.float32) + + feed_labs = np.zeros((100, 2)) + feed_labs[np.arange(100), np.random.randint(0, 1, 100)] = 1 + x_adv = self.attack.generate_np( + x_val, + max_iterations=100, + binary_search_steps=3, + initial_const=1, + clip_min=-5, + clip_max=5, + batch_size=100, + y_target=feed_labs, + ) + + new_labs = np.argmax(self.sess.run(self.model.get_logits(x_adv)), axis=1) + + self.assertTrue(np.mean(np.argmax(feed_labs, axis=1) == new_labs) > 0.9) + + def test_generate_gives_adversarial_example(self): + + x_val = np.random.rand(100, 2) + x_val = np.array(x_val, dtype=np.float32) + + orig_labs = np.argmax(self.sess.run(self.model.get_logits(x_val)), axis=1) + feed_labs = np.zeros((100, 2)) + feed_labs[np.arange(100), orig_labs] = 1 + x = tf.placeholder(tf.float32, x_val.shape) + y = tf.placeholder(tf.float32, feed_labs.shape) + + x_adv_p = self.attack.generate( + x, + max_iterations=100, + binary_search_steps=3, + initial_const=1, + clip_min=-5, + clip_max=5, + batch_size=100, + y=y, + ) + self.assertEqual(x_val.shape, x_adv_p.shape) + x_adv = self.sess.run(x_adv_p, {x: x_val, y: feed_labs}) + + new_labs = np.argmax(self.sess.run(self.model.get_logits(x_adv)), axis=1) + + self.assertTrue(np.mean(orig_labs == new_labs) < 0.1) + + def test_generate_np_gives_clipped_adversarial_examples(self): + x_val = np.random.rand(100, 2) + x_val = np.array(x_val, dtype=np.float32) + + x_adv = self.attack.generate_np( + x_val, + max_iterations=10, + binary_search_steps=1, + learning_rate=1e-3, + initial_const=1, + clip_min=-0.2, + clip_max=0.3, + batch_size=100, + ) + + self.assertTrue(-0.201 < np.min(x_adv)) + self.assertTrue(np.max(x_adv) < 0.301) + + def test_generate_np_high_confidence_targeted_examples(self): + + trivial_model = TrivialModel() + + for CONFIDENCE in [0, 2.3]: + x_val = np.random.rand(10, 1) - 0.5 + x_val = np.array(x_val, dtype=np.float32) + + feed_labs = np.zeros((10, 2)) + feed_labs[np.arange(10), np.random.randint(0, 2, 10)] = 1 + attack = CarliniWagnerL2(trivial_model, sess=self.sess) + x_adv = attack.generate_np( + x_val, + max_iterations=100, + binary_search_steps=2, + learning_rate=1e-2, + initial_const=1, + clip_min=-10, + clip_max=10, + confidence=CONFIDENCE, + y_target=feed_labs, + batch_size=10, + ) + + new_labs = self.sess.run(trivial_model.get_logits(x_adv)) + + good_labs = new_labs[np.arange(10), np.argmax(feed_labs, axis=1)] + bad_labs = new_labs[np.arange(10), 1 - np.argmax(feed_labs, axis=1)] + + self.assertTrue( + np.isclose(0, np.min(good_labs - (bad_labs + CONFIDENCE)), atol=1e-1) + ) + self.assertTrue( + np.mean(np.argmax(new_labs, axis=1) == np.argmax(feed_labs, axis=1)) + > 0.9 + ) + + def test_generate_np_high_confidence_untargeted_examples(self): + + trivial_model = TrivialModel() + + for CONFIDENCE in [0, 2.3]: + x_val = np.random.rand(10, 1) - 0.5 + x_val = np.array(x_val, dtype=np.float32) + + orig_labs = np.argmax( + self.sess.run(trivial_model.get_logits(x_val)), axis=1 + ) + attack = CarliniWagnerL2(trivial_model, sess=self.sess) + x_adv = attack.generate_np( + x_val, + max_iterations=100, + binary_search_steps=2, + learning_rate=1e-2, + initial_const=1, + clip_min=-10, + clip_max=10, + confidence=CONFIDENCE, + batch_size=10, + ) + + new_labs = self.sess.run(trivial_model.get_logits(x_adv)) + + good_labs = new_labs[np.arange(10), 1 - orig_labs] + bad_labs = new_labs[np.arange(10), orig_labs] + + self.assertTrue(np.mean(np.argmax(new_labs, axis=1) == orig_labs) == 0) + self.assertTrue( + np.isclose(0, np.min(good_labs - (bad_labs + CONFIDENCE)), atol=1e-1) + ) + + +class TestSaliencyMapMethod(CleverHansTest): + def setUp(self): + super(TestSaliencyMapMethod, self).setUp() + + self.sess = tf.Session() + self.sess.as_default() + self.model = DummyModel() + self.attack = SaliencyMapMethod(self.model, sess=self.sess) + + # initialize model + with tf.name_scope("dummy_model"): + self.model.get_logits(tf.placeholder(tf.float32, shape=(None, 1000))) + self.sess.run(tf.global_variables_initializer()) + + self.attack = SaliencyMapMethod(self.model, sess=self.sess) + + def test_generate_np_targeted_gives_adversarial_example(self): + x_val = np.random.rand(10, 1000) + x_val = np.array(x_val, dtype=np.float32) + + feed_labs = np.zeros((10, 10)) + feed_labs[np.arange(10), np.random.randint(0, 9, 10)] = 1 + x_adv = self.attack.generate_np( + x_val, clip_min=-5.0, clip_max=5.0, y_target=feed_labs + ) + new_labs = np.argmax(self.sess.run(self.model.get_logits(x_adv)), axis=1) + + worked = np.mean(np.argmax(feed_labs, axis=1) == new_labs) + self.assertTrue(worked > 0.9) + + +class TestDeepFool(CleverHansTest): + def setUp(self): + super(TestDeepFool, self).setUp() + + self.sess = tf.Session() + self.model = SimpleModel() + self.attack = DeepFool(self.model, sess=self.sess) + + def test_generate_np_gives_adversarial_example(self): + x_val = np.random.rand(100, 2) + x_val = np.array(x_val, dtype=np.float32) + + x_adv = self.attack.generate_np( + x_val, overshoot=0.02, max_iter=50, nb_candidate=2, clip_min=-5, clip_max=5 + ) + + orig_labs = np.argmax(self.sess.run(self.model.get_logits(x_val)), axis=1) + new_labs = np.argmax(self.sess.run(self.model.get_logits(x_adv)), axis=1) + + self.assertTrue(np.mean(orig_labs == new_labs) < 0.1) + + def test_generate_gives_adversarial_example(self): + x_val = np.random.rand(100, 2) + x_val = np.array(x_val, dtype=np.float32) + + orig_labs = np.argmax(self.sess.run(self.model.get_logits(x_val)), axis=1) + x = tf.placeholder(tf.float32, x_val.shape) + + x_adv_p = self.attack.generate( + x, overshoot=0.02, max_iter=50, nb_candidate=2, clip_min=-5, clip_max=5 + ) + self.assertEqual(x_val.shape, x_adv_p.shape) + x_adv = self.sess.run(x_adv_p, {x: x_val}) + + new_labs = np.argmax(self.sess.run(self.model.get_logits(x_adv)), axis=1) + + self.assertTrue(np.mean(orig_labs == new_labs) < 0.1) + + def test_generate_np_gives_clipped_adversarial_examples(self): + x_val = np.random.rand(100, 2) + x_val = np.array(x_val, dtype=np.float32) + + x_adv = self.attack.generate_np( + x_val, + overshoot=0.02, + max_iter=50, + nb_candidate=2, + clip_min=-0.2, + clip_max=0.3, + ) + + self.assertTrue(-0.201 < np.min(x_adv)) + self.assertTrue(np.max(x_adv) < 0.301) + + +class TestMomentumIterativeMethod(TestProjectedGradientDescent): + def setUp(self): + super(TestMomentumIterativeMethod, self).setUp() + + self.attack = MomentumIterativeMethod(self.model, sess=self.sess) + + def test_generate_np_can_be_called_with_different_decay_factor(self): + x_val = np.random.rand(100, 2) + x_val = np.array(x_val, dtype=np.float32) + + for decay_factor in [0.0, 0.5, 1.0]: + x_adv = self.attack.generate_np( + x_val, + eps=0.5, + ord=np.inf, + decay_factor=decay_factor, + clip_min=-5.0, + clip_max=5.0, + ) + + delta = np.max(np.abs(x_adv - x_val), axis=1) + self.assertClose(delta, 0.5) + + def test_multiple_initial_random_step(self): + # There is no initial random step, so nothing to test here + pass + + +class TestMadryEtAl(CleverHansTest): + def setUp(self): + super(TestMadryEtAl, self).setUp() + self.model = DummyModel("madryetal_dummy_model") + self.sess = tf.Session() + + def test_attack_can_be_constructed(self): + # The test passes if this does not raise an exception + self.attack = MadryEtAl(self.model, sess=self.sess) + + +class TestBasicIterativeMethod(CleverHansTest): + def setUp(self): + super(TestBasicIterativeMethod, self).setUp() + self.model = DummyModel("bim_dummy_model") + self.sess = tf.Session() + + def test_attack_can_be_constructed(self): + # The test passes if this raises no exceptions + self.attack = BasicIterativeMethod(self.model, sess=self.sess) + + +class TestFastFeatureAdversaries(CleverHansTest): + def setUp(self): + super(TestFastFeatureAdversaries, self).setUp() + + def make_imagenet_cnn(input_shape=(None, 224, 224, 3)): + """ + Similar CNN to AlexNet. + """ + + class ModelImageNetCNN(Model): + def __init__(self, scope, nb_classes=1000, **kwargs): + del kwargs + Model.__init__(self, scope, nb_classes, locals()) + + def fprop(self, x, **kwargs): + del kwargs + my_conv = functools.partial( + tf.layers.conv2d, + kernel_size=3, + strides=2, + padding="valid", + activation=tf.nn.relu, + kernel_initializer=HeReLuNormalInitializer, + ) + my_dense = functools.partial( + tf.layers.dense, kernel_initializer=HeReLuNormalInitializer + ) + with tf.variable_scope(self.scope, reuse=tf.AUTO_REUSE): + for depth in [96, 256, 384, 384, 256]: + x = my_conv(x, depth) + y = tf.layers.flatten(x) + y = my_dense(y, 4096, tf.nn.relu) + y = fc7 = my_dense(y, 4096, tf.nn.relu) + y = my_dense(y, 1000) + return { + "fc7": fc7, + self.O_LOGITS: y, + self.O_PROBS: tf.nn.softmax(logits=y), + } + + return ModelImageNetCNN("imagenet") + + self.input_shape = [10, 224, 224, 3] + self.sess = tf.Session() + self.model = make_imagenet_cnn(self.input_shape) + self.attack = FastFeatureAdversaries(self.model, sess=self.sess) + + def test_attack_strength(self): + """ + This test generates a random source and guide and feeds them in a + randomly initialized CNN. Checks if an adversarial example can get + at least 50% closer to the guide compared to the original distance of + the source and the guide. + """ + tf.set_random_seed(1234) + input_shape = self.input_shape + x_src = tf.abs(tf.random_uniform(input_shape, 0.0, 1.0)) + x_guide = tf.abs(tf.random_uniform(input_shape, 0.0, 1.0)) + + layer = "fc7" + attack_params = { + "eps": 5.0 / 256, + "clip_min": 0.0, + "clip_max": 1.0, + "nb_iter": 10, + "eps_iter": 0.005, + "layer": layer, + } + x_adv = self.attack.generate(x_src, x_guide, **attack_params) + h_adv = self.model.fprop(x_adv)[layer] + h_src = self.model.fprop(x_src)[layer] + h_guide = self.model.fprop(x_guide)[layer] + + init = tf.global_variables_initializer() + self.sess.run(init) + + ha, hs, hg, _xa, _xs, _xg = self.sess.run( + [h_adv, h_src, h_guide, x_adv, x_src, x_guide] + ) + d_as = np.sqrt(((hs - ha) * (hs - ha)).sum()) + d_ag = np.sqrt(((hg - ha) * (hg - ha)).sum()) + d_sg = np.sqrt(((hg - hs) * (hg - hs)).sum()) + print( + "L2 distance between source and adversarial example `%s`: %.4f" + % (layer, d_as) + ) + print( + "L2 distance between guide and adversarial example `%s`: %.4f" + % (layer, d_ag) + ) + print("L2 distance between source and guide `%s`: %.4f" % (layer, d_sg)) + print("d_ag/d_sg*100 `%s`: %.4f" % (layer, d_ag * 100 / d_sg)) + self.assertTrue(d_ag * 100 / d_sg < 50.0) + + +class TestLBFGS(CleverHansTest): + def setUp(self): + super(TestLBFGS, self).setUp() + + self.sess = tf.Session() + self.model = SimpleModel() + self.attack = LBFGS(self.model, sess=self.sess) + + def test_generate_np_targeted_gives_adversarial_example(self): + x_val = np.random.rand(100, 2) + x_val = np.array(x_val, dtype=np.float32) + + feed_labs = np.zeros((100, 2)) + feed_labs[np.arange(100), np.random.randint(0, 1, 100)] = 1 + x_adv = self.attack.generate_np( + x_val, + max_iterations=100, + binary_search_steps=3, + initial_const=1, + clip_min=-5, + clip_max=5, + batch_size=100, + y_target=feed_labs, + ) + + new_labs = np.argmax(self.sess.run(self.model.get_logits(x_adv)), axis=1) + + self.assertTrue(np.mean(np.argmax(feed_labs, axis=1) == new_labs) > 0.9) + + def test_generate_targeted_gives_adversarial_example(self): + x_val = np.random.rand(100, 2) + x_val = np.array(x_val, dtype=np.float32) + + feed_labs = np.zeros((100, 2)) + feed_labs[np.arange(100), np.random.randint(0, 1, 100)] = 1 + x = tf.placeholder(tf.float32, x_val.shape) + y = tf.placeholder(tf.float32, feed_labs.shape) + + x_adv_p = self.attack.generate( + x, + max_iterations=100, + binary_search_steps=3, + initial_const=1, + clip_min=-5, + clip_max=5, + batch_size=100, + y_target=y, + ) + self.assertEqual(x_val.shape, x_adv_p.shape) + x_adv = self.sess.run(x_adv_p, {x: x_val, y: feed_labs}) + + new_labs = np.argmax(self.sess.run(self.model.get_logits(x_adv)), axis=1) + + self.assertTrue(np.mean(np.argmax(feed_labs, axis=1) == new_labs) > 0.9) + + def test_generate_np_gives_clipped_adversarial_examples(self): + x_val = np.random.rand(100, 2) + x_val = np.array(x_val, dtype=np.float32) + + feed_labs = np.zeros((100, 2)) + feed_labs[np.arange(100), np.random.randint(0, 1, 100)] = 1 + x_adv = self.attack.generate_np( + x_val, + max_iterations=10, + binary_search_steps=1, + initial_const=1, + clip_min=-0.2, + clip_max=0.3, + batch_size=100, + y_target=feed_labs, + ) + + self.assertTrue(-0.201 < np.min(x_adv)) + self.assertTrue(np.max(x_adv) < 0.301) + + +class SimpleSpatialBrightPixelModel(Model): + """ + If there is a bright pixel in the image returns the first class. + Otherwise returns the second class. Spatial attack should push the + bright pixels off of the image. + """ + + def __init__(self, scope="simple_spatial", nb_classes=2, **kwargs): + del kwargs + Model.__init__(self, scope, nb_classes, locals()) + + def fprop(self, x, **kwargs): + del kwargs + + flat_x = slim.flatten(x) + first_logit = tf.reduce_max(flat_x, axis=1) + second_logit = tf.ones_like(first_logit) * 0.5 + res = tf.stack([second_logit, first_logit], axis=1) + return {self.O_LOGITS: res, self.O_PROBS: tf.nn.softmax(res)} + + +@unittest.skipIf( + [int(v) for v in tf.__version__.split(".")[:2]] < [1, 6], + "SpatialAttack requires tf 1.6 or higher", +) +class TestSpatialTransformationMethod(CleverHansTest): + """Tests for SpatialTransformationMethod""" + + def setUp(self): + """ + Allocate session, model, and attack + initialize tf Variables + """ + super(TestSpatialTransformationMethod, self).setUp() + + self.sess = tf.Session() + self.model = SimpleSpatialBrightPixelModel() + self.attack = SpatialTransformationMethod(self.model, sess=self.sess) + + # initialize model + with tf.name_scope("dummy_model_spatial"): + self.model.get_logits(tf.placeholder(tf.float32, shape=(None, 2, 2, 1))) + self.sess.run(tf.global_variables_initializer()) + + def test_no_transformation(self): + """Test that setting transformation params to 0. is a no-op""" + x_val = np.random.rand(100, 2, 2, 1) + x_val = np.array(x_val, dtype=np.float32) + x = tf.placeholder(tf.float32, shape=(None, 2, 2, 1)) + + x_adv_p = self.attack.generate( + x, + batch_size=100, + dx_min=0.0, + dx_max=0.0, + n_dxs=1, + dy_min=0.0, + dy_max=0.0, + n_dys=1, + angle_min=0, + angle_max=0, + n_angles=1, + ) + x_adv = self.sess.run(x_adv_p, {x: x_val}) + self.assertClose(x_adv, x_val) + + def test_push_pixels_off_image(self): + """Test that the attack pushes some pixels off the image""" + x_val = np.random.rand(100, 2, 2, 1) + x_val = np.array(x_val, dtype=np.float32) + + # The correct answer is that they are bright + # So the attack must push the pixels off the edge + y = np.zeros([100, 2]) + y[:, 0] = 1.0 + + x = tf.placeholder(tf.float32, shape=(None, 2, 2, 1)) + x_adv_p = self.attack.generate( + x, + y=y, + batch_size=100, + dx_min=-0.5, + dx_max=0.5, + n_dxs=3, + dy_min=-0.5, + dy_max=0.5, + n_dys=3, + angle_min=0, + angle_max=0, + n_angles=1, + ) + x_adv = self.sess.run(x_adv_p, {x: x_val}) + + old_labs = np.argmax(y, axis=1) + new_labs = np.argmax(self.sess.run(self.model.get_logits(x_adv)), axis=1) + print(np.mean(old_labs == new_labs)) + self.assertTrue(np.mean(old_labs == new_labs) < 0.3) + + def test_keep_pixels_on_image(self): + """Test that the attack does not push some pixels off the image""" + x_val = np.random.rand(100, 2, 2, 1) + x_val = np.array(x_val, dtype=np.float32) + + # The correct answer is that they are NOT bright + # So the attack must NOT push the pixels off the edge + y = np.zeros([100, 2]) + y[:, 0] = 1.0 + + x = tf.placeholder(tf.float32, shape=(None, 2, 2, 1)) + x_adv_p = self.attack.generate( + x, + y=y, + batch_size=100, + dx_min=-0.5, + dx_max=0.5, + n_dxs=3, + dy_min=-0.5, + dy_max=0.5, + n_dys=3, + angle_min=0, + angle_max=0, + n_angles=1, + ) + x_adv = self.sess.run(x_adv_p, {x: x_val}) + + old_labs = np.argmax(y, axis=1) + new_labs = np.argmax(self.sess.run(self.model.get_logits(x_adv)), axis=1) + print(np.mean(old_labs == new_labs)) + self.assertTrue(np.mean(old_labs == new_labs) < 0.3) + + +class TestHopSkipJumpAttack(CleverHansTest): + """Tests for Test HopSkipJumpAttack""" + + def setUp(self): + super(TestHopSkipJumpAttack, self).setUp() + + self.sess = tf.Session() + self.model = SimpleModel() + self.attack = HopSkipJumpAttack(self.model, sess=self.sess) + + def test_generate_np_untargeted_l2(self): + x_val = np.random.rand(50, 2) + x_val = np.array(x_val, dtype=np.float32) + bapp_params = { + "constraint": "l2", + "stepsize_search": "geometric_progression", + "num_iterations": 10, + "verbose": True, + } + x_adv = self.attack.generate_np(x_val, **bapp_params) + + orig_labs = np.argmax(self.sess.run(self.model.get_logits(x_val)), axis=1) + new_labs = np.argmax(self.sess.run(self.model.get_logits(x_adv)), axis=1) + + self.assertTrue(np.mean(orig_labs == new_labs) < 0.1) + + def test_generate_untargeted_linf(self): + + x_val = np.random.rand(50, 2) + x_val = np.array(x_val, dtype=np.float32) + + orig_labs = np.argmax(self.sess.run(self.model.get_logits(x_val)), axis=1) + + # Requires input to have batchsize 1. + x = tf.placeholder(tf.float32, [1, 2]) + + bapp_params = { + "constraint": "linf", + "stepsize_search": "grid_search", + "num_iterations": 10, + "verbose": True, + } + x_adv_p = self.attack.generate(x, **bapp_params) + + self.assertEqual(x_adv_p.shape, [1, 2]) + x_adv = [] + for single_x_val in x_val: + single_x_adv = self.sess.run(x_adv_p, {x: np.expand_dims(single_x_val, 0)}) + x_adv.append(single_x_adv) + + x_adv = np.concatenate(x_adv, axis=0) + + new_labs = np.argmax(self.sess.run(self.model.get_logits(x_adv)), axis=1) + + self.assertTrue(np.mean(orig_labs == new_labs) < 0.1) + + def test_generate_np_targeted_linf(self): + x_val = np.random.rand(200, 2) + x_val = np.array(x_val, dtype=np.float32) + + orig_labs = np.argmax(self.sess.run(self.model.get_logits(x_val)), axis=1) + x_val_pos = x_val[orig_labs == 1] + x_val_neg = x_val[orig_labs == 0] + + x_val_under_attack = np.concatenate((x_val_pos[:25], x_val_neg[:25]), axis=0) + y_target = np.eye(2)[np.concatenate((np.zeros(25), np.ones(25))).astype(int)] + image_target = np.concatenate((x_val_neg[25:50], x_val_pos[25:50]), axis=0) + + bapp_params = { + "constraint": "linf", + "stepsize_search": "geometric_progression", + "num_iterations": 10, + "verbose": True, + "y_target": y_target, + "image_target": image_target, + } + x_adv = self.attack.generate_np(x_val_under_attack, **bapp_params) + + new_labs = np.argmax(self.sess.run(self.model.get_logits(x_adv)), axis=1) + + self.assertTrue(np.mean(np.argmax(y_target, axis=1) == new_labs) > 0.9) + + def test_generate_targeted_l2(self): + + # Create data in numpy arrays. + x_val = np.random.rand(200, 2) + x_val = np.array(x_val, dtype=np.float32) + orig_labs = np.argmax(self.sess.run(self.model.get_logits(x_val)), axis=1) + x_val_pos = x_val[orig_labs == 1] + x_val_neg = x_val[orig_labs == 0] + x_val_under_attack = np.concatenate((x_val_pos[:25], x_val_neg[:25]), axis=0) + y_target = np.eye(2)[np.concatenate((np.zeros(25), np.ones(25))).astype(int)] + image_target = np.concatenate((x_val_neg[25:50], x_val_pos[25:50]), axis=0) + + # Create placeholders. + # Require input has batchsize 1. + x = tf.placeholder(tf.float32, [1, 2]) + y_target_ph = tf.placeholder(tf.float32, [1, 2]) + image_target_ph = tf.placeholder(tf.float32, [1, 2]) + + # Create graph. + bapp_params = { + "constraint": "l2", + "stepsize_search": "grid_search", + "num_iterations": 10, + "verbose": True, + "y_target": y_target_ph, + "image_target": image_target_ph, + } + x_adv_p = self.attack.generate(x, **bapp_params) + self.assertEqual(x_adv_p.shape, [1, 2]) + + # Generate adversarial examples. + x_adv = [] + for i, single_x_val in enumerate(x_val_under_attack): + print(image_target.shape, y_target.shape) + single_x_adv = self.sess.run( + x_adv_p, + { + x: np.expand_dims(single_x_val, 0), + y_target_ph: np.expand_dims(y_target[i], 0), + image_target_ph: np.expand_dims(image_target[i], 0), + }, + ) + x_adv.append(single_x_adv) + x_adv = np.concatenate(x_adv, axis=0) + + new_labs = np.argmax(self.sess.run(self.model.get_logits(x_adv)), axis=1) + self.assertTrue(np.mean(np.argmax(y_target, axis=1) == new_labs) > 0.9) diff --git a/cleverhans_v3.1.0/tests_tf/test_attacks_tf.py b/cleverhans_v3.1.0/tests_tf/test_attacks_tf.py new file mode 100644 index 000000000..e057f2c6c --- /dev/null +++ b/cleverhans_v3.1.0/tests_tf/test_attacks_tf.py @@ -0,0 +1,281 @@ +"""Tests of cleverhans.attacks_tf + +""" +# pylint: disable=missing-docstring +from functools import partial +import unittest + +import numpy as np +import tensorflow as tf + +from cleverhans.devtools.checks import CleverHansTest +from cleverhans.attacks_tf import ( + fgm, + pgd_attack, + UnrolledAdam, + UnrolledGradientDescent, + parallel_apply_transformations, +) +from cleverhans.devtools.mocks import random_feed_dict +from cleverhans.model import Model + + +class SimpleModel(Model): + """ + A very simple neural network + """ + + def __init__(self, scope="simple", nb_classes=2, **kwargs): + del kwargs + Model.__init__(self, scope, nb_classes, locals()) + + def fprop(self, x, **kwargs): + del kwargs + with tf.variable_scope(self.scope, reuse=tf.AUTO_REUSE): + w1 = tf.constant([[1.5, 0.3], [-2, 0.3]], dtype=tf.as_dtype(x.dtype)) + w2 = tf.constant([[-2.4, 1.2], [0.5, -2.3]], dtype=tf.as_dtype(x.dtype)) + h1 = tf.nn.sigmoid(tf.matmul(x, w1)) + res = tf.matmul(h1, w2) + return {self.O_LOGITS: res, self.O_PROBS: tf.nn.softmax(res)} + + +class TestAttackTF(CleverHansTest): + def setUp(self): + super(TestAttackTF, self).setUp() + self.sess = tf.Session() + self.model = SimpleModel() + + def test_fgm_gradient_max(self): + input_dim = 2 + nb_classes = 3 + batch_size = 4 + rng = np.random.RandomState([2017, 8, 23]) + x = tf.placeholder(tf.float32, [batch_size, input_dim]) + weights = tf.placeholder(tf.float32, [input_dim, nb_classes]) + logits = tf.matmul(x, weights) + probs = tf.nn.softmax(logits) + adv_x = fgm(x, probs) + random_example = rng.randint(batch_size) + random_feature = rng.randint(input_dim) + output = tf.slice(adv_x, [random_example, random_feature], [1, 1]) + (dx,) = tf.gradients(output, x) + # The following line catches GitHub issue #243 + self.assertIsNotNone(dx) + dx = self.sess.run(dx, feed_dict=random_feed_dict(rng, [x, weights])) + ground_truth = np.zeros((batch_size, input_dim)) + ground_truth[random_example, random_feature] = 1.0 + self.assertClose(dx, ground_truth) + + def helper_pgd_attack( + self, + unrolled_optimizer, + targeted, + nb_iters=20, + epsilon=0.5, + clip_min=-5.0, + clip_max=5.0, + assert_threshold=0.5, + ): + def loss_fn(input_image, label, targeted): + res = self.model.fprop(input_image) + logits = res[self.model.O_LOGITS] + multiplier = 1.0 if targeted else -1.0 + return multiplier * tf.nn.sparse_softmax_cross_entropy_with_logits( + labels=label, logits=logits + ) + + x_val_ph = tf.placeholder(tf.float32, shape=[100, 2]) + x_val = np.random.randn(100, 2).astype(np.float32) + init_model_output = self.model.fprop(x_val_ph) + init_model_logits = init_model_output[self.model.O_LOGITS] + if targeted: + labels = np.random.random_integers(0, 1, size=(100,)) + else: + + labels = tf.stop_gradient(tf.argmax(init_model_logits, axis=1)) + + def _project_perturbation( + perturbation, epsilon, input_image, clip_min, clip_max + ): + clipped_perturbation = tf.clip_by_value(perturbation, -epsilon, epsilon) + new_image = tf.clip_by_value( + input_image + clipped_perturbation, clip_min, clip_max + ) + return new_image - input_image + + x_adv = pgd_attack( + loss_fn=partial(loss_fn, targeted=targeted), + input_image=x_val_ph, + label=labels, + epsilon=epsilon, + num_steps=nb_iters, + optimizer=unrolled_optimizer, + project_perturbation=_project_perturbation, + clip_min=clip_min, + clip_max=clip_max, + ) + + final_model_output = self.model.fprop(x_adv) + final_model_logits = final_model_output[self.model.O_LOGITS] + + if not targeted: + logits1, logits2 = self.sess.run( + [init_model_logits, final_model_logits], feed_dict={x_val_ph: x_val} + ) + preds1 = np.argmax(logits1, axis=1) + preds2 = np.argmax(logits2, axis=1) + + self.assertTrue( + np.mean(preds1 == preds2) < assert_threshold, np.mean(preds1 == preds2) + ) + + else: + logits_adv = self.sess.run(final_model_logits, feed_dict={x_val_ph: x_val}) + preds_adv = np.argmax(logits_adv, axis=1) + + self.assertTrue(np.mean(labels == preds_adv) > assert_threshold) + + def test_pgd_untargeted_attack_with_adam_optimizer(self): + unrolled_optimizer = UnrolledAdam(lr=0.1) + self.helper_pgd_attack( + unrolled_optimizer=unrolled_optimizer, + targeted=False, + epsilon=0.5, + nb_iters=20, + clip_min=-10.0, + clip_max=10.0, + assert_threshold=0.7, + ) + + def test_stronger_pgd_untargeted_attack_with_adam_optimizer(self): + unrolled_optimizer = UnrolledAdam(lr=0.1) + self.helper_pgd_attack( + unrolled_optimizer=unrolled_optimizer, + targeted=False, + epsilon=5.0, + nb_iters=100, + clip_min=-10.0, + clip_max=10.0, + assert_threshold=0.1, + ) + + def test_pgd_targeted_attack_with_adam_optimizer(self): + unrolled_optimizer = UnrolledAdam(lr=0.1) + self.helper_pgd_attack( + unrolled_optimizer=unrolled_optimizer, + targeted=True, + epsilon=0.5, + nb_iters=20, + clip_min=-10.0, + clip_max=10.0, + assert_threshold=0.7, + ) + + def test_stronger_pgd_targeted_attack_with_adam_optimizer(self): + unrolled_optimizer = UnrolledAdam(lr=0.1) + self.helper_pgd_attack( + unrolled_optimizer=unrolled_optimizer, + targeted=True, + epsilon=5.0, + nb_iters=100, + clip_min=-10.0, + clip_max=10.0, + assert_threshold=0.9, + ) + + def test_pgd_untargeted_attack_with_sgd_optimizer(self): + unrolled_optimizer = UnrolledGradientDescent(lr=1000.0) + self.helper_pgd_attack( + unrolled_optimizer=unrolled_optimizer, + targeted=False, + epsilon=0.5, + nb_iters=20, + clip_min=-10.0, + clip_max=10.0, + assert_threshold=0.6, + ) + + def test_stronger_pgd_untargeted_attack_with_sgd_optimizer(self): + unrolled_optimizer = UnrolledGradientDescent(lr=1000.0) + self.helper_pgd_attack( + unrolled_optimizer=unrolled_optimizer, + targeted=False, + epsilon=5.0, + nb_iters=100, + clip_min=-10.0, + clip_max=10.0, + assert_threshold=0.1, + ) + + def test_pgd_targeted_attack_with_sgd_optimizer(self): + unrolled_optimizer = UnrolledGradientDescent(lr=1000.0) + self.helper_pgd_attack( + unrolled_optimizer=unrolled_optimizer, + targeted=True, + epsilon=0.5, + nb_iters=20, + clip_min=-10.0, + clip_max=10.0, + assert_threshold=0.6, + ) + + def test_stronger_pgd_targeted_attack_with_sgd_optimizer(self): + unrolled_optimizer = UnrolledGradientDescent(lr=1000.0) + self.helper_pgd_attack( + unrolled_optimizer=unrolled_optimizer, + targeted=True, + epsilon=5.0, + nb_iters=100, + clip_min=-10.0, + clip_max=10.0, + assert_threshold=0.9, + ) + + @unittest.skip("This test requires human inspection of the images") + def test_parallel_apply(self): + def _save_image_to_png(image_np, filename): + from PIL import Image + import os + + dirname = os.path.dirname(filename) + if not os.path.exists(dirname): + os.makedirs(dirname) + + if image_np.shape[-1] == 3: + img = Image.fromarray(np.uint8(image_np * 255.0), "RGB") + else: + img = Image.fromarray(np.uint8(image_np[:, :, 0] * 255.0), "L") + img.save(filename) + + x = tf.ones([3, 200, 200, 3]) + + transforms = [ + [0.2, 0, 20], + [0, 0, 0], + # [-0.2, 0, 20], + # [-0.4, 0, 20], + ] + transformed_ims = parallel_apply_transformations( + x, transforms, black_border_size=30 + ) + + worst_sample_idx = tf.convert_to_tensor([0, 1, 1]) + batch_size = tf.shape(x)[0] + keys = tf.stack( + [tf.range(batch_size, dtype=tf.int32), tf.cast(worst_sample_idx, tf.int32)], + axis=1, + ) + + transformed_ims_bshwc = tf.einsum("sbhwc->bshwc", transformed_ims) + after_lookup = tf.gather_nd(transformed_ims_bshwc, keys) # BHWC + + with tf.Session() as sess: + img_batch_np = sess.run(after_lookup)[:, :, :, :] + + for i, img in enumerate(img_batch_np): + filename = "/tmp/test_image%s.png" % (i) + _save_image_to_png(img, filename) + + +if __name__ == "__main__": + unittest.main() diff --git a/cleverhans_v3.1.0/tests_tf/test_confidence_report.py b/cleverhans_v3.1.0/tests_tf/test_confidence_report.py new file mode 100644 index 000000000..d84ba753f --- /dev/null +++ b/cleverhans_v3.1.0/tests_tf/test_confidence_report.py @@ -0,0 +1,113 @@ +""" +Tests for cleverhans.confidence_report +""" + +import numpy as np +import tensorflow as tf + +from cleverhans.attacks import Noise +from cleverhans.attack_bundling import AttackConfig +from cleverhans.attack_bundling import bundle_attacks +from cleverhans.attack_bundling import Misclassify +from cleverhans.confidence_report import ConfidenceReport +from cleverhans.confidence_report import ConfidenceReportEntry +from cleverhans.confidence_report import make_confidence_report_bundled +from cleverhans.devtools.mocks import SimpleDataset +from cleverhans.picklable_model import MLP, Linear +from cleverhans import serial + + +def test_confidence_report(): + """ + Test that we can make a confidence report, put an entry in it, and get + that entry back out + """ + report = ConfidenceReport() + entry = ConfidenceReportEntry( + correctness=np.array([True, False]), confidence=np.array([0.9, 0.1]) + ) + report["clean"] = entry + assert report["clean"] is entry + + +def test_make_confidence_report_bundled(): + """ + A very simple test that just makes sure make_confidence_report_bundled can run without crashing + """ + + sess = tf.Session() + try: + nb_classes = 3 + nb_features = 2 + batch_size = 5 + nb_test_examples = batch_size * 2 + layer = Linear(num_hid=nb_classes) + model = MLP(layers=[layer], input_shape=(None, nb_features)) + dataset = SimpleDataset(test_end=nb_test_examples, nb_classes=nb_classes) + model.dataset_factory = dataset.get_factory() + filepath = ".test_model.joblib" + with sess.as_default(): + sess.run(tf.global_variables_initializer()) + serial.save(filepath, model) + + def recipe( + sess, + model, + x, + y, + nb_classes, + eps, + clip_min, + clip_max, + eps_iter, + nb_iter, + report_path, + eps_iter_small, + batch_size, + ): + """ + Mock recipe that just runs the Noise attack so the test runs fast + """ + attack_configs = [AttackConfig(Noise(model, sess), {"eps": eps})] + new_work_goal = {config: 1 for config in attack_configs} + goals = [Misclassify(new_work_goal=new_work_goal)] + bundle_attacks( + sess, + model, + x, + y, + attack_configs, + goals, + report_path, + attack_batch_size=batch_size, + eval_batch_size=batch_size, + ) + + make_confidence_report_bundled( + filepath, + test_end=nb_test_examples, + recipe=recipe, + base_eps=0.1, + base_eps_iter=0.01, + batch_size=batch_size, + ) + finally: + sess.close() + + +def test_save_load_confidence_report(): + """ + Test that a confidence report can be loaded and saved. + """ + report = ConfidenceReport() + num_examples = 2 + clean_correctness = np.zeros((num_examples,), dtype=np.bool) + clean_confidence = np.zeros((num_examples,), dtype=np.float32) + adv_correctness = clean_correctness.copy() + adv_confidence = clean_confidence.copy() + report["clean"] = ConfidenceReportEntry(clean_correctness, clean_confidence) + report["adv"] = ConfidenceReportEntry(adv_correctness, adv_confidence) + report.completed = True + filepath = ".test_confidence_report.joblib" + serial.save(filepath, report) + report = serial.load(filepath) diff --git a/cleverhans_v3.1.0/tests_tf/test_dataset.py b/cleverhans_v3.1.0/tests_tf/test_dataset.py new file mode 100644 index 000000000..8f4d7650f --- /dev/null +++ b/cleverhans_v3.1.0/tests_tf/test_dataset.py @@ -0,0 +1,23 @@ +"""Tests for cleverhans.dataset""" +from cleverhans.dataset import Dataset +from cleverhans.devtools.checks import CleverHansTest + + +class LightweightDataset(Dataset): + """ + A dataset that does not actually load any data so it is cheap to run + in tests. + """ + + +class TestDataset(CleverHansTest): + """ + Tests for the Dataset class + """ + + def test_factory(self): + """test_factory: Test that dataset->factory->dataset preserves type""" + d1 = LightweightDataset() + factory = d1.get_factory() + d2 = factory() + self.assertTrue(type(d1) is type(d2)) diff --git a/cleverhans_v3.1.0/tests_tf/test_defenses.py b/cleverhans_v3.1.0/tests_tf/test_defenses.py new file mode 100644 index 000000000..8cea59337 --- /dev/null +++ b/cleverhans_v3.1.0/tests_tf/test_defenses.py @@ -0,0 +1,107 @@ +# pylint: disable=missing-docstring +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import unittest + +import numpy as np +import tensorflow as tf + +from cleverhans.attacks import FastGradientMethod +from cleverhans.loss import CrossEntropy, MixUp, FeaturePairing +from cleverhans.devtools.checks import CleverHansTest +from cleverhans.model import Model + + +class SimpleModel(Model): + """ + A very simple neural network + """ + + def __init__(self, scope="simple", nb_classes=2, **kwargs): + del kwargs + Model.__init__(self, scope, nb_classes, locals()) + + def fprop(self, x, **kwargs): + del kwargs + with tf.variable_scope(self.scope, reuse=tf.AUTO_REUSE): + w1 = tf.constant([[1.5, 0.3], [-2, 0.3]], dtype=tf.as_dtype(x.dtype)) + w2 = tf.constant([[-2.4, 1.2], [0.5, -2.3]], dtype=tf.as_dtype(x.dtype)) + h1 = tf.nn.sigmoid(tf.matmul(x, w1)) + res = tf.matmul(h1, w2) + return { + self.O_FEATURES: [h1, res], + self.O_LOGITS: res, + self.O_PROBS: tf.nn.softmax(res), + } + + +class TestDefenses(CleverHansTest): + def setUp(self): + super(TestDefenses, self).setUp() + self.model = SimpleModel() + self.vx = np.array(((1, -1), (-1, 1)), "f") + self.vy = np.array(((1, 0), (0, 1)), "f") + self.x = tf.placeholder(tf.float32, [None, 2], "x") + self.y = tf.placeholder(tf.float32, [None, 2], "y") + + def test_xe(self): + loss = CrossEntropy(self.model, smoothing=0.0) + l = loss.fprop(self.x, self.y) + with tf.Session() as sess: + vl1 = sess.run(l, feed_dict={self.x: self.vx, self.y: self.vy}) + vl2 = sess.run(l, feed_dict={self.x: self.vx, self.y: self.vy}) + self.assertClose(vl1, sum([2.210599660, 1.53666997]) / 2.0, atol=1e-6) + self.assertClose(vl2, sum([2.210599660, 1.53666997]) / 2.0, atol=1e-6) + + def test_xe_smoothing(self): + loss = CrossEntropy(self.model, smoothing=0.1) + l = loss.fprop(self.x, self.y) + with tf.Session() as sess: + vl1 = sess.run(l, feed_dict={self.x: self.vx, self.y: self.vy}) + vl2 = sess.run(l, feed_dict={self.x: self.vx, self.y: self.vy}) + self.assertClose(vl1, sum([2.10587597, 1.47194624]) / 2.0, atol=1e-6) + self.assertClose(vl2, sum([2.10587597, 1.47194624]) / 2.0, atol=1e-6) + + def test_mixup(self): + def eval_loss(l, count=1000): + with tf.Session() as sess: + vl = np.zeros(2, "f") + for _ in range(count): + vl += sess.run(l, feed_dict={self.x: self.vx, self.y: self.vy}) + return vl / count + + loss = MixUp(self.model, beta=1.0) + vl = eval_loss(loss.fprop(self.x, self.y)) + self.assertClose(vl, [1.23, 1.23], atol=5e-2) + + loss = MixUp(self.model, beta=0.5) + vl = eval_loss(loss.fprop(self.x, self.y)) + self.assertClose(vl, [1.40, 1.40], atol=5e-2) + + def test_feature_pairing(self): + sess = tf.Session() + fgsm = FastGradientMethod(self.model, sess=sess) + + def attack(x): + return fgsm.generate(x) + + loss = FeaturePairing(self.model, weight=0.1, attack=attack) + l = loss.fprop(self.x, self.y) + vl1 = sess.run(l, feed_dict={self.x: self.vx, self.y: self.vy}) + vl2 = sess.run(l, feed_dict={self.x: self.vx, self.y: self.vy}) + self.assertClose(vl1, sum([4.296023369, 2.963884830]) / 2.0, atol=1e-6) + self.assertClose(vl2, sum([4.296023369, 2.963884830]) / 2.0, atol=1e-6) + + loss = FeaturePairing(self.model, weight=10.0, attack=attack) + l = loss.fprop(self.x, self.y) + vl1 = sess.run(l, feed_dict={self.x: self.vx, self.y: self.vy}) + vl2 = sess.run(l, feed_dict={self.x: self.vx, self.y: self.vy}) + self.assertClose(vl1, sum([4.333082676, 3.00094414]) / 2.0, atol=1e-6) + self.assertClose(vl2, sum([4.333082676, 3.00094414]) / 2.0, atol=1e-6) + + +if __name__ == "__main__": + unittest.main() diff --git a/cleverhans_v3.1.0/tests_tf/test_evaluation.py b/cleverhans_v3.1.0/tests_tf/test_evaluation.py new file mode 100644 index 000000000..1568581c8 --- /dev/null +++ b/cleverhans_v3.1.0/tests_tf/test_evaluation.py @@ -0,0 +1,17 @@ +"""Tests for cleverhans.evaluation""" +from cleverhans.devtools.checks import CleverHansTest +from cleverhans.evaluation import _CorrectFactory +from cleverhans.model import Model + + +class TestEvaluation(CleverHansTest): + """Tests for cleverhans.evaluation""" + + def test_cache(self): + """test_cache: Test that _CorrectFactory can be cached""" + model = Model() + factory_1 = _CorrectFactory(model) + factory_2 = _CorrectFactory(model) + cache = {} + cache[factory_1] = True + self.assertTrue(factory_2 in cache) diff --git a/cleverhans_v3.1.0/tests_tf/test_mnist_blackbox.py b/cleverhans_v3.1.0/tests_tf/test_mnist_blackbox.py new file mode 100644 index 000000000..b370e001c --- /dev/null +++ b/cleverhans_v3.1.0/tests_tf/test_mnist_blackbox.py @@ -0,0 +1,57 @@ +# pylint: disable=missing-docstring +import unittest +import numpy as np + +# pylint bug on next line +from tensorflow.python.client import device_lib # pylint: disable=no-name-in-module +from cleverhans.devtools.checks import CleverHansTest + +HAS_GPU = "GPU" in {x.device_type for x in device_lib.list_local_devices()} + + +class TestMNISTBlackboxF(CleverHansTest): + def test_mnist_blackbox(self): + import tensorflow as tf + from cleverhans_tutorials import mnist_blackbox + + # Run the MNIST tutorial on a dataset of reduced size, reduced number + # of data augmentations, increased substitute holdout for faster runtime. + mnist_blackbox_args = { + "train_start": 0, + "train_end": 5000, + "test_start": 0, + "test_end": 2000, + "data_aug": 1, + "holdout": 1000, + "nb_epochs": 2, + "nb_epochs_s": 6, + } + g = tf.Graph() + with g.as_default(): + np.random.seed(42) + report = mnist_blackbox.mnist_blackbox(**mnist_blackbox_args) + + # Check accuracy values contained in the AccuracyReport object + self.assertTrue(report["bbox"] > 0.7, report["bbox"]) + self.assertTrue(report["sub"] > 0.7, report["sub"]) + self.assertTrue( + report["bbox_on_sub_adv_ex"] < 0.3, report["bbox_on_sub_adv_ex"] + ) + + g = tf.Graph() + with g.as_default(): + np.random.seed(42) + report_2 = mnist_blackbox.mnist_blackbox(**mnist_blackbox_args) + + atol_fac = 1e-2 if HAS_GPU else 1e-6 + self.assertClose(report["bbox"], report_2["bbox"], atol=atol_fac * 1) + self.assertClose(report["sub"], report_2["sub"], atol=atol_fac * 1) + self.assertClose( + report["bbox_on_sub_adv_ex"], + report_2["bbox_on_sub_adv_ex"], + atol=atol_fac * 1, + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/cleverhans_v3.1.0/tests_tf/test_mnist_tutorial_cw.py b/cleverhans_v3.1.0/tests_tf/test_mnist_tutorial_cw.py new file mode 100644 index 000000000..1e992a336 --- /dev/null +++ b/cleverhans_v3.1.0/tests_tf/test_mnist_tutorial_cw.py @@ -0,0 +1,63 @@ +# pylint: disable=missing-docstring +import unittest +import numpy as np +from cleverhans.devtools.checks import CleverHansTest + + +class TestMNISTTutorialCW(CleverHansTest): + def test_mnist_tutorial_cw(self): + import tensorflow as tf + from cleverhans_tutorials import mnist_tutorial_cw + + # Run the MNIST tutorial on a dataset of reduced size + # and disable visualization. + cw_tutorial_args = { + "train_start": 0, + "train_end": 10000, + "test_start": 0, + "test_end": 1666, + "viz_enabled": False, + } + g = tf.Graph() + with g.as_default(): + np.random.seed(42) + report = mnist_tutorial_cw.mnist_tutorial_cw(**cw_tutorial_args) + + # Check accuracy values contained in the AccuracyReport object + self.assertGreater(report.clean_train_clean_eval, 0.85) + self.assertEqual(report.clean_train_adv_eval, 0.00) + + # There is no adversarial training in the CW tutorial + self.assertEqual(report.adv_train_clean_eval, 0.0) + self.assertEqual(report.adv_train_adv_eval, 0.0) + + g = tf.Graph() + with g.as_default(): + np.random.seed(42) + report_2 = mnist_tutorial_cw.mnist_tutorial_cw(**cw_tutorial_args) + + atol_fac = 1e-6 + self.assertClose( + report.train_clean_train_clean_eval, + report_2.train_clean_train_clean_eval, + atol=atol_fac * 1, + ) + self.assertClose( + report.train_clean_train_adv_eval, + report_2.train_clean_train_adv_eval, + atol=atol_fac * 1, + ) + self.assertClose( + report.train_adv_train_clean_eval, + report_2.train_adv_train_clean_eval, + atol=atol_fac * 1, + ) + self.assertClose( + report.train_adv_train_adv_eval, + report_2.train_adv_train_adv_eval, + atol=atol_fac * 1, + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/cleverhans_v3.1.0/tests_tf/test_mnist_tutorial_jsma.py b/cleverhans_v3.1.0/tests_tf/test_mnist_tutorial_jsma.py new file mode 100644 index 000000000..88530ca34 --- /dev/null +++ b/cleverhans_v3.1.0/tests_tf/test_mnist_tutorial_jsma.py @@ -0,0 +1,68 @@ +# pylint: disable=missing-docstring +import unittest +import numpy as np +from cleverhans.devtools.checks import CleverHansTest + + +class TestMNISTTutorialJSMA(CleverHansTest): + def test_mnist_tutorial_jsma(self): + + import tensorflow as tf + from cleverhans_tutorials import mnist_tutorial_jsma + + # Run the MNIST tutorial on a dataset of reduced size + # and disable visualization. + jsma_tutorial_args = { + "train_start": 0, + "train_end": 1000, + "test_start": 0, + "test_end": 1666, + "viz_enabled": False, + "source_samples": 1, + "nb_epochs": 2, + } + g = tf.Graph() + with g.as_default(): + np.random.seed(42) + report = mnist_tutorial_jsma.mnist_tutorial_jsma(**jsma_tutorial_args) + + # Check accuracy values contained in the AccuracyReport object + # We already have JSMA tests in test_attacks.py, so just sanity + # check the values here. + self.assertTrue(report.clean_train_clean_eval > 0.65) + self.assertTrue(report.clean_train_adv_eval < 0.25) + + # There is no adversarial training in the JSMA tutorial + self.assertTrue(report.adv_train_clean_eval == 0.0) + self.assertTrue(report.adv_train_adv_eval == 0.0) + + g = tf.Graph() + with g.as_default(): + np.random.seed(42) + report_2 = mnist_tutorial_jsma.mnist_tutorial_jsma(**jsma_tutorial_args) + + atol_fac = 1e-6 + self.assertClose( + report.train_clean_train_clean_eval, + report_2.train_clean_train_clean_eval, + atol=atol_fac * 1, + ) + self.assertClose( + report.train_clean_train_adv_eval, + report_2.train_clean_train_adv_eval, + atol=atol_fac * 1, + ) + self.assertClose( + report.train_adv_train_clean_eval, + report_2.train_adv_train_clean_eval, + atol=atol_fac * 1, + ) + self.assertClose( + report.train_adv_train_adv_eval, + report_2.train_adv_train_adv_eval, + atol=atol_fac * 1, + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/cleverhans_v3.1.0/tests_tf/test_mnist_tutorial_keras.py b/cleverhans_v3.1.0/tests_tf/test_mnist_tutorial_keras.py new file mode 100644 index 000000000..42b27f145 --- /dev/null +++ b/cleverhans_v3.1.0/tests_tf/test_mnist_tutorial_keras.py @@ -0,0 +1,67 @@ +# pylint: disable=missing-docstring +import unittest +import numpy as np + +# pylint bug on next line +from tensorflow.python.client import device_lib # pylint: disable=no-name-in-module +from cleverhans.devtools.checks import CleverHansTest + +HAS_GPU = "GPU" in {x.device_type for x in device_lib.list_local_devices()} + + +class TestMNISTTutorialKeras(CleverHansTest): + def test_mnist_tutorial_keras(self): + + import tensorflow as tf + from cleverhans_tutorials import mnist_tutorial_keras + + # Run the MNIST tutorial on a dataset of reduced size + test_dataset_indices = { + "train_start": 0, + "train_end": 5000, + "test_start": 0, + "test_end": 333, + "nb_epochs": 2, + "testing": True, + } + g = tf.Graph() + with g.as_default(): + np.random.seed(42) + report = mnist_tutorial_keras.mnist_tutorial(**test_dataset_indices) + + # Check accuracy values contained in the AccuracyReport object + self.assertTrue(report.train_clean_train_clean_eval > 0.90) + self.assertTrue(report.train_clean_train_adv_eval < 0.05) + self.assertTrue(report.train_adv_train_clean_eval > 0.90) + self.assertTrue(report.train_adv_train_adv_eval > 0.30) + + atol_fac = 5e-2 if HAS_GPU else 1e-6 + g = tf.Graph() + with g.as_default(): + np.random.seed(42) + report_2 = mnist_tutorial_keras.mnist_tutorial(**test_dataset_indices) + + self.assertClose( + report.train_clean_train_clean_eval, + report_2.train_clean_train_clean_eval, + atol=atol_fac * 1, + ) + self.assertClose( + report.train_clean_train_adv_eval, + report_2.train_clean_train_adv_eval, + atol=atol_fac * 1, + ) + self.assertClose( + report.train_adv_train_clean_eval, + report_2.train_adv_train_clean_eval, + atol=atol_fac * 1, + ) + self.assertClose( + report.train_adv_train_adv_eval, + report_2.train_adv_train_adv_eval, + atol=atol_fac * 1, + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/cleverhans_v3.1.0/tests_tf/test_mnist_tutorial_keras_tf.py b/cleverhans_v3.1.0/tests_tf/test_mnist_tutorial_keras_tf.py new file mode 100644 index 000000000..86657c1e7 --- /dev/null +++ b/cleverhans_v3.1.0/tests_tf/test_mnist_tutorial_keras_tf.py @@ -0,0 +1,70 @@ +# pylint: disable=missing-docstring +import unittest +import numpy as np + +# pylint bug on next line +from tensorflow.python.client import device_lib # pylint: disable=no-name-in-module +from cleverhans.devtools.checks import CleverHansTest + +HAS_GPU = "GPU" in {x.device_type for x in device_lib.list_local_devices()} + + +class TestMNISTTutorialKerasTF(CleverHansTest): + def test_mnist_tutorial_keras_tf(self): + + import tensorflow as tf + from cleverhans_tutorials import mnist_tutorial_keras_tf + + # Run the MNIST tutorial on a dataset of reduced size + test_dataset_indices = { + "train_start": 0, + "train_end": 5000, + "test_start": 0, + "test_end": 333, + "nb_epochs": 3, + "train_dir": "/tmp", + "filename": "mnist.ckpt", + "load_model": False, + "testing": True, + } + g = tf.Graph() + with g.as_default(): + np.random.seed(42) + report = mnist_tutorial_keras_tf.mnist_tutorial(**test_dataset_indices) + + # Check accuracy values contained in the AccuracyReport object + self.assertTrue(report.train_clean_train_clean_eval > 0.90) + self.assertTrue(report.train_clean_train_adv_eval < 0.05) + self.assertTrue(report.train_adv_train_clean_eval > 0.90) + self.assertTrue(report.train_adv_train_adv_eval > 0.30) + + atol_fac = 2e-2 if HAS_GPU else 1e-6 + g = tf.Graph() + with g.as_default(): + np.random.seed(42) + report_2 = mnist_tutorial_keras_tf.mnist_tutorial(**test_dataset_indices) + + self.assertClose( + report.train_clean_train_clean_eval, + report_2.train_clean_train_clean_eval, + atol=atol_fac * 1, + ) + self.assertClose( + report.train_clean_train_adv_eval, + report_2.train_clean_train_adv_eval, + atol=atol_fac * 1, + ) + self.assertClose( + report.train_adv_train_clean_eval, + report_2.train_adv_train_clean_eval, + atol=atol_fac * 1, + ) + self.assertClose( + report.train_adv_train_adv_eval, + report_2.train_adv_train_adv_eval, + atol=atol_fac * 1, + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/cleverhans_v3.1.0/tests_tf/test_mnist_tutorial_tf.py b/cleverhans_v3.1.0/tests_tf/test_mnist_tutorial_tf.py new file mode 100644 index 000000000..82ad2c0b0 --- /dev/null +++ b/cleverhans_v3.1.0/tests_tf/test_mnist_tutorial_tf.py @@ -0,0 +1,71 @@ +# pylint: disable=missing-docstring +import unittest +import numpy as np + +# pylint bug on next line +from tensorflow.python.client import device_lib # pylint: disable=no-name-in-module +from cleverhans.devtools.checks import CleverHansTest + +HAS_GPU = "GPU" in {x.device_type for x in device_lib.list_local_devices()} + + +class TestMNISTTutorialTF(CleverHansTest): + def test_mnist_tutorial_tf(self): + + import tensorflow as tf + from cleverhans_tutorials import mnist_tutorial_tf + + # Run the MNIST tutorial on a dataset of reduced size + test_dataset_indices = { + "train_start": 0, + "train_end": 5000, + "test_start": 0, + "test_end": 333, + "nb_epochs": 2, + "testing": True, + } + g = tf.Graph() + with g.as_default(): + np.random.seed(42) + report = mnist_tutorial_tf.mnist_tutorial( + num_threads=1, **test_dataset_indices + ) + + # Check accuracy values contained in the AccuracyReport object + self.assertGreater(report.train_clean_train_clean_eval, 0.97) + self.assertLess(report.train_clean_train_adv_eval, 0.05) + self.assertGreater(report.train_adv_train_clean_eval, 0.93) + self.assertGreater(report.train_adv_train_adv_eval, 0.4) + + # Check that the tutorial is deterministic (seeded properly) + atol_fac = 2e-2 if HAS_GPU else 1e-6 + g = tf.Graph() + with g.as_default(): + np.random.seed(42) + report_2 = mnist_tutorial_tf.mnist_tutorial( + num_threads=1, **test_dataset_indices + ) + self.assertClose( + report.train_clean_train_clean_eval, + report_2.train_clean_train_clean_eval, + atol=atol_fac * 1, + ) + self.assertClose( + report.train_clean_train_adv_eval, + report_2.train_clean_train_adv_eval, + atol=atol_fac * 1, + ) + self.assertClose( + report.train_adv_train_clean_eval, + report_2.train_adv_train_clean_eval, + atol=atol_fac * 1, + ) + self.assertClose( + report.train_adv_train_adv_eval, + report_2.train_adv_train_adv_eval, + atol=atol_fac * 1, + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/cleverhans_v3.1.0/tests_tf/test_model.py b/cleverhans_v3.1.0/tests_tf/test_model.py new file mode 100644 index 000000000..70fd31b86 --- /dev/null +++ b/cleverhans_v3.1.0/tests_tf/test_model.py @@ -0,0 +1,73 @@ +""" +Tests for cleverhans.model +""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import unittest + +from cleverhans.model import Model, CallableModelWrapper + + +class TestModelClass(unittest.TestCase): + """ + Tests for cleverhans.model.Model + """ + + # pylint: disable=missing-docstring + + def test_get_logits(self): + # Define empty model + model = Model("model", 10, {}) + x = [] + + # Exception is thrown when `get_logits` not implemented + with self.assertRaises(Exception) as context: + model.get_logits(x) + self.assertTrue(context.exception) + + def test_get_probs(self): + # Define empty model + model = Model("model", 10, {}) + x = [] + + # Exception is thrown when `get_probs` not implemented + with self.assertRaises(Exception) as context: + model.get_probs(x) + self.assertTrue(context.exception) + + def test_fprop(self): + # Define empty model + model = Model("model", 10, {}) + x = [] + + # Exception is thrown when `fprop` not implemented + with self.assertRaises(Exception) as context: + model.fprop(x) + self.assertTrue(context.exception) + + +class TestCallableModelWrapperInitArguments(unittest.TestCase): + """ + Tests for CallableModelWrapper's init argument + """ + + def test_output_layer(self): + """ + Test that the CallableModelWrapper can be constructed without causing Exceptions + """ + + def model(**kwargs): + """Mock model""" + del kwargs + return True + + # The following two calls should not raise Exceptions + CallableModelWrapper(model, "probs") + CallableModelWrapper(model, "logits") + + +if __name__ == "__main__": + unittest.main() diff --git a/cleverhans_v3.1.0/tests_tf/test_picklable_model.py b/cleverhans_v3.1.0/tests_tf/test_picklable_model.py new file mode 100644 index 000000000..24b915bcd --- /dev/null +++ b/cleverhans_v3.1.0/tests_tf/test_picklable_model.py @@ -0,0 +1,111 @@ +"""Tests for cleverhans.picklable_model""" +import numpy as np +import tensorflow as tf + +from cleverhans.devtools.checks import CleverHansTest +from cleverhans.picklable_model import Dropout +from cleverhans.picklable_model import MLP +from cleverhans.picklable_model import PerImageStandardize + + +class TestPerImageStandardize(CleverHansTest): + """ + Tests for the PerImageStandardize class. + """ + + def setUp(self): + """ + Set up session and build model graph + """ + super(TestPerImageStandardize, self).setUp() + + self.input_shape = (128, 32, 32, 3) + self.sess = tf.Session() + self.model = MLP( + input_shape=self.input_shape, layers=[PerImageStandardize(name="output")] + ) + + self.x = tf.placeholder(shape=self.input_shape, dtype=tf.float32) + self.y = self.model.get_layer(self.x, "output") + + self.y_true = tf.map_fn(tf.image.per_image_standardization, self.x) + + def run_and_check_output(self, x): + """ + Make sure y and y_true evaluate to the same value + """ + y, y_true = self.sess.run([self.y, self.y_true], feed_dict={self.x: x}) + self.assertClose(y, y_true) + + def test_random_inputs(self): + """ + Test on random inputs + """ + x = np.random.rand(*self.input_shape) + self.run_and_check_output(x) + + def test_ones_inputs(self): + """ + Test with input set to all ones. + """ + x = np.ones(self.input_shape) + self.run_and_check_output(x) + + +class TestDropout(CleverHansTest): + """ + Tests for the Dropout class + """ + + def test_no_drop(self): + """test_no_drop: Make sure dropout does nothing by default + (so it does not cause stochasticity at test time)""" + + model = MLP(input_shape=[1, 1], layers=[Dropout(name="output")]) + x = tf.constant([[1]], dtype=tf.float32) + y = model.get_layer(x, "output") + sess = tf.Session() + # Do multiple runs because dropout is stochastic + for _ in range(10): + y_value = sess.run(y) + self.assertClose(y_value, 1.0) + + def test_drop(self): + """test_drop: Make sure dropout is activated successfully""" + + # We would like to configure the test to deterministically drop, + # so that the test does not need to use multiple runs. + # However, tf.nn.dropout divides by include_prob, so zero or + # infinitesimal include_prob causes NaNs. + # 1e-8 does not cause NaNs and shouldn't be a significant source + # of test flakiness relative to dependency downloads failing, etc. + model = MLP( + input_shape=[1, 1], layers=[Dropout(name="output", include_prob=1e-8)] + ) + x = tf.constant([[1]], dtype=tf.float32) + y = model.get_layer(x, "output", dropout=True) + sess = tf.Session() + y_value = sess.run(y) + # Subject to very rare random failure because include_prob is not exact 0 + self.assertClose(y_value, 0.0) + + def test_override(self): + """test_override: Make sure dropout_dict changes dropout probabilities + successfully.""" + + # We would like to configure the test to deterministically drop, + # so that the test does not need to use multiple runs. + # However, tf.nn.dropout divides by include_prob, so zero or + # infinitesimal include_prob causes NaNs. + # For this test, random failure to drop will not cause the test to fail. + # The stochastic version should not even run if everything is working + # right. + model = MLP( + input_shape=[1, 1], layers=[Dropout(name="output", include_prob=1e-8)] + ) + x = tf.constant([[1]], dtype=tf.float32) + dropout_dict = {"output": 1.0} + y = model.get_layer(x, "output", dropout=True, dropout_dict=dropout_dict) + sess = tf.Session() + y_value = sess.run(y) + self.assertClose(y_value, 1.0) diff --git a/cleverhans_v3.1.0/tests_tf/test_projected_gradient_descent.py b/cleverhans_v3.1.0/tests_tf/test_projected_gradient_descent.py new file mode 100644 index 000000000..08a869dfe --- /dev/null +++ b/cleverhans_v3.1.0/tests_tf/test_projected_gradient_descent.py @@ -0,0 +1,48 @@ +"""Tests for the ProjectGradientDescent attack +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from nose.tools import assert_raises +import tensorflow as tf + +from cleverhans.attacks import ProjectedGradientDescent +from cleverhans.model import Model + + +def test_no_logits(): + """test_no_logits: Check that a model without logits causes an error""" + batch_size = 2 + nb_classes = 3 + + class NoLogitsModel(Model): + """ + A model that neither defines logits nor makes it possible to find logits + by inspecting the inputs to a softmax op. + """ + + def fprop(self, x, **kwargs): + return {"probs": tf.ones((batch_size, nb_classes)) / nb_classes} + + model = NoLogitsModel() + sess = tf.Session() + attack = ProjectedGradientDescent(model, sess=sess) + x = tf.ones((batch_size, 3)) + assert_raises(NotImplementedError, attack.generate, x) + + +def test_rejects_callable(): + """test_rejects_callable: Check that callables are not accepted as models""" + + def model(x): + """Mock model""" + return x + + sess = tf.Session() + assert_raises(TypeError, ProjectedGradientDescent, model, sess) + + +if __name__ == "__main__": + test_rejects_callable() diff --git a/cleverhans_v3.1.0/tests_tf/test_serial.py b/cleverhans_v3.1.0/tests_tf/test_serial.py new file mode 100644 index 000000000..977e4bbc0 --- /dev/null +++ b/cleverhans_v3.1.0/tests_tf/test_serial.py @@ -0,0 +1,29 @@ +"""Tests for cleverhans.serial""" +import numpy as np +import tensorflow as tf + +from cleverhans.devtools.checks import CleverHansTest +from cleverhans.serial import PicklableVariable +from cleverhans.serial import load +from cleverhans.serial import save + + +class TestSerial(CleverHansTest): + """ + Tests for cleverhans.serial + """ + + def test_save_and_load_var(self): + """test_save_and_load_var: Test that we can save and load a + PicklableVariable with joblib + """ + sess = tf.Session() + with sess.as_default(): + x = np.ones(1) + xv = PicklableVariable(x) + xv.var.initializer.run() + save("/tmp/var.joblib", xv) + sess.run(tf.assign(xv.var, np.ones(1) * 2)) + new_xv = load("/tmp/var.joblib") + self.assertClose(sess.run(xv.var), np.ones(1) * 2) + self.assertClose(sess.run(new_xv.var), np.ones(1)) diff --git a/cleverhans_v3.1.0/tests_tf/test_utils.py b/cleverhans_v3.1.0/tests_tf/test_utils.py new file mode 100644 index 000000000..32c6be39a --- /dev/null +++ b/cleverhans_v3.1.0/tests_tf/test_utils.py @@ -0,0 +1,100 @@ +# pylint: disable=missing-docstring +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import unittest +import numpy as np + +from cleverhans import utils +from cleverhans.utils_keras import cnn_model +from cleverhans.utils_keras import KerasModelWrapper + + +class TestUtils(unittest.TestCase): + def test_to_categorical_with_nb_classes_arg(self): + vec = np.asarray([0]) + cat = np.asarray([[1, 0, 0]]) + self.assertTrue(np.all(utils.to_categorical(vec, 3) == cat)) + + def test_random_targets_vector(self): + # Test utils.random_targets with a vector of labels as the input + gt_labels = np.asarray([0, 1, 2, 3]) + rt = utils.random_targets(gt_labels, 5) + + # Make sure random_targets returns a one-hot encoded labels + self.assertTrue(len(rt.shape) == 2) + rt_labels = np.argmax(rt, axis=1) + + # Make sure all labels are different from the correct labels + self.assertTrue(np.all(rt_labels != gt_labels)) + + def test_random_targets_one_hot(self): + # Test utils.random_targets with one-hot encoded labels as the input + gt = np.asarray( + [[0, 0, 1, 0, 0], [1, 0, 0, 0, 0], [0, 0, 0, 1, 0], [1, 0, 0, 0, 0]] + ) + gt_labels = np.argmax(gt, axis=1) + rt = utils.random_targets(gt, 5) + + # Make sure random_targets returns a one-hot encoded labels + self.assertTrue(len(rt.shape) == 2) + rt_labels = np.argmax(rt, axis=1) + + # Make sure all labels are different from the correct labels + self.assertTrue(np.all(rt_labels != gt_labels)) + + def test_random_targets_one_hot_single_label(self): + # Test utils.random_targets with a single one-hot encoded label + gt = np.asarray([0, 0, 1, 0, 0]) + gt = gt.reshape((1, 5)) + gt_labels = np.argmax(gt, axis=1) + rt = utils.random_targets(gt, 5) + + # Make sure random_targets returns a one-hot encoded labels + self.assertTrue(len(rt.shape) == 2) + rt_labels = np.argmax(rt, axis=1) + + # Make sure all labels are different from the correct labels + self.assertTrue(np.all(rt_labels != gt_labels)) + + def test_other_classes_neg_class_ind(self): + with self.assertRaises(Exception) as context: + utils.other_classes(10, -1) + self.assertTrue(context.exception) + + def test_other_classes_invalid_class_ind(self): + with self.assertRaises(Exception) as context: + utils.other_classes(5, 8) + self.assertTrue(context.exception) + + def test_other_classes_return_val(self): + res = utils.other_classes(5, 2) + res_expected = [0, 1, 3, 4] + self.assertTrue(res == res_expected) + + def test_get_logits_over_interval(self): + import tensorflow as tf + + model = cnn_model() + wrap = KerasModelWrapper(model) + fgsm_params = {"eps": 0.5} + img = np.ones(shape=(28, 28, 1)) + num_points = 21 + with tf.Session() as sess: + tf.global_variables_initializer().run() + logits = utils.get_logits_over_interval( + sess, + wrap, + img, + fgsm_params, + min_epsilon=-10, + max_epsilon=10, + num_points=num_points, + ) + self.assertEqual(logits.shape[0], num_points) + + +if __name__ == "__main__": + unittest.main() diff --git a/cleverhans_v3.1.0/tests_tf/test_utils_keras.py b/cleverhans_v3.1.0/tests_tf/test_utils_keras.py new file mode 100644 index 000000000..222d720d9 --- /dev/null +++ b/cleverhans_v3.1.0/tests_tf/test_utils_keras.py @@ -0,0 +1,102 @@ +# pylint: disable=missing-docstring +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import unittest +import numpy as np + +# Weird imports / assignment because the normal import syntax doesn't work for tf.keras in tf 1.8 +from tensorflow import keras + +# pylint:disable=wrong-import-position +Sequential = keras.models.Sequential +Dense = keras.layers.Dense +Activation = keras.layers.Activation + +from cleverhans.utils_keras import KerasModelWrapper + + +class TestKerasModelWrapper(unittest.TestCase): + def setUp(self): + import tensorflow as tf + + def dummy_model(): + input_shape = (100,) + return Sequential( + [ + Dense(20, name="l1", input_shape=input_shape), + Dense(10, name="l2"), + Activation("softmax", name="softmax"), + ] + ) + + self.sess = tf.Session() + self.sess.as_default() + self.model = dummy_model() + + def test_softmax_layer_name_is_softmax(self): + model = KerasModelWrapper(self.model) + softmax_name = model._get_softmax_name() + self.assertEqual(softmax_name, "softmax") + + def test_logit_layer_name_is_logits(self): + model = KerasModelWrapper(self.model) + logits_name = model._get_logits_name() + self.assertEqual(logits_name, "l2") + + def test_get_logits(self): + import tensorflow as tf + + model = KerasModelWrapper(self.model) + x = tf.placeholder(tf.float32, shape=(None, 100)) + preds = model.get_probs(x) + logits = model.get_logits(x) + + x_val = np.random.rand(2, 100) + tf.global_variables_initializer().run(session=self.sess) + p_val, logits = self.sess.run([preds, logits], feed_dict={x: x_val}) + p_gt = np.exp(logits) / np.sum(np.exp(logits), axis=1, keepdims=True) + self.assertTrue(np.allclose(p_val, p_gt, atol=1e-6)) + + def test_get_probs(self): + import tensorflow as tf + + model = KerasModelWrapper(self.model) + x = tf.placeholder(tf.float32, shape=(None, 100)) + preds = model.get_probs(x) + + x_val = np.random.rand(2, 100) + tf.global_variables_initializer().run(session=self.sess) + p_val = self.sess.run(preds, feed_dict={x: x_val}) + self.assertTrue(np.allclose(np.sum(p_val, axis=1), 1, atol=1e-6)) + self.assertTrue(np.all(p_val >= 0)) + self.assertTrue(np.all(p_val <= 1)) + + def test_get_layer_names(self): + model = KerasModelWrapper(self.model) + layer_names = model.get_layer_names() + self.assertEqual(layer_names, ["l1", "l2", "softmax"]) + + def test_fprop(self): + import tensorflow as tf + + model = KerasModelWrapper(self.model) + x = tf.placeholder(tf.float32, shape=(None, 100)) + out_dict = model.fprop(x) + + self.assertEqual(set(out_dict.keys()), set(["l1", "l2", "softmax"])) + # Test the dimension of the hidden represetation + self.assertEqual(int(out_dict["l1"].shape[1]), 20) + self.assertEqual(int(out_dict["l2"].shape[1]), 10) + + # Test the caching + x2 = tf.placeholder(tf.float32, shape=(None, 100)) + out_dict2 = model.fprop(x2) + self.assertEqual(set(out_dict2.keys()), set(["l1", "l2", "softmax"])) + self.assertEqual(int(out_dict2["l1"].shape[1]), 20) + + +if __name__ == "__main__": + unittest.main() diff --git a/cleverhans_v3.1.0/tests_tf/test_utils_tf.py b/cleverhans_v3.1.0/tests_tf/test_utils_tf.py new file mode 100644 index 000000000..60c66e819 --- /dev/null +++ b/cleverhans_v3.1.0/tests_tf/test_utils_tf.py @@ -0,0 +1,165 @@ +"""Tests for cleverhans.utils_tf""" +# pylint: disable=missing-docstring +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import numpy as np +import tensorflow as tf + +from cleverhans import utils_tf +from cleverhans.devtools.checks import CleverHansTest + + +def numpy_kl_with_logits(p_logits, q_logits): + def numpy_softmax(logits): + logits -= np.max(logits, axis=1, keepdims=True) + exp_logits = np.exp(logits) + return exp_logits / np.sum(exp_logits, axis=1, keepdims=True) + + p = numpy_softmax(p_logits) + log_p = p_logits - np.log(np.sum(np.exp(p_logits), axis=1, keepdims=True)) + log_q = q_logits - np.log(np.sum(np.exp(q_logits), axis=1, keepdims=True)) + return (p * (log_p - log_q)).sum(axis=1).mean() + + +class TestUtilsTF(CleverHansTest): + """Test class for utils_tf""" + + def setUp(self): + super(TestUtilsTF, self).setUp() + self.sess = tf.Session() + + def test_clip_by_value_numpy_dtype(self): + # Test that it's possible to use clip_by_value while mixing numpy and tf + clip_min = np.zeros((1,)) + clip_max = tf.ones((1,)) + x = tf.ones((1,)) + # The point of this test is just to make sure the casting logic doesn't raise an exception + utils_tf.clip_by_value(x, clip_min, clip_max) + + def test_l2_batch_normalize(self): + x = tf.random_normal((100, 1000)) + x_norm = self.sess.run(utils_tf.l2_batch_normalize(x)) + self.assertClose(np.sum(x_norm ** 2, axis=1), 1, atol=1e-6) + + def test_kl_with_logits(self): + p_logits = tf.placeholder(tf.float32, shape=(100, 20)) + q_logits = tf.placeholder(tf.float32, shape=(100, 20)) + p_logits_np = np.random.normal(0, 10, size=(100, 20)) + q_logits_np = np.random.normal(0, 10, size=(100, 20)) + kl_div_tf = self.sess.run( + utils_tf.kl_with_logits(p_logits, q_logits), + feed_dict={p_logits: p_logits_np, q_logits: q_logits_np}, + ) + kl_div_ref = numpy_kl_with_logits(p_logits_np, q_logits_np) + self.assertClose(kl_div_ref, kl_div_tf) + + def test_clip_eta_norm_0(self): + """test_clip_eta_norm_0: Test that `clip_eta` still works when the + norm of `eta` is zero. This used to cause a divide by zero for ord + 1 and ord 2.""" + eta = tf.zeros((5, 3)) + self.assertTrue(eta.dtype == tf.float32, eta.dtype) + eps = 0.25 + for ord_arg in [np.inf, 1, 2]: + try: + clipped = utils_tf.clip_eta(eta, ord_arg, eps) + except NotImplementedError: + # Don't raise SkipTest, it skips the rest of the for loop + continue + clipped = self.sess.run(clipped) + self.assertTrue(not np.any(np.isinf(clipped))) + self.assertTrue(not np.any(np.isnan(clipped)), (ord_arg, clipped)) + + def test_clip_eta_goldilocks(self): + """test_clip_eta_goldilocks: Test that the clipping handles perturbations + that are too small, just right, and too big correctly""" + eta = tf.constant([[2.0], [3.0], [4.0]]) + self.assertTrue(eta.dtype == tf.float32, eta.dtype) + eps = 3.0 + for ord_arg in [np.inf, 1, 2]: + for sign in [-1.0, 1.0]: + try: + clipped = utils_tf.clip_eta(eta * sign, ord_arg, eps) + except NotImplementedError: + # Don't raise SkipTest, it skips the rest of the for loop + continue + clipped_value = self.sess.run(clipped) + gold = sign * np.array([[2.0], [3.0], [3.0]]) + self.assertClose(clipped_value, gold) + (grad,) = tf.gradients(clipped, eta) + grad_value = self.sess.run(grad) + # Note: the second 1. is debatable (the left-sided derivative + # and the right-sided derivative do not match, so formally + # the derivative is not defined). This test makes sure that + # we at least handle this oddity consistently across all the + # argument values we test + gold = sign * np.array([[1.0], [1.0], [0.0]]) + self.assertClose(grad_value, gold) + + def test_zero_out_clipped_grads(self): + """ + test_zero_out_clipped_grads: Test that gradient gets zeroed out at positions + where no progress can be made due to clipping. + """ + + clip_min = -1 + clip_max = 1 + eta = tf.constant([[0.0], [-1.0], [1], [0.5], [-1], [1], [-0.9], [0.9]]) + grad = tf.constant([[1.0], [-1.0], [1.0], [1.0], [1.0], [-1.0], [-1.0], [1.0]]) + + grad2 = self.sess.run( + utils_tf.zero_out_clipped_grads(grad, eta, clip_min, clip_max) + ) + + expected = np.asarray( + [[1.0], [0.0], [0.0], [1.0], [1.0], [-1.0], [-1.0], [1.0]] + ) + self.assertClose(grad2, expected) + + def test_random_lp_vector_linf(self): + """ + test_random_lp_sample_linf: Test that `random_lp_vector` returns + random samples in the l-inf ball. + """ + + eps = 0.5 + d = 10 + + r = self.sess.run(utils_tf.random_lp_vector((1000, d), np.infty, eps)) + + # test that some values are close to the boundaries + self.assertLessEqual(np.max(r), eps) + self.assertGreaterEqual(np.max(r), 0.95 * eps) + self.assertGreaterEqual(np.min(r), -eps) + self.assertLessEqual(np.min(r), -0.95 * eps) + + # test that the mean value of each feature is close to zero + means = np.mean(r, axis=0) + self.assertClose(means, np.zeros(d), atol=0.05) + + def test_random_lp_srandom_lp_vector_l1_l2(self): + """ + test_random_lp_vector_l1_l2: Test that `random_lp_vector` returns + random samples in an l1 or l2 ball. + """ + + eps = 0.5 + d = 10 + + for ord in [1, 2]: + r = self.sess.run(utils_tf.random_lp_vector((1000, d), ord, eps)) + + norms = np.linalg.norm(r, axis=-1, ord=ord) + + # test that some values are close to the boundaries + self.assertLessEqual(np.max(norms), eps) + self.assertGreaterEqual(np.max(norms), 0.95 * eps) + + # The expected norm is eps * Exp[U[0,1]^(1/d)] where U is a standard + # uniform random variable and d is the dimension. The second term is + # equal to the expected value of a Beta(d, 1) variable which is d/(d+1). + expected_mean_norm = eps * (d / (d + 1.0)) + self.assertClose(np.mean(norms), expected_mean_norm, atol=0.02) diff --git a/defenses/README.md b/defenses/README.md index 0246acca8..4758a8806 100644 --- a/defenses/README.md +++ b/defenses/README.md @@ -1,4 +1,7 @@ -# Defenses [Future] - -In the future, the content of `future/` will be moved to this folder. +# Defenses +This folder contains implementations of defenses (as standalone scripts) in one +of the three frameworks (JAX, PyTorch, TF2). Each defense should be implemented +as a script that helps reproduce results reported in the paper introducing the +defense. The goal is for these scripts to be authoritative ways to reproduce +the defense. diff --git a/defenses/future/README.md b/defenses/future/README.md deleted file mode 100644 index 67b0a5a4e..000000000 --- a/defenses/future/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# Defenses [Future] - -In the future, this folder will be at the root of this repository `//defenses`. - -This folder contains implementations of defenses (as standalone scripts) in one -of the three frameworks (JAX, PyTorch, TF2). Each defense should be implemented -as a script that helps reproduce results reported in the paper introducing the -defense. The goal is for these scripts to be authoritative ways to reproduce -the defense. diff --git a/defenses/future/jax/README.md b/defenses/jax/README.md similarity index 100% rename from defenses/future/jax/README.md rename to defenses/jax/README.md diff --git a/defenses/future/tf2/README.md b/defenses/tf2/README.md similarity index 100% rename from defenses/future/tf2/README.md rename to defenses/tf2/README.md diff --git a/defenses/future/torch/README.md b/defenses/torch/README.md similarity index 100% rename from defenses/future/torch/README.md rename to defenses/torch/README.md diff --git a/docsource/conf.py b/docsource/conf.py index 8c11cc29e..0fca84f4b 100644 --- a/docsource/conf.py +++ b/docsource/conf.py @@ -20,7 +20,7 @@ import os import sys -sys.path.insert(0, os.path.abspath('..')) +sys.path.insert(0, os.path.abspath("..")) # -- General configuration ------------------------------------------------ @@ -32,27 +32,28 @@ # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.mathjax', - 'sphinx.ext.viewcode', - 'sphinx.ext.githubpages'] + "sphinx.ext.autodoc", + "sphinx.ext.mathjax", + "sphinx.ext.viewcode", + "sphinx.ext.githubpages", +] # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # # source_suffix = ['.rst', '.md'] -source_suffix = '.md' +source_suffix = ".md" # The master toctree document. -master_doc = 'index' +master_doc = "index" # General information about the project. -project = 'CleverHans' +project = "CleverHans" -author = 'Ian Goodfellow, Nicolas Papernot, Ryan Sheatsley' +author = "Ian Goodfellow, Nicolas Papernot, Ryan Sheatsley" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -73,10 +74,10 @@ # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This patterns also effect to html_static_path and html_extra_path -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +pygments_style = "sphinx" # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = False @@ -86,7 +87,7 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # -html_theme = 'alabaster' +html_theme = "alabaster" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the @@ -97,7 +98,7 @@ # Add any paths that contain custom static files (such as style sheets) here, # 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'] +html_static_path = ["_static"] # Custom sidebar templates must be a dictionary that maps document names # to template names. @@ -105,19 +106,19 @@ # This is required for the alabaster theme # refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars html_sidebars = { - '**': [ - 'about.html', - 'navigation.html', - 'relations.html', # needs 'show_related': True theme option to display - 'searchbox.html', - 'donate.html', + "**": [ + "about.html", + "navigation.html", + "relations.html", # needs 'show_related': True theme option to display + "searchbox.html", + "donate.html", ] } # -- Options for HTMLHelp output ------------------------------------------ # Output file base name for HTML help builder. -htmlhelp_basename = 'CleverHansdoc' +htmlhelp_basename = "CleverHansdoc" # -- Options for LaTeX output --------------------------------------------- @@ -125,15 +126,12 @@ # The paper size ('letterpaper' or 'a4paper'). # # 'papersize': 'letterpaper', - # The font size ('10pt', '11pt' or '12pt'). # # 'pointsize': '10pt', - # Additional stuff for the LaTeX preamble. # # 'preamble': '', - # Latex figure (float) alignment # # 'figure_align': 'htbp', @@ -143,18 +141,20 @@ # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - (master_doc, 'CleverHans.tex', 'CleverHans Documentation', - 'Ian Goodfellow, Nicolas Papernot, Ryan Sheatsley', 'manual'), + ( + master_doc, + "CleverHans.tex", + "CleverHans Documentation", + "Ian Goodfellow, Nicolas Papernot, Ryan Sheatsley", + "manual", + ), ] # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). -man_pages = [ - (master_doc, 'cleverhans', 'CleverHans Documentation', - [author], 1) -] +man_pages = [(master_doc, "cleverhans", "CleverHans Documentation", [author], 1)] # -- Options for Texinfo output ------------------------------------------- @@ -162,7 +162,13 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - (master_doc, 'CleverHans', 'CleverHans Documentation', - author, 'CleverHans', 'One line description of project.', - 'Miscellaneous'), + ( + master_doc, + "CleverHans", + "CleverHans Documentation", + author, + "CleverHans", + "One line description of project.", + "Miscellaneous", + ), ] diff --git a/examples/README.md b/examples/README.md index 895382671..df635b4e6 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,12 +1 @@ # Examples - -* **CIFAR10 with FGSM using the TensorFlow backend** ([code](ex_cifar10_tf.py)): this tutorial covers how to train a CIFAR10 model using TensorFlow, -craft adversarial examples using the [fast gradient sign method](https://arxiv.org/abs/1412.6572), -and make the model more robust to adversarial -examples using adversarial training. -* **Adversarial faces using FGSM against Facenet library** ([code](facenet_adversarial_faces)): this example covers how to create adversarial faces using the [fast gradient sign method](https://arxiv.org/abs/1412.6572) over the Facenet library. - -# Additional dependencies - -Besides the standard CleverHans dependencies, these examples also require -`wget`. diff --git a/examples/RL-attack/enjoy-adv.py b/examples/RL-attack/enjoy-adv.py deleted file mode 100644 index 8acbfe98f..000000000 --- a/examples/RL-attack/enjoy-adv.py +++ /dev/null @@ -1,227 +0,0 @@ -""" DQN - Test-time attacks - -============ Sample usage ============ -No attack, testing a DQN model of Breakout trained without parameter noise: -$> python3 enjoy-adv.py --env Breakout --model-dir \ - ./data/Breakout/model-100 --video ./Breakout.mp4 - -No attack, testing a DQN model of Breakout trained with parameter noise -(NoisyNet implementation): -$> python3 enjoy-adv.py --env Breakout --noisy \ - --model-dir ./data/Breakout/model-173000 --video ./Breakout.mp4 - -Whitebox FGSM attack, testing a DQN model of Breakout -trained without parameter noise: -$> python3 enjoy-adv.py --env Breakout --model-dir \ - ./data/Breakout/model-173000 --attack fgsm --video ./Breakout.mp4 - -Whitebox FGSM attack, testing a DQN model of Breakout -trained with parameter noise (NoisyNet implementation): -$> python3 enjoy-adv.py --env Breakout --noisy --model-dir \ - ./data/Breakout/model-173000 --attack fgsm --video ./Breakout.mp4 - -Blackbox FGSM attack, testing a DQN model of Breakout -trained without parameter noise: -$> python3 enjoy-adv.py --env Breakout --model-dir \ - ./data/Breakout/model-173000 --attack fgsm --blackbox \ - --model-dir2 ./data/Breakout/model-173000-2 --video ./Breakout.mp4 - -Blackbox FGSM attack, testing a DQN model of Breakout -trained with parameter noise (NoisyNet implementation), -replica model trained without parameter noise: -$> python3 enjoy-adv.py --env Breakout --noisy \ - --model-dir ./data/Breakout/model-173000 --attack fgsm --blackbox \ - --model-dir2 ./data/Breakout/model2-173000-2 --video ./Breakout.mp4 - -Blackbox FGSM attack, testing a DQN model of Breakout -trained with parameter noise (NoisyNet implementation), -replica model trained with parameter noise: -$> python3 enjoy-adv.py --env Breakout --noisy --model-dir \ - ./data/Breakout/model-173000 --attack fgsm --blackbox \ - --model-dir2 ./data/Breakout/model2-173000 --noisy2 --video ./Breakout.mp4 - -""" - -import argparse -import gym -import os -import numpy as np - -from gym.monitoring import VideoRecorder - -import rlattack.common.tf_util as U - -from rlattack import deepq -from rlattack.common.misc_util import ( - boolean_flag, - SimpleMonitor, -) -from rlattack.common.atari_wrappers_deprecated import wrap_dqn - - -# V: imports# -import tensorflow as tf -import cv2 -from collections import deque -from model import model, dueling_model -from statistics import statistics - - -class DQNModel: - """ - Creating Q-graph, FGSM graph - Supports loading multiple graphs - needed for blackbox attacks - """ - - def __init__(self, env, dueling, noisy, fname): - self.g = tf.Graph() - self.noisy = noisy - self.dueling = dueling - self.env = env - with self.g.as_default(): - self.act = deepq.build_act_enjoy( - make_obs_ph=lambda name: U.Uint8Input( - env.observation_space.shape, name=name), - q_func=dueling_model if dueling else model, - num_actions=env.action_space.n, - noisy=noisy - ) - self.saver = tf.train.Saver() - self.sess = tf.Session(graph=self.g) - - if fname is not None: - print('Loading Model...') - self.saver.restore(self.sess, fname) - - def get_act(self): - return self.act - - def get_session(self): - return self.sess - - def craft_adv(self): - with self.sess.as_default(): - with self.g.as_default(): - craft_adv_obs = deepq.build_adv( - make_obs_tf=lambda name: U.Uint8Input( - self.env.observation_space.shape, name=name), - q_func=dueling_model if self.dueling else model, - num_actions=self.env.action_space.n, - epsilon=1.0 / 255.0, - noisy=self.noisy, - ) - return craft_adv_obs - - -def parse_args(): - parser = argparse.ArgumentParser("Run an already learned DQN model.") - # Environment - parser.add_argument("--env", type=str, required=True, - help="name of the game") - parser.add_argument("--model-dir", type=str, default=None, - help="load model from this directory. ") - parser.add_argument("--video", type=str, default=None, - help="Path to mp4 file where the \ - video of first episode will be recorded.") - boolean_flag(parser, "stochastic", default=True, - help="whether or not to use stochastic \ - actions according to models eps value") - boolean_flag(parser, "dueling", default=False, - help="whether or not to use dueling model") - # V: Attack Arguments# - parser.add_argument("--model-dir2", type=str, default=None, - help="load adversarial model from \ - this directory (blackbox attacks). ") - parser.add_argument("--attack", type=str, default=None, - help="Method to attack the model.") - boolean_flag(parser, "noisy", default=False, - help="whether or not to NoisyNetwork") - boolean_flag(parser, "noisy2", default=False, - help="whether or not to NoisyNetwork") - boolean_flag(parser, "blackbox", default=False, - help="whether or not to NoisyNetwork") - - return parser.parse_args() - - -def make_env(game_name): - env = gym.make(game_name + "NoFrameskip-v4") - env = SimpleMonitor(env) - env = wrap_dqn(env) - return env - - -def play(env, act, craft_adv_obs, craft_adv_obs2, stochastic, video_path, - attack, m_target, m_adv): - num_episodes = 0 - num_moves = 0 - num_transfer = 0 - - video_recorder = None - video_recorder = VideoRecorder( - env, video_path, enabled=video_path is not None) - obs = env.reset() - while True: - env.unwrapped.render() - video_recorder.capture_frame() - - # V: Attack # - if attack is not None: - # Craft adv. examples - with m_adv.get_session().as_default(): - adv_obs = \ - craft_adv_obs(np.array(obs)[None], - stochastic_adv=stochastic)[0] - with m_target.get_session().as_default(): - action = act(np.array(adv_obs)[None], - stochastic=stochastic)[0] - action2 = act(np.array(obs)[None], stochastic=stochastic)[0] - num_moves += 1 - if action != action2: - num_transfer += 1 - else: - # Normal - action = act(np.array(obs)[None], stochastic=stochastic)[0] - - obs, rew, done, info = env.step(action) - if done: - obs = env.reset() - - if len(info["rewards"]) > num_episodes: - if len(info["rewards"]) == 1 and video_recorder.enabled: - # save video of first episode - print("Saved video.") - video_recorder.close() - video_recorder.enabled = False - print('Reward: ' + str(info["rewards"][-1])) - num_episodes = len(info["rewards"]) - print('Episode: ' + str(num_episodes)) - success = float(num_transfer / num_moves) * 100.0 - print("Percentage of successful attacks: " + str(success)) - num_moves = 0 - num_transfer = 0 - - -if __name__ == '__main__': - args = parse_args() - env = make_env(args.env) - g1 = tf.Graph() - g2 = tf.Graph() - with g1.as_default(): - m1 = DQNModel(env, args.dueling, args.noisy, - os.path.join(args.model_dir, "saved")) - if args.blackbox: - with g2.as_default(): - m2 = DQNModel(env, args.dueling, args.noisy2, - os.path.join(args.model_dir2, "saved")) - with m2.get_session().as_default(): - craft_adv_obs = m2.craft_adv() - with m1.get_session().as_default(): - craft_adv_obs2 = m1.craft_adv() - play(env, m1.get_act(), craft_adv_obs, craft_adv_obs2, - args.stochastic, args.video, args.attack, m1, m2) - else: - with m1.get_session().as_default(): - craft_adv_obs = m1.craft_adv() - play(env, m1.get_act(), craft_adv_obs, None, args.stochastic, - args.video, args.attack, m1, m1) diff --git a/examples/RL-attack/model.py b/examples/RL-attack/model.py deleted file mode 100644 index e0f2df813..000000000 --- a/examples/RL-attack/model.py +++ /dev/null @@ -1,95 +0,0 @@ -import tensorflow as tf -import tensorflow.contrib.layers as layers -from rlattack.common.tf_util import noisy_dense - - -def model(img_in, num_actions, scope, noisy=False, reuse=False, - concat_softmax=False): - with tf.variable_scope(scope, reuse=reuse): - out = img_in - with tf.variable_scope("convnet"): - # original architecture - out = layers.convolution2d(out, num_outputs=32, kernel_size=8, - stride=4, activation_fn=tf.nn.relu) - out = layers.convolution2d(out, num_outputs=64, kernel_size=4, - stride=2, activation_fn=tf.nn.relu) - out = layers.convolution2d(out, num_outputs=64, kernel_size=3, - stride=1, activation_fn=tf.nn.relu) - out = layers.flatten(out) - - with tf.variable_scope("action_value"): - if noisy: - # Apply noisy network on fully connected layers - # ref: https://arxiv.org/abs/1706.10295 - out = noisy_dense(out, name='noisy_fc1', size=512, - activation_fn=tf.nn.relu) - out = noisy_dense(out, name='noisy_fc2', size=num_actions) - else: - out = layers.fully_connected(out, num_outputs=512, - activation_fn=tf.nn.relu) - out = layers.fully_connected(out, num_outputs=num_actions, - activation_fn=None) - # V: Softmax - inspired by deep-rl-attack # - if concat_softmax: - out = tf.nn.softmax(out) - return out - - -def dueling_model(img_in, num_actions, scope, noisy=False, reuse=False, - concat_softmax=False): - """As described in https://arxiv.org/abs/1511.06581""" - with tf.variable_scope(scope, reuse=reuse): - out = img_in - with tf.variable_scope("convnet"): - # original architecture - out = layers.convolution2d(out, num_outputs=32, kernel_size=8, - stride=4, activation_fn=tf.nn.relu) - out = layers.convolution2d(out, num_outputs=64, kernel_size=4, - stride=2, activation_fn=tf.nn.relu) - out = layers.convolution2d(out, num_outputs=64, kernel_size=3, - stride=1, activation_fn=tf.nn.relu) - out = layers.flatten(out) - - with tf.variable_scope("state_value"): - if noisy: - # Apply noisy network on fully connected layers - # ref: https://arxiv.org/abs/1706.10295 - state_hidden = noisy_dense(out, name='noisy_fc1', size=512, - activation_fn=tf.nn.relu) - state_score = noisy_dense(state_hidden, name='noisy_fc2', - size=1) - else: - state_hidden = layers.fully_connected( - out, - num_outputs=512, - activation_fn=tf.nn.relu - ) - state_score = layers.fully_connected(state_hidden, - num_outputs=1, - activation_fn=None) - with tf.variable_scope("action_value"): - if noisy: - # Apply noisy network on fully connected layers - # ref: https://arxiv.org/abs/1706.10295 - actions_hidden = noisy_dense(out, name='noisy_fc1', size=512, - activation_fn=tf.nn.relu) - action_scores = noisy_dense(actions_hidden, name='noisy_fc2', - size=num_actions) - else: - actions_hidden = layers.fully_connected( - out, - num_outputs=512, - activation_fn=tf.nn.relu - ) - action_scores = layers.fully_connected( - actions_hidden, - num_outputs=num_actions, - activation_fn=None - ) - action_scores_mean = tf.reduce_mean(action_scores, 1) - action_scores = action_scores - tf.expand_dims( - action_scores_mean, - 1 - ) - - return state_score + action_scores diff --git a/examples/RL-attack/train.py b/examples/RL-attack/train.py deleted file mode 100644 index 431111dc1..000000000 --- a/examples/RL-attack/train.py +++ /dev/null @@ -1,353 +0,0 @@ -import argparse -import gym -import numpy as np -import os -import tensorflow as tf -import tempfile -import time -import json -import random - -import rlattack.common.tf_util as U - -from rlattack import logger -from rlattack import deepq -from rlattack.deepq.replay_buffer import ReplayBuffer, PrioritizedReplayBuffer -from rlattack.common.misc_util import ( - boolean_flag, - pickle_load, - pretty_eta, - relatively_safe_pickle_dump, - set_global_seeds, - RunningAvg, - SimpleMonitor -) -from rlattack.common.schedules import LinearSchedule, PiecewiseSchedule -# when updating this to non-deprecated ones, it is important to -# copy over LazyFrames -from rlattack.common.atari_wrappers_deprecated import wrap_dqn -from rlattack.common.azure_utils import Container -from model import model, dueling_model -from statistics import statistics - - -def parse_args(): - parser = argparse.ArgumentParser("DQN experiments for Atari games") - # Environment - parser.add_argument("--env", type=str, default="Pong", - help="name of the game") - parser.add_argument("--seed", type=int, default=42, - help="which seed to use") - # Core DQN parameters - parser.add_argument("--replay-buffer-size", type=int, default=int(1e6), - help="replay buffer size") - parser.add_argument("--lr", type=float, default=1e-4, - help="learning rate for Adam optimizer") - parser.add_argument("--num-steps", type=int, default=int(2e8), - help="total number of steps to \ - run the environment for") - parser.add_argument("--batch-size", type=int, default=32, - help="number of transitions to optimize \ - at the same time") - parser.add_argument("--learning-freq", type=int, default=4, - help="number of iterations between \ - every optimization step") - parser.add_argument("--target-update-freq", type=int, default=40000, - help="number of iterations between \ - every target network update") - # Bells and whistles - boolean_flag(parser, "noisy", default=False, - help="whether or not to NoisyNetwork") - boolean_flag(parser, "double-q", default=True, - help="whether or not to use double q learning") - boolean_flag(parser, "dueling", default=False, - help="whether or not to use dueling model") - boolean_flag(parser, "prioritized", default=False, - help="whether or not to use prioritized replay buffer") - parser.add_argument("--prioritized-alpha", type=float, default=0.6, - help="alpha parameter for prioritized replay buffer") - parser.add_argument("--prioritized-beta0", type=float, default=0.4, - help="initial value of beta \ - parameters for prioritized replay") - parser.add_argument("--prioritized-eps", type=float, default=1e-6, - help="eps parameter for prioritized replay buffer") - # Checkpointing - parser.add_argument("--save-dir", type=str, default=None, required=True, - help="directory in which \ - training state and model should be saved.") - parser.add_argument("--save-azure-container", type=str, default=None, - help="It present data will saved/loaded from Azure. \ - Should be in format ACCOUNT_NAME:ACCOUNT_KEY:\ - CONTAINER") - parser.add_argument("--save-freq", type=int, default=1e6, - help="save model once every time this many \ - iterations are completed") - boolean_flag(parser, "load-on-start", default=True, - help="if true and model was previously saved then training \ - will be resumed") - - # V: Attack Arguments # - parser.add_argument("--attack", type=str, default=None, - help="Method to attack the model.") - parser.add_argument("--attack-init", type=int, default=0, - help="Iteration no. to begin attacks") - parser.add_argument("--attack-prob", type=float, default=0.0, - help="Probability of attack at each step, \ - float in range 0 - 1.0") - return parser.parse_args() - - -def make_env(game_name): - env = gym.make(game_name + "NoFrameskip-v4") - monitored_env = SimpleMonitor(env) - env = wrap_dqn(monitored_env) - return env, monitored_env - - -def maybe_save_model(savedir, container, state): - if savedir is None: - return - start_time = time.time() - model_dir = "model-{}".format(state["num_iters"]) - U.save_state(os.path.join(savedir, model_dir, "saved")) - if container is not None: - container.put(os.path.join(savedir, model_dir), model_dir) - relatively_safe_pickle_dump(state, - os.path.join(savedir, - 'training_state.pkl.zip'), - compression=True) - if container is not None: - container.put(os.path.join(savedir, 'training_state.pkl.zip'), - 'training_state.pkl.zip') - relatively_safe_pickle_dump(state["monitor_state"], - os.path.join(savedir, 'monitor_state.pkl')) - if container is not None: - container.put(os.path.join(savedir, 'monitor_state.pkl'), - 'monitor_state.pkl') - logger.log("Saved model in {} seconds\n".format(time.time() - start_time)) - - -def maybe_load_model(savedir, container): - """Load model if present at the specified path.""" - if savedir is None: - return - - state_path = os.path.join(os.path.join(savedir, 'training_state.pkl.zip')) - if container is not None: - logger.log("Attempting to download model from Azure") - found_model = container.get(savedir, 'training_state.pkl.zip') - else: - found_model = os.path.exists(state_path) - if found_model: - state = pickle_load(state_path, compression=True) - model_dir = "model-{}".format(state["num_iters"]) - if container is not None: - container.get(savedir, model_dir) - U.load_state(os.path.join(savedir, model_dir, "saved")) - logger.log("Loaded models checkpoint at {} iterations".format( - state["num_iters"])) - return state - - -if __name__ == '__main__': - args = parse_args() - # Parse savedir and azure container. - savedir = args.save_dir - if args.save_azure_container is not None: - account_name, account_key, container_name = \ - args.save_azure_container.split(":") - container = Container( - account_name=account_name, - account_key=account_key, - container_name=container_name, - maybe_create=True - ) - if savedir is None: - # Careful! This will not get cleaned up. - savedir = tempfile.TemporaryDirectory().name - else: - container = None - # Create and seed the env. - env, monitored_env = make_env(args.env) - if args.seed > 0: - set_global_seeds(args.seed) - env.unwrapped.seed(args.seed) - - # V: Save arguments, configure log dump path to savedir # - if savedir: - with open(os.path.join(savedir, 'args.json'), 'w') as f: - json.dump(vars(args), f) - logger.configure(dir=savedir) # log to savedir - - with U.make_session(4) as sess: - # Create training graph and replay buffer - act, train, update_target, debug, craft_adv = deepq.build_train( - make_obs_ph=lambda name: U.Uint8Input(env.observation_space.shape, - name=name), - q_func=dueling_model if args.dueling else model, - num_actions=env.action_space.n, - optimizer=tf.train.AdamOptimizer(learning_rate=args.lr, - epsilon=1e-4), - gamma=0.99, - grad_norm_clipping=10, - double_q=args.double_q, - noisy=args.noisy, - attack=args.attack - ) - approximate_num_iters = args.num_steps / 4 - exploration = PiecewiseSchedule([ - (0, 1.0), - (approximate_num_iters / 50, 0.1), - (approximate_num_iters / 5, 0.01) - ], outside_value=0.01) - - if args.prioritized: - replay_buffer = PrioritizedReplayBuffer(args.replay_buffer_size, - args.prioritized_alpha) - beta_schedule = LinearSchedule(approximate_num_iters, - initial_p=args.prioritized_beta0, - final_p=1.0) - else: - replay_buffer = ReplayBuffer(args.replay_buffer_size) - - U.initialize() - update_target() - num_iters = 0 - - # Load the model - state = maybe_load_model(savedir, container) - if state is not None: - num_iters, replay_buffer = state["num_iters"], state[ - "replay_buffer"], - monitored_env.set_state(state["monitor_state"]) - - start_time, start_steps = None, None - steps_per_iter = RunningAvg(0.999) - iteration_time_est = RunningAvg(0.999) - obs = env.reset() - # Record the mean of the \sigma - sigma_name_list = [] - sigma_list = [] - for param in tf.trainable_variables(): - # only record the \sigma in the action network - if 'sigma' in param.name \ - and 'deepq/q_func/action_value' in param.name: - summary_name = \ - param.name.replace( - 'deepq/q_func/action_value/', '').replace( - '/', '.').split(':')[0] - sigma_name_list.append(summary_name) - sigma_list.append(tf.reduce_mean(tf.abs(param))) - f_mean_sigma = U.function(inputs=[], outputs=sigma_list) - # Statistics - writer = tf.summary.FileWriter(savedir, sess.graph) - im_stats = statistics(scalar_keys=['action', 'im_reward', 'td_errors', - 'huber_loss'] + sigma_name_list) - ep_stats = statistics(scalar_keys=['ep_reward', 'ep_length']) - # Main trianing loop - ep_length = 0 - while True: - num_iters += 1 - ep_length += 1 - - # V: Perturb observation if we are past the init stage - # and at a designated attack step - # if craft_adv != None and (num_iters >= args.attack_init) - # and ((num_iters - args.attack_init) % args.attack_freq == 0) : - if craft_adv is not None and (num_iters >= args.attack_init) and ( - random.random() <= args.attack_prob): - obs = craft_adv(np.array(obs)[None])[0] - - # Take action and store transition in the replay buffer. - if args.noisy: - # greedily choose - action = act(np.array(obs)[None], stochastic=False)[0] - else: - # epsilon greedy - action = act(np.array(obs)[None], - update_eps=exploration.value(num_iters))[0] - new_obs, rew, done, info = env.step(action) - replay_buffer.add(obs, action, rew, new_obs, float(done)) - obs = new_obs - if done: - obs = env.reset() - - if (num_iters > max(5 * args.batch_size, - args.replay_buffer_size // 20) and - num_iters % args.learning_freq == 0): - # Sample a bunch of transitions from replay buffer - if args.prioritized: - experience = replay_buffer.sample(args.batch_size, - beta=beta_schedule.value( - num_iters)) - (obses_t, actions, rewards, obses_tp1, dones, weights, - batch_idxes) = experience - else: - obses_t, actions, rewards, obses_tp1, dones = \ - replay_buffer.sample(args.batch_size) - weights = np.ones_like(rewards) - # Minimize the error in Bellman's and compute TD-error - td_errors, huber_loss = train(obses_t, actions, rewards, - obses_tp1, dones, weights) - # Update the priorities in the replay buffer - if args.prioritized: - new_priorities = np.abs(td_errors) + args.prioritized_eps - replay_buffer.update_priorities( - batch_idxes, new_priorities - ) - # Write summary - mean_sigma = f_mean_sigma() - im_stats.add_all_summary(writer, - [action, rew, np.mean(td_errors), - np.mean(huber_loss)] + mean_sigma, - num_iters) - - # Update target network. - if num_iters % args.target_update_freq == 0: - update_target() - - if start_time is not None: - steps_per_iter.update(info['steps'] - start_steps) - iteration_time_est.update(time.time() - start_time) - start_time, start_steps = time.time(), info["steps"] - - # Save the model and training state. - if num_iters > 0 and (num_iters % args.save_freq == 0 or info[ - "steps"] > args.num_steps): - maybe_save_model(savedir, container, { - 'replay_buffer': replay_buffer, - 'num_iters': num_iters, - 'monitor_state': monitored_env.get_state() - }) - - if info["steps"] > args.num_steps: - break - - if done: - steps_left = args.num_steps - info["steps"] - completion = np.round(info["steps"] / args.num_steps, 1) - mean_ep_reward = np.mean(info["rewards"][-100:]) - logger.record_tabular("% completion", completion) - logger.record_tabular("steps", info["steps"]) - logger.record_tabular("iters", num_iters) - logger.record_tabular("episodes", len(info["rewards"])) - logger.record_tabular("reward (100 epi mean)", - np.mean(info["rewards"][-100:])) - if not args.noisy: - logger.record_tabular("exploration", - exploration.value(num_iters)) - if args.prioritized: - logger.record_tabular("max priority", - replay_buffer._max_priority) - fps_estimate = ( - float(steps_per_iter) / (float(iteration_time_est) + 1e-6) - if steps_per_iter._value is not None else "calculating:") - logger.dump_tabular() - logger.log() - logger.log("ETA: " + - pretty_eta(int(steps_left / fps_estimate))) - logger.log() - # add summary for one episode - ep_stats.add_all_summary(writer, [mean_ep_reward, ep_length], - num_iters) - ep_length = 0 diff --git a/examples/adversarial_asr/generate_imperceptible_adv.py b/examples/adversarial_asr/generate_imperceptible_adv.py deleted file mode 100644 index e84e4e82f..000000000 --- a/examples/adversarial_asr/generate_imperceptible_adv.py +++ /dev/null @@ -1,430 +0,0 @@ -import tensorflow as tf -from lingvo import model_imports -from lingvo import model_registry -import numpy as np -import scipy.io.wavfile as wav -import generate_masking_threshold as generate_mask -from tool import Transform, create_features, create_inputs -import time -from lingvo.core import cluster_factory -from absl import flags -from absl import app - -# data directory -flags.DEFINE_string("root_dir", "./", "location of Librispeech") -flags.DEFINE_string('input', 'read_data.txt', - 'Input audio .wav file(s), at 16KHz (separated by spaces)') - -# data processing -flags.DEFINE_integer('window_size', '2048', 'window size in spectrum analysis') -flags.DEFINE_integer('max_length_dataset', '223200', - 'the length of the longest audio in the whole dataset') -flags.DEFINE_float('initial_bound', '2000', 'initial l infinity norm for adversarial perturbation') - -# training parameters -flags.DEFINE_string('checkpoint', "./model/ckpt-00908156", - 'location of checkpoint') -flags.DEFINE_integer('batch_size', '5', 'batch size') -flags.DEFINE_float('lr_stage1', '100', 'learning_rate for stage 1') -flags.DEFINE_float('lr_stage2', '1', 'learning_rate for stage 2') -flags.DEFINE_integer('num_iter_stage1', '1000', 'number of iterations in stage 1') -flags.DEFINE_integer('num_iter_stage2', '4000', 'number of iterations in stage 2') -flags.DEFINE_integer('num_gpu', '0', 'which gpu to run') - -FLAGS = flags.FLAGS - - -def ReadFromWav(data, batch_size): - """ - Returns: - audios_np: a numpy array of size (batch_size, max_length) in float - trans: a numpy array includes the targeted transcriptions (batch_size, ) - th_batch: a numpy array of the masking threshold, each of size (?, 1025) - psd_max_batch: a numpy array of the psd_max of the original audio (batch_size) - max_length: the max length of the batch of audios - sample_rate_np: a numpy array - masks: a numpy array of size (batch_size, max_length) - masks_freq: a numpy array of size (batch_size, max_length_freq, 80) - lengths: a list of the length of original audios - """ - audios = [] - lengths = [] - th_batch = [] - psd_max_batch = [] - - # read the .wav file - for i in range(batch_size): - sample_rate_np, audio_temp = wav.read(FLAGS.root_dir + str(data[0, i])) - # read the wav form range from [-32767, 32768] or [-1, 1] - if max(audio_temp) < 1: - audio_np = audio_temp * 32768 - else: - audio_np = audio_temp - - length = len(audio_np) - - audios.append(audio_np) - lengths.append(length) - - max_length = max(lengths) - - # pad the input audio - audios_np = np.zeros([batch_size, max_length]) - masks = np.zeros([batch_size, max_length]) - lengths_freq = (np.array(lengths) // 2 + 1) // 240 * 3 - max_length_freq = max(lengths_freq) - masks_freq = np.zeros([batch_size, max_length_freq, 80]) - for i in range(batch_size): - audio_float = audios[i].astype(float) - audios_np[i, :lengths[i]] = audio_float - masks[i, :lengths[i]] = 1 - masks_freq[i, :lengths_freq[i], :] = 1 - - # compute the masking threshold - th, psd_max = generate_mask.generate_th(audios_np[i], sample_rate_np, FLAGS.window_size) - th_batch.append(th) - psd_max_batch.append(psd_max) - - th_batch = np.array(th_batch) - psd_max_batch = np.array(psd_max_batch) - - # read the transcription - trans = data[2, :] - - return audios_np, trans, th_batch, psd_max_batch, max_length, sample_rate_np, masks, masks_freq, lengths - - -class Attack: - def __init__(self, sess, batch_size=1, - lr_stage1=100, lr_stage2=0.1, num_iter_stage1=1000, num_iter_stage2=4000, th=None, - psd_max_ori=None): - - self.sess = sess - self.num_iter_stage1 = num_iter_stage1 - self.num_iter_stage2 = num_iter_stage2 - self.batch_size = batch_size - self.lr_stage1 = lr_stage1 - - tf.set_random_seed(1234) - params = model_registry.GetParams('asr.librispeech.Librispeech960Wpm', 'Test') - params.random_seed = 1234 - params.is_eval = True - params.cluster.worker.gpus_per_replica = 1 - cluster = cluster_factory.Cluster(params.cluster) - with cluster, tf.device(cluster.GetPlacer()): - model = params.cls(params) - self.delta_large = tf.Variable(np.zeros((batch_size, FLAGS.max_length_dataset), dtype=np.float32), name='qq_delta') - - # placeholders - self.input_tf = tf.placeholder(tf.float32, shape=[batch_size, None], name='qq_input') - self.tgt_tf = tf.placeholder(tf.string) - self.sample_rate_tf = tf.placeholder(tf.int32, name='qq_sample_rate') - self.th = tf.placeholder(tf.float32, shape=[batch_size, None, None], name='qq_th') - self.psd_max_ori = tf.placeholder(tf.float32, shape=[batch_size], name='qq_psd') - self.mask = tf.placeholder(dtype=np.float32, shape=[batch_size, None], name='qq_mask') - self.mask_freq = tf.placeholder(dtype=np.float32, shape=[batch_size, None, 80]) - self.noise = tf.placeholder(np.float32, shape=[batch_size, None], name="qq_noise") - self.maxlen = tf.placeholder(np.int32) - self.lr_stage2 = tf.placeholder(np.float32) - - # variable - self.rescale = tf.Variable(np.ones((batch_size,1), dtype=np.float32), name='qq_rescale') - self.alpha = tf.Variable(np.ones((batch_size), dtype=np.float32) * 0.05, name='qq_alpha') - - # extract the delta - self.delta = tf.slice(tf.identity(self.delta_large), [0, 0], [batch_size, self.maxlen]) - self.apply_delta = tf.clip_by_value(self.delta, -FLAGS.initial_bound, FLAGS.initial_bound) * self.rescale - self.new_input = self.apply_delta * self.mask + self.input_tf - self.pass_in = tf.clip_by_value(self.new_input + self.noise, -2**15, 2**15-1) - - # generate the inputs that are needed for the lingvo model - self.features = create_features(self.pass_in, self.sample_rate_tf, self.mask_freq) - self.inputs = create_inputs(model, self.features, self.tgt_tf, self.batch_size, self.mask_freq) - - task = model.GetTask() - metrics = task.FPropDefaultTheta(self.inputs) - # self.celoss with the shape (batch_size) - self.celoss = tf.get_collection("per_loss")[0] - self.decoded = task.Decode(self.inputs) - - - # compute the loss for masking threshold - self.loss_th_list = [] - self.transform = Transform(FLAGS.window_size) - for i in range(self.batch_size): - logits_delta = self.transform((self.apply_delta[i, :]), (self.psd_max_ori)[i]) - loss_th = tf.reduce_mean(tf.nn.relu(logits_delta - (self.th)[i])) - loss_th = tf.expand_dims(loss_th, dim=0) - self.loss_th_list.append(loss_th) - self.loss_th = tf.concat(self.loss_th_list, axis=0) - - - self.optimizer1 = tf.train.AdamOptimizer(self.lr_stage1) - self.optimizer2 = tf.train.AdamOptimizer(self.lr_stage2) - - grad1, var1 = self.optimizer1.compute_gradients(self.celoss, [self.delta_large])[0] - grad21, var21 = self.optimizer2.compute_gradients(self.celoss, [self.delta_large])[0] - grad22, var22 = self.optimizer2.compute_gradients(self.alpha * self.loss_th, [self.delta_large])[0] - - self.train1 = self.optimizer1.apply_gradients([(tf.sign(grad1), var1)]) - self.train21 = self.optimizer2.apply_gradients([(grad21, var21)]) - self.train22 = self.optimizer2.apply_gradients([(grad22, var22)]) - self.train2 = tf.group(self.train21, self.train22) - - def attack_stage1(self, audios, trans, th_batch, psd_max_batch, maxlen, sample_rate, masks, masks_freq, num_loop, data, lr_stage2): - sess = self.sess - # initialize and load the pretrained model - sess.run(tf.initializers.global_variables()) - saver = tf.train.Saver([x for x in tf.global_variables() if x.name.startswith("librispeech")]) - saver.restore(sess, FLAGS.checkpoint) - - # reassign the variables - sess.run(tf.assign(self.rescale, np.ones((self.batch_size, 1), dtype=np.float32))) - sess.run(tf.assign(self.delta_large, np.zeros((self.batch_size, FLAGS.max_length_dataset), dtype=np.float32))) - - #noise = np.random.normal(scale=2, size=audios.shape) - noise = np.zeros(audios.shape) - feed_dict = {self.input_tf: audios, - self.tgt_tf: trans, - self.sample_rate_tf: sample_rate, - self.th: th_batch, - self.psd_max_ori: psd_max_batch, - self.mask: masks, - self.mask_freq: masks_freq, - self.noise: noise, - self.maxlen: maxlen, - self.lr_stage2: lr_stage2} - losses, predictions = sess.run((self.celoss, self.decoded), feed_dict) - - # show the initial predictions - for i in range(self.batch_size): - print("example: {}, loss: {}".format(num_loop * self.batch_size + i, losses[i])) - print("pred:{}".format(predictions['topk_decoded'][i, 0])) - print("targ:{}".format(trans[i].lower())) - print("true: {}".format(data[1, i].lower())) - - - # We'll make a bunch of iterations of gradient descent here - now = time.time() - MAX = self.num_iter_stage1 - loss_th = [np.inf] * self.batch_size - final_deltas = [None] * self.batch_size - clock = 0 - - for i in range(MAX): - now = time.time() - - # Actually do the optimization - sess.run(self.train1, feed_dict) - if i % 10 == 0: - d, cl, predictions, new_input = sess.run((self.delta, self.celoss, self.decoded, self.new_input), feed_dict) - - for ii in range(self.batch_size): - # print out the prediction each 100 iterations - if i % 1000 == 0: - print("pred:{}".format(predictions['topk_decoded'][ii, 0])) - #print("rescale: {}".format(sess.run(self.rescale[ii]))) - if i % 10 == 0: - if i % 100 == 0: - print("example: {}".format(num_loop * self.batch_size + ii)) - print("iteration: {}. loss {}".format(i, cl[ii])) - - if predictions['topk_decoded'][ii, 0] == trans[ii].lower(): - print("-------------------------------True--------------------------") - - # update rescale - rescale = sess.run(self.rescale) - if rescale[ii] * FLAGS.initial_bound > np.max(np.abs(d[ii])): - rescale[ii] = np.max(np.abs(d[ii])) / FLAGS.initial_bound - rescale[ii] *= .8 - - # save the best adversarial example - final_deltas[ii] = new_input[ii] - - print("Iteration i=%d, worked ii=%d celoss=%f bound=%f"%(i, ii, cl[ii], FLAGS.initial_bound * rescale[ii])) - sess.run(tf.assign(self.rescale, rescale)) - - # in case no final_delta return - if (i == MAX-1 and final_deltas[ii] is None): - final_deltas[ii] = new_input[ii] - - if i % 10 == 0: - print("ten iterations take around {} ".format(clock)) - clock = 0 - - clock += time.time() - now - - return final_deltas - - def attack_stage2(self, audios, trans, adv, th_batch, psd_max_batch, maxlen, sample_rate, masks, masks_freq, num_loop, data, lr_stage2): - sess = self.sess - # initialize and load the pretrained model - sess.run(tf.initializers.global_variables()) - saver = tf.train.Saver([x for x in tf.global_variables() if x.name.startswith("librispeech")]) - saver.restore(sess, FLAGS.checkpoint) - - sess.run(tf.assign(self.rescale, np.ones((self.batch_size, 1), dtype=np.float32))) - sess.run(tf.assign(self.alpha, np.ones((self.batch_size), dtype=np.float32) * 0.05)) - - # reassign the variables - sess.run(tf.assign(self.delta_large, adv)) - - #noise = np.random.normal(scale=2, size=audios.shape) - noise = np.zeros(audios.shape) - feed_dict = {self.input_tf: audios, - self.tgt_tf: trans, - self.sample_rate_tf: sample_rate, - self.th: th_batch, - self.psd_max_ori: psd_max_batch, - self.mask: masks, - self.mask_freq: masks_freq, - self.noise: noise, - self.maxlen: maxlen, - self.lr_stage2: lr_stage2} - losses, predictions = sess.run((self.celoss, self.decoded), feed_dict) - - # show the initial predictions - for i in range(self.batch_size): - print("example: {}, loss: {}".format(num_loop * self.batch_size + i, losses[i])) - print("pred:{}".format(predictions['topk_decoded'][i, 0])) - print("targ:{}".format(trans[i].lower())) - print("true: {}".format(data[1, i].lower())) - - - # We'll make a bunch of iterations of gradient descent here - now = time.time() - MAX = self.num_iter_stage2 - loss_th = [np.inf] * self.batch_size - final_deltas = [None] * self.batch_size - final_alpha = [None] * self.batch_size - #final_th = [None] * self.batch_size - clock = 0 - min_th = 0.0005 - for i in range(MAX): - now = time.time() - if i == 3000: - #min_th = -np.inf - lr_stage2 = 0.1 - feed_dict = {self.input_tf: audios, - self.tgt_tf: trans, - self.sample_rate_tf: sample_rate, - self.th: th_batch, - self.psd_max_ori: psd_max_batch, - self.mask: masks, - self.mask_freq: masks_freq, - self.noise: noise, - self.maxlen: maxlen, - self.lr_stage2: lr_stage2} - - # Actually do the optimization - sess.run(self.train2, feed_dict) - - if i % 10 == 0: - d, cl, l, predictions, new_input = sess.run((self.delta, self.celoss, self.loss_th, self.decoded, self.new_input), feed_dict) - - for ii in range(self.batch_size): - # print out the prediction each 100 iterations - if i % 1000 == 0: - print("pred:{}".format(predictions['topk_decoded'][ii, 0])) - #print("rescale: {}".format(sess.run(self.rescale[ii]))) - if i % 10 == 0: - #print("example: {}".format(num_loop * self.batch_size + ii)) - - alpha = sess.run(self.alpha) - if i % 100 == 0: - print("example: {}".format(num_loop * self.batch_size + ii)) - print("iteration: %d, alpha: %f, loss_ce: %f, loss_th: %f"%(i, alpha[ii], cl[ii], l[ii])) - - # if the network makes the targeted prediction - if predictions['topk_decoded'][ii, 0] == trans[ii].lower(): - if l[ii] < loss_th[ii]: - final_deltas[ii] = new_input[ii] - loss_th[ii] = l[ii] - final_alpha[ii] = alpha[ii] - print("-------------------------------------Succeed---------------------------------") - print("save the best example=%d at iteration= %d, alpha = %f"%(ii, i, alpha[ii])) - - # increase the alpha each 20 iterations - if i % 20 == 0: - alpha[ii] *= 1.2 - sess.run(tf.assign(self.alpha, alpha)) - - # if the network fails to make the targeted prediction, reduce alpha each 50 iterations - if i % 50 == 0 and predictions['topk_decoded'][ii, 0] != trans[ii].lower(): - alpha[ii] *= 0.8 - alpha[ii] = max(alpha[ii], min_th) - sess.run(tf.assign(self.alpha, alpha)) - - # in case no final_delta return - if (i == MAX-1 and final_deltas[ii] is None): - final_deltas[ii] = new_input[ii] - if i % 500 == 0: - print("alpha is {}, loss_th is {}".format(final_alpha, loss_th)) - if i % 10 == 0: - print("ten iterations take around {} ".format(clock)) - clock = 0 - - clock += time.time() - now - - return final_deltas, loss_th, final_alpha - - -def main(argv): - data = np.loadtxt(FLAGS.input, dtype=str, delimiter=",") - data = data[:, FLAGS.num_gpu * 10 : (FLAGS.num_gpu + 1) * 10] - num = len(data[0]) - batch_size = FLAGS.batch_size - num_loops = num / batch_size - assert num % batch_size == 0 - - with tf.device("/gpu:0"): - tfconf = tf.ConfigProto(allow_soft_placement=True) - with tf.Session(config=tfconf) as sess: - # set up the attack class - attack = Attack(sess, - batch_size=batch_size, - lr_stage1=FLAGS.lr_stage1, - lr_stage2=FLAGS.lr_stage2, - num_iter_stage1=FLAGS.num_iter_stage1, - num_iter_stage2=FLAGS.num_iter_stage2) - - for l in range(num_loops): - data_sub = data[:, l * batch_size:(l + 1) * batch_size] - - # stage 1 - # all the output are numpy arrays - audios, trans, th_batch, psd_max_batch, maxlen, sample_rate, masks, masks_freq, lengths = ReadFromWav(data_sub, batch_size) - adv_example = attack.attack_stage1(audios, trans, th_batch, psd_max_batch, maxlen, sample_rate, masks, masks_freq, l, data_sub, FLAGS.lr_stage2) - - # save the adversarial examples in stage 1 - for i in range(batch_size): - print("Final distortion for stage 1", np.max(np.abs(adv_example[i][:lengths[i]] - audios[i, :lengths[i]]))) - name, _ = data_sub[0, i].split(".") - saved_name = FLAGS.root_dir + str(name) + "_stage1.wav" - adv_example_float = adv_example[i] / 32768. - wav.write(saved_name, 16000, np.array(adv_example_float[:lengths[i]])) - print(saved_name) - - # stage 2 - # read the adversarial examples saved in stage 1 - adv = np.zeros([batch_size, FLAGS.max_length_dataset]) - adv[:, :maxlen] = adv_example - audios - - adv_example, loss_th, final_alpha = attack.attack_stage2(audios, trans, adv, th_batch, psd_max_batch, maxlen, sample_rate, masks, masks_freq, l, data_sub, FLAGS.lr_stage2) - - # save the adversarial examples in stage 2 - for i in range(batch_size): - print("example: {}".format(i)) - print("Final distortion for stage 2: {}, final alpha is {}, final loss_th is {}".format(np.max(np.abs(adv_example[i][:lengths[i]] - audios[i, :lengths[i]])), final_alpha[i], loss_th[i])) - - name, _ = data_sub[0, i].split(".") - saved_name = FLAGS.root_dir + str(name) + "_stage2.wav" - adv_example[i] = adv_example[i] / 32768. - wav.write(saved_name, 16000, np.array(adv_example[i][:lengths[i]])) - print(saved_name) - - -if __name__ == '__main__': - app.run(main) - - diff --git a/examples/adversarial_asr/generate_robust_adv.py b/examples/adversarial_asr/generate_robust_adv.py deleted file mode 100644 index 150f11df3..000000000 --- a/examples/adversarial_asr/generate_robust_adv.py +++ /dev/null @@ -1,444 +0,0 @@ -import tensorflow as tf -from lingvo import model_imports -from lingvo import model_registry -import numpy as np -import scipy.io.wavfile as wav -import generate_masking_threshold as generate_mask -from tool import create_features, create_inputs, create_speech_rir -import time -from lingvo.core import cluster_factory -from absl import flags -from absl import app -import random - -# data directory -flags.DEFINE_string("root_dir", "./", "location of Librispeech") -flags.DEFINE_string('input', 'read_data.txt', - 'the text file saved the dir of audios and the corresponding original and targeted transcriptions') -flags.DEFINE_string('rir_dir', 'LibriSpeech/test-clean/3575/170457/3575-170457-0013', - 'directory of generated room reverberations') - -# data processing -flags.DEFINE_integer('max_length_dataset', '223200', 'the length of the longest audio in the whole dataset') -flags.DEFINE_float('initial_bound', '2000.', 'initial l infinity norm for adversarial perturbation') -flags.DEFINE_integer('num_rir', '1000', 'number of room reverberations used in training') -flags.DEFINE_integer('num_counter', '2', 'the initial number of required successful rooms') -flags.DEFINE_integer('num_rooms', '10', 'the initial number of rooms to test') -flags.DEFINE_integer('max_delta', '300', 'the max delta added to the max l infinity norm') - -# training parameters -flags.DEFINE_string('checkpoint', "./model/ckpt-00908156", 'location of checkpoint') -flags.DEFINE_integer('batch_size', '5', 'batch size') -flags.DEFINE_float('lr_stage1', '50', 'learning_rate for stage 1') -flags.DEFINE_float('lr_stage2', '5', 'learning_rate for stage 2') -flags.DEFINE_integer('num_iter_stage1', '2000', 'number of iterations in stage 1') -flags.DEFINE_integer('num_iter_stage2', '4000', 'number of iterations in stage 2') -flags.DEFINE_integer('num_gpu', '0', 'which gpu to run') - - -FLAGS = flags.FLAGS - -def ReadFromWav(data, batch_size): - """ - Returns: - audios_np: a numpy array of size (batch_size, max_length) in float - trans: a numpy array includes the targeted transcriptions (batch_size, ) - max_length: the max length of the batch of audios - sample_rate_np: a numpy array - masks: a numpy array of size (batch_size, max_length) - masks_freq: a numpy array of size (batch_size, max_length_freq, 80) - lengths: a list of the length of original audios - """ - audios = [] - lengths = [] - - # read the .wav file - for i in range(batch_size): - sample_rate_np, audio_temp = wav.read(FLAGS.root_dir + str(data[0, i])) - # read the wav form range from [-32767, 32768] or [-1, 1] - if max(audio_temp) < 1: - audio_np = audio_temp * 32768 - else: - audio_np = audio_temp - - length = len(audio_np) - - audios.append(audio_np) - lengths.append(length) - - max_length = max(lengths) - - # pad the input audio - audios_np = np.zeros([batch_size, max_length]) - masks = np.zeros([batch_size, max_length]) - lengths_freq = (np.array(lengths) // 2 + 1) // 240 * 3 - max_length_freq = max(lengths_freq) - masks_freq = np.zeros([batch_size, max_length_freq, 80]) - for i in range(batch_size): - audio_float = audios[i].astype(float) - audios_np[i, :lengths[i]] = audio_float - masks[i, :lengths[i]] = 1 - masks_freq[i, :lengths_freq[i], :] = 1 - - # read the transcription - trans = data[2, :] - lengths = np.array(lengths).astype(np.int32) - - return audios_np, trans, max_length, sample_rate_np, masks, masks_freq, lengths - -def Readrir(): - ''' - Return: - rir: a numpy array of the room reverberation - - ''' - index = random.randint(1, FLAGS.num_rir) - _, rir = wav.read(FLAGS.root_dir + FLAGS.rir_dir + "_rir_" + str(index) + ".wav") - return rir - - -class Attack: - def __init__(self, sess, batch_size=1, - lr_stage1=100, lr_stage2=0.1, num_iter_stage1=1000, num_iter_stage2=4000, th=None, - psd_max_ori=None): - - self.sess = sess - self.num_iter_stage1 = num_iter_stage1 - self.num_iter_stage2 = num_iter_stage2 - self.batch_size = batch_size - self.lr_stage1 = lr_stage1 - self.lr_stage2 = lr_stage2 - - tf.set_random_seed(1234) - params = model_registry.GetParams('asr.librispeech.Librispeech960Wpm', 'Test') - params.random_seed = 1234 - params.is_eval = True - params.cluster.worker.gpus_per_replica = 1 - cluster = cluster_factory.Cluster(params.cluster) - with cluster, tf.device(cluster.GetPlacer()): - model = params.cls(params) - self.delta_large = tf.Variable(np.zeros((batch_size, FLAGS.max_length_dataset), dtype=np.float32), name='qq_delta') - - # placeholders - self.input_tf = tf.placeholder(tf.float32, shape=[batch_size, None], name='qq_input') - self.tgt_tf = tf.placeholder(tf.string) - self.rir = tf.placeholder(tf.float32) - - self.sample_rate_tf = tf.placeholder(tf.int32, name='qq_sample_rate') - self.mask = tf.placeholder(dtype=np.float32, shape=[batch_size, None], name='qq_mask') - self.mask_freq = tf.placeholder(dtype=np.float32, shape=[batch_size, None, 80]) - self.noise = tf.placeholder(np.float32, shape=[batch_size, None], name="qq_noise") - self.maxlen = tf.placeholder(np.int32) - self.lr = tf.placeholder(np.float32) - self.lengths = tf.placeholder(np.int32, shape=[batch_size,]) - - # variable - self.rescale = tf.Variable(np.ones((batch_size,1), dtype=np.float32) * FLAGS.initial_bound, name='qq_rescale') - - # extract the delta - self.delta = tf.slice(tf.identity(self.delta_large), [0, 0], [batch_size, self.maxlen]) - self.apply_delta = tf.clip_by_value(self.delta, -self.rescale, self.rescale) - self.before_rir = tf.clip_by_value(self.apply_delta * self.mask + self.input_tf, -2**15, 2**15-1) - self.new_input = create_speech_rir(self.before_rir, self.rir, self.lengths, self.maxlen, self.batch_size) * self.mask - self.pass_in = tf.clip_by_value(self.new_input + self.noise, -2**15, 2**15-1) - - # generate the inputs that are needed for the lingvo model - self.features = create_features(self.pass_in, self.sample_rate_tf, self.mask_freq) - self.inputs = create_inputs(model, self.features, self.tgt_tf, self.batch_size, self.mask_freq) - - task = model.GetTask() - metrics = task.FPropDefaultTheta(self.inputs) - - # self.celoss with the shape (batch_size) - self.celoss = tf.get_collection("per_loss")[0] - self.decoded = task.Decode(self.inputs) - - self.optimizer1 = tf.train.AdamOptimizer(self.lr) - grad1, var1 = self.optimizer1.compute_gradients(self.celoss, [self.delta_large])[0] - self.train1 = self.optimizer1.apply_gradients([(tf.sign(grad1), var1)]) - - - def attack_stage1(self, audios, trans, maxlen, sample_rate, masks, masks_freq, num_loop, data, lengths): - """ - The first stage saves the adversarial examples that can successfully attack one room - """ - - sess = self.sess - # initialize and load the pretrained model - sess.run(tf.initializers.global_variables()) - saver = tf.train.Saver([x for x in tf.global_variables() if x.name.startswith("librispeech")]) - saver.restore(sess, FLAGS.checkpoint) - - # reassign the variables - sess.run(tf.assign(self.rescale, np.ones((self.batch_size, 1), dtype=np.float32) * FLAGS.initial_bound)) - sess.run(tf.assign(self.delta_large, np.zeros((self.batch_size, FLAGS.max_length_dataset), dtype=np.float32))) - - noise = np.zeros(audios.shape) - rir = Readrir() - feed_dict = {self.input_tf: audios, - self.tgt_tf: trans, - self.sample_rate_tf: sample_rate, - self.mask: masks, - self.mask_freq: masks_freq, - self.noise: noise, - self.maxlen: maxlen, - self.lengths: lengths, - self.rir: rir, - self.lr: self.lr_stage1} - losses, predictions = sess.run((self.celoss, self.decoded), feed_dict) - - # show the initial predictions - for i in range(self.batch_size): - print("example: {}, loss: {}".format(num_loop * self.batch_size + i, losses[i])) - print("pred:{}".format(predictions['topk_decoded'][i, 0])) - print("targ:{}".format(trans[i].lower())) - print("true: {}".format(data[1, i].lower())) - - - # We'll make a bunch of iterations of gradient descent here - now = time.time() - MAX = self.num_iter_stage1 - loss_th = [np.inf] * self.batch_size - final_adv = [None] * self.batch_size - final_perturb = [None] * self.batch_size - clock = 0 - - for i in range(1, MAX + 1): - now = time.time() - - rir = Readrir() - feed_dict = {self.input_tf: audios, - self.tgt_tf: trans, - self.sample_rate_tf: sample_rate, - self.mask: masks, - self.mask_freq: masks_freq, - self.noise: noise, - self.maxlen: maxlen, - self.lengths: lengths, - self.rir: rir, - self.lr: self.lr_stage1} - losses, predictions = sess.run((self.celoss, self.decoded), feed_dict) - - # Actually do the optimization - sess.run(self.train1, feed_dict) - if i % 10 == 0: - d, apply_delta, cl, predictions, new_input = sess.run((self.delta, self.apply_delta, self.celoss, self.decoded, self.new_input), feed_dict) - - for ii in range(self.batch_size): - if i % 100 == 0: - print("example: {}".format(num_loop * self.batch_size + ii)) - print("iteration: {}. loss {}".format(i, cl[ii])) - print("pred:{}".format(predictions['topk_decoded'][ii, 0])) - print("targ:{}".format(trans[ii].lower())) - - if i % 10 == 0: - if predictions['topk_decoded'][ii, 0] == trans[ii].lower(): - print("-------------------------------True--------------------------") - rescale = sess.run(self.rescale) - # update rescale - if i % 10 == 0: - if rescale[ii] > np.max(np.abs(d[ii])): - rescale[ii] = np.max(np.abs(d[ii])) - rescale[ii] *= .8 - print("Iteration i=%d, worked ii=%d celoss=%f bound=%f"%(i, ii, cl[ii], rescale[ii])) - sess.run(tf.assign(self.rescale, rescale)) - - # save the best adversarial example - final_adv[ii] = new_input[ii] - final_perturb[ii] = apply_delta[ii] - print("Stage 1: save the example at iteration i=%d example ii=%d celoss=%f bound=%f" % (i, ii, cl[ii], rescale[ii])) - - # in case no final_delta return - if (i == MAX-1 and final_adv[ii] is None): - final_adv[ii] = new_input[ii] - final_perturb[ii] = apply_delta[ii] - - if i % 10 == 0: - print("ten iterations take around {} ".format(clock)) - clock = 0 - - clock += time.time() - now - - return final_adv, final_perturb - - def attack_stage2(self, audios, trans, adv, rescales, maxlen, sample_rate, masks, masks_freq, num_loop, data, lengths): - sess = self.sess - # initialize and load the pretrained model - sess.run(tf.initializers.global_variables()) - saver = tf.train.Saver([x for x in tf.global_variables() if x.name.startswith("librispeech")]) - saver.restore(sess, FLAGS.checkpoint) - - # reassign the variables - sess.run(tf.assign(self.delta_large, adv)) - sess.run(tf.assign(self.rescale, rescales)) - - noise = np.zeros(audios.shape) - rir = Readrir() - feed_dict = {self.input_tf: audios, - self.tgt_tf: trans, - self.sample_rate_tf: sample_rate, - self.mask: masks, - self.mask_freq: masks_freq, - self.noise: noise, - self.maxlen: maxlen, - self.lengths: lengths, - self.rir: rir, - self.lr: self.lr_stage2} - losses, predictions = sess.run((self.celoss, self.decoded), feed_dict) - - # show the initial predictions - for i in range(self.batch_size): - print("example: {}, loss: {}".format(num_loop * self.batch_size + i, losses[i])) - print("pred:{}".format(predictions['topk_decoded'][i, 0])) - print("targ:{}".format(trans[i].lower())) - print("true: {}".format(data[1, i].lower())) - - - # We'll make a bunch of iterations of gradient descent here - now = time.time() - MAX = self.num_iter_stage2 - loss_th = [np.inf] * self.batch_size - final_adv = [None] * self.batch_size - final_perturb = [None] * self.batch_size - num_counters = [FLAGS.num_counter] * self.batch_size - num_rooms = [FLAGS.num_rooms] * self.batch_size - clock = 0 - - for i in range(1, MAX + 1): - now = time.time() - - rir = Readrir() - feed_dict = {self.input_tf: audios, - self.tgt_tf: trans, - self.sample_rate_tf: sample_rate, - self.mask: masks, - self.mask_freq: masks_freq, - self.noise: noise, - self.maxlen: maxlen, - self.lengths: lengths, - self.rir: rir, - self.lr: self.lr_stage2} - losses, predictions = sess.run((self.celoss, self.decoded), feed_dict) - - # Actually do the optimization - sess.run(self.train1, feed_dict) - if i % 10 == 0: - d, apply_delta, cl, predictions, new_input = sess.run((self.delta, self.apply_delta, self.celoss, self.decoded, self.new_input), feed_dict) - - for ii in range(self.batch_size): - if i % 100 == 0: - print("example: {}".format(num_loop * self.batch_size + ii)) - print("iteration: {}. loss {}".format(i, cl[ii])) - print("pred:{}".format(predictions['topk_decoded'][ii, 0])) - print("targ:{}".format(trans[ii].lower())) - - sum_counter = 0 - if i % 10 == 0: - for counter in range(num_rooms[ii]): - if predictions['topk_decoded'][ii, 0] == trans[ii].lower(): - sum_counter += 1 - print("succeed %d times for example %d" % (sum_counter, ii)) - rir = Readrir() - feed_dict = {self.input_tf: audios, - self.tgt_tf: trans, - self.sample_rate_tf: sample_rate, - self.mask: masks, - self.mask_freq: masks_freq, - self.noise: noise, - self.maxlen: maxlen, - self.lengths: lengths, - self.rir: rir, - self.lr: self.lr_stage2} - predictions = sess.run(self.decoded, feed_dict) - - if sum_counter == num_counters[ii]: - print("-------------------------------True--------------------------") - print(" The num_counter is %d for example %d" % (num_counters[ii], ii)) - num_counters[ii] += 1 - if num_counters[ii] > num_rooms[ii]: - num_rooms[ii] += 1 - # save the best adversarial example - final_adv[ii] = new_input[ii] - final_perturb[ii] = apply_delta[ii] - print("Stage 2: save the example at iteration i=%d example ii=%d celoss=%f" % (i, ii, cl[ii])) - - # in case no final_delta return - if (i == MAX-1 and final_adv[ii] is None): - final_adv[ii] = new_input[ii] - final_perturb[ii] = apply_delta[ii] - - if i % 10 == 0: - print("ten iterations take around {} ".format(clock)) - clock = 0 - clock += time.time() - now - - return final_adv, final_perturb - -def main(argv): - data = np.loadtxt(FLAGS.input, dtype=str, delimiter=",") - data = data[:, FLAGS.num_gpu * 10 : (FLAGS.num_gpu + 1) * 10] - num = len(data[0]) - batch_size = FLAGS.batch_size - num_loops = num / batch_size - assert num % batch_size == 0 - - with tf.device("/gpu:0"): - tfconf = tf.ConfigProto(allow_soft_placement=True) - with tf.Session(config=tfconf) as sess: - # set up the attack class - attack = Attack(sess, - batch_size=batch_size, - lr_stage1=FLAGS.lr_stage1, - lr_stage2=FLAGS.lr_stage2, - num_iter_stage1=FLAGS.num_iter_stage1, - num_iter_stage2=FLAGS.num_iter_stage2) - - for l in range(num_loops): - data_sub = data[:, l * batch_size:(l + 1) * batch_size] - - # stage 1 - # all the output are numpy arrays - audios, trans, maxlen, sample_rate, masks, masks_freq, lengths = ReadFromWav(data_sub, batch_size) - adv_example, perturb = attack.attack_stage1(audios, trans, maxlen, sample_rate, masks, masks_freq, l, data_sub, lengths) - - # save the adversarial examples in stage 1 that can only successfully attack one simulated room - for i in range(batch_size): - print("Final distortion for stage 1", np.max(np.abs(adv_example[i][:lengths[i]] - audios[i, :lengths[i]]))) - name, _ = data_sub[0, i].split(".") - saved_name = FLAGS.root_dir + str(name) + "_robust_speechrir_stage1.wav" - adv_example_float = adv_example[i] / 32768. - wav.write(saved_name, 16000, np.array(np.clip(adv_example_float[:lengths[i]], -2**15, 2**15-1))) - - saved_name = FLAGS.root_dir + str(name) + "_robust_perturb_stage1.wav" - perturb_float = perturb[i] / 32768. - wav.write(saved_name, 16000, np.array(np.clip(perturb_float[:lengths[i]], -2**15, 2**15-1))) - print(saved_name) - - # stage 2 - # read the adversarial examples saved in stage 1 - adv = np.zeros([batch_size, FLAGS.max_length_dataset]) - adv[:, :maxlen] = adv_example - audios - rescales = np.max(np.abs(adv), axis=1) + FLAGS.max_delta - rescales = np.expand_dims(rescales, axis=1) - - audios, trans, maxlen, sample_rate, masks, masks_freq, lengths = ReadFromWav(data_sub, batch_size) - adv_example, perturb = attack.attack_stage2(audios, trans, adv, rescales, maxlen, sample_rate, masks, masks_freq, l, data_sub, lengths) - - # save the adversarial examples in stage 2 that can successfully attack a set of simulated rooms - for i in range(batch_size): - print("Final distortion for stage 2", np.max(np.abs(adv_example[i][:lengths[i]] - audios[i, :lengths[i]]))) - name, _ = data_sub[0, i].split(".") - saved_name = FLAGS.root_dir + str(name) + "_robust_speechrir_stage2.wav" - adv_example_float = adv_example[i] / 32768. - wav.write(saved_name, 16000, np.array(np.clip(adv_example_float[:lengths[i]], -2**15, 2**15-1))) - - saved_name = FLAGS.root_dir + str(name) + "_robust_perturb_stage2.wav" - perturb_float = perturb[i] / 32768. - wav.write(saved_name, 16000, np.array(np.clip(perturb_float[:lengths[i]], -2**15, 2**15-1))) - print(saved_name) - - -if __name__ == '__main__': - app.run(main) - - diff --git a/examples/adversarial_asr/room_simulator.py b/examples/adversarial_asr/room_simulator.py deleted file mode 100644 index 460452490..000000000 --- a/examples/adversarial_asr/room_simulator.py +++ /dev/null @@ -1,95 +0,0 @@ -import numpy as np -import matplotlib.pyplot as plt -from scipy.io import wavfile -from scipy.signal import fftconvolve -import pyroomacoustics as pra -import random -import pickle - -def clip(signal, high, low): - ''' - Clip a signal from above at high and from below at low. - ''' - s = signal.copy() - - s[np.where(s > high)] = high - s[np.where(s < low)] = low - - return s - -def normalize(signal, bits=None): - ''' - normalize to be in a given range. - ''' - - s = signal.copy() - s /= np.abs(s).max() - - # if one wants to scale for bits allocated - if bits is not None: - s *= 2 ** (bits - 1) - 1 - s = clip(s, 2 ** (bits - 1) - 1, -2 ** (bits - 1)) - - return s - -# name of the rir -data = np.loadtxt("./read_data.txt", dtype=str, delimiter=",") -name = data[0, 0] -name_sub, _ = name.split('.') - -# read one audio -fs, signal = wavfile.read(name) - -temp = 0 -room_settings = [] -# set the number of room reverberations that you want to create -num_rooms = 1500 - -for i in range(num_rooms): - print("Saved room reverberation: " + str(i)) - width = random.randint(3, 5) - length = random.randint(4, 6) - height = random.randint(2, 4) - - room_dim = [width, length, height] - x_source = random.randint(0, width*10)/10. - y_source = random.randint(0, length*10)/10. - z_source = random.randint(0, height*10)/10. - - x_mic = random.randint(0, width*10)/10. - y_mic = random.randint(0, length*10)/10. - z_mic = random.randint(0, height*10)/10. - - source = [x_source, y_source, z_source] - microphone = np.array([[x_mic], [y_mic], [z_mic]]) - - room_setting = [width, length, height, x_source, y_source, z_source, x_mic, y_mic, z_mic] - - if room_setting not in room_settings: - temp += 1 - - room_settings.append(room_setting) - max_order =100 - - # set max_order to a low value for a quick (but less accurate) RIR - room = pra.ShoeBox(room_dim, fs=fs, max_order=max_order, absorption=0.2) - - # add source and set the signal to WAV file content - room.add_source(source, signal=signal) - - # add two-microphone array - room.add_microphone_array(pra.MicrophoneArray(microphone, room.fs)) - - # compute image sources - room.image_source_model(use_libroom=True) - - room.compute_rir() - rir = room.rir[0][0] - - # save the room reverberations - wavfile.write(name_sub +"_rir_" + str(temp) + '.wav', 16000, rir) - -with open('room_setting.data', 'wb') as f: - pickle.dump(room_settings, f) - - diff --git a/examples/adversarial_asr/test_robust_adv.py b/examples/adversarial_asr/test_robust_adv.py deleted file mode 100644 index 66ddc7e11..000000000 --- a/examples/adversarial_asr/test_robust_adv.py +++ /dev/null @@ -1,189 +0,0 @@ -import tensorflow as tf -from lingvo import model_imports -from lingvo import model_registry -import numpy as np -import scipy.io.wavfile as wav -import generate_masking_threshold as generate_mask -from tool import create_features, create_inputs, create_speech_rir -import time -from lingvo.core import cluster_factory -from absl import flags -from absl import app - -flags.DEFINE_string("root_dir", "./", "location of Librispeech") -flags.DEFINE_string('input', 'read_data.txt', - 'the text file saved the dir of audios and the corresponding original and targeted transcriptions') -flags.DEFINE_string('rir_dir', 'LibriSpeech/test-clean/3575/170457/3575-170457-0013', - 'directory of generated room reverberations') -flags.DEFINE_string('checkpoint', './model/ckpt-00908156', - 'location of checkpoint') - -flags.DEFINE_integer('batch_size', '5', - 'batch_size to do the testing') -flags.DEFINE_string('stage', 'stage2', 'which step to test') -flags.DEFINE_boolean('adv', 'True', 'to test adversarial examples or clean examples') -flags.DEFINE_integer('num_test_rooms', '100', - 'batch_size to do the testing') -flags.DEFINE_integer('num_train_rooms', '1000', - 'batch_size to do the testing') - -FLAGS = flags.FLAGS - -def Read_input(data, batch_size): - """ - Returns: - audios_np: a numpy array of size (batch_size, max_length) in float - sample_rate: a numpy array - trans: an array includes the targeted transcriptions (batch_size,) - """ - audios = [] - lengths = [] - - for i in range(batch_size): - name, _ = data[0,i].split(".") - - if FLAGS.adv: - sample_rate_np, delta = wav.read("./" + str(name) + "_robust_perturb_" + FLAGS.stage + ".wav") - _, audio_orig = wav.read("./" + str(name) + ".wav") - if max(delta) < 1: - delta = delta * 32768 - audio_np = audio_orig + delta - else: - sample_rate_np, audio_np = wav.read("./" + str(name) + ".wav") - - length = len(audio_np) - - audios.append(audio_np) - lengths.append(length) - - max_length = max(lengths) - masks = np.zeros([batch_size, max_length]) - lengths_freq = (np.array(lengths) // 2 + 1) // 240 * 3 - max_length_freq = max(lengths_freq) - masks_freq = np.zeros([batch_size, max_length_freq, 80]) - - # combine the audios into one array - audios_np = np.zeros([batch_size, max_length]) - - for i in range(batch_size): - audios_np[i, :lengths[i]] = audios[i] - masks[i, :lengths[i]] = 1 - masks_freq[i, :lengths_freq[i], :] = 1 - - audios_np = audios_np.astype(float) - - if FLAGS.adv: - trans = data[2, :] - else: - trans = data[1, :] - - lengths = np.array(lengths).astype(np.int32) - - return audios_np, sample_rate_np, trans, masks_freq, lengths, max_length, masks - - -def Readrir(num_room): - ''' - Return: - rir: a numpy array of the room reverberation - (make sure the test rooms are different from training rooms) - - ''' - index = num_room + FLAGS.num_train_rooms + 1 - _, rir = wav.read(FLAGS.root_dir + FLAGS.rir_dir + "_rir_" + str(index) + ".wav") - return rir - -def main(argv): - data = np.loadtxt(FLAGS.input, dtype=str, delimiter=",") - # calculate the number of loops to run the test - num = len(data[0]) - batch_size = FLAGS.batch_size - num_loops = num / batch_size - assert num % batch_size == 0 - - with tf.device("/gpu:0"): - tf.set_random_seed(1234) - tfconf = tf.ConfigProto(allow_soft_placement=True) - with tf.Session(config=tfconf) as sess: - params = model_registry.GetParams('asr.librispeech.Librispeech960Wpm', 'Test') - params.cluster.worker.gpus_per_replica = 1 - cluster = cluster_factory.Cluster(params.cluster) - with cluster, tf.device(cluster.GetPlacer()): - params.vn.global_vn = False - params.random_seed = 1234 - params.is_eval = True - model = params.cls(params) - task = model.GetTask() - saver = tf.train.Saver() - saver.restore(sess, FLAGS.checkpoint) - - # define the placeholders - input_tf = tf.placeholder(tf.float32, shape=[batch_size, None]) - tgt_tf = tf.placeholder(tf.string) - sample_rate_tf = tf.placeholder(tf.int32) - mask_tf = tf.placeholder(tf.float32, shape=[batch_size, None, 80]) - rir_tf = tf.placeholder(tf.float32) - lengths = tf.placeholder(np.int32, shape=[batch_size,]) - maxlen = tf.placeholder(np.int32) - mask = tf.placeholder(dtype=np.float32, shape=[batch_size, None]) - - # generate the features and inputs - new_input = create_speech_rir(input_tf, rir_tf, lengths, maxlen, batch_size) * mask - features = create_features(new_input, sample_rate_tf, mask_tf) - shape = tf.shape(features) - inputs = create_inputs(model, features, tgt_tf, batch_size, mask_tf) - - # loss - metrics = task.FPropDefaultTheta(inputs) - loss = tf.get_collection("per_loss")[0] - - # prediction - decoded_outputs = task.Decode(inputs) - dec_metrics_dict = task.CreateDecoderMetrics() - - success_rates = [] - for num_room in range(FLAGS.num_test_rooms): - correct = 0 - rir = Readrir(num_room) - - for l in range(num_loops): - data_sub = data[:, l * batch_size:(l + 1) * batch_size] - audios_np, sample_rate, tgt_np, mask_freq, lengths_np, max_len, masks = Read_input(data_sub, batch_size) - - feed_dict={input_tf: audios_np, - sample_rate_tf: sample_rate, - tgt_tf: tgt_np, - mask_tf: mask_freq, - rir_tf: rir, - lengths: lengths_np, - maxlen: max_len, - mask: masks} - - losses = sess.run(loss, feed_dict) - predictions = sess.run(decoded_outputs, feed_dict) - - task.PostProcessDecodeOut(predictions, dec_metrics_dict) - wer_value = dec_metrics_dict['wer'].value * 100. - - for i in range(batch_size): - print("example: {}, loss_ce: {}".format(l*batch_size + i, losses[i])) - print("pred:{}".format(predictions['topk_decoded'][i, 0])) - print("targ:{}".format(tgt_np[i].lower())) - print("true: {}".format(data_sub[1, i].lower())) - - if predictions['topk_decoded'][i,0] == tgt_np[i].lower(): - correct += 1 - - print("--------------------------------") - print("Now, the WER is: {0:.2f}%".format(wer_value)) - - print("num of examples succeed for room {}: {}".format(num_room, correct)) - success_rate = correct / float(num) * 100 - print("success rate for room {}: {}%".format(num_room, success_rate)) - - success_rates.append(success_rate) - success_ave = float(sum(success_rates))/len(success_rates) - print("success rate overall: {}%".format(success_ave)) - -if __name__ == '__main__': - app.run(main) diff --git a/examples/facenet_adversarial_faces/facenet_fgsm.py b/examples/facenet_adversarial_faces/facenet_fgsm.py deleted file mode 100644 index e58872f76..000000000 --- a/examples/facenet_adversarial_faces/facenet_fgsm.py +++ /dev/null @@ -1,137 +0,0 @@ -import facenet - -import tensorflow as tf -import numpy as np -from cleverhans.model import Model -from cleverhans.attacks import FastGradientMethod - -import set_loader - - -class InceptionResnetV1Model(Model): - model_path = "models/facenet/20180402-114759/20180402-114759.pb" - - def __init__(self): - super(InceptionResnetV1Model, self).__init__(scope='model') - - # Load Facenet CNN - facenet.load_model(self.model_path) - # Save input and output tensors references - graph = tf.get_default_graph() - self.face_input = graph.get_tensor_by_name("input:0") - self.embedding_output = graph.get_tensor_by_name("embeddings:0") - - def convert_to_classifier(self): - # Create victim_embedding placeholder - self.victim_embedding_input = tf.placeholder( - tf.float32, - shape=(None, 512)) - - # Squared Euclidean Distance between embeddings - distance = tf.reduce_sum( - tf.square(self.embedding_output - self.victim_embedding_input), - axis=1) - - # Convert distance to a softmax vector - # 0.99 out of 4 is the distance threshold for the Facenet CNN - threshold = 0.99 - score = tf.where( - distance > threshold, - 0.5 + ((distance - threshold) * 0.5) / (4.0 - threshold), - 0.5 * distance / threshold) - reverse_score = 1.0 - score - self.softmax_output = tf.transpose(tf.stack([reverse_score, score])) - - # Save softmax layer - self.layer_names = [] - self.layers = [] - self.layers.append(self.softmax_output) - self.layer_names.append('logits') - - def fprop(self, x, set_ref=False): - return dict(zip(self.layer_names, self.layers)) - - -with tf.Graph().as_default(): - with tf.Session() as sess: - # Load model - model = InceptionResnetV1Model() - # Convert to classifier - model.convert_to_classifier() - - # Load pairs of faces and their labels in one-hot encoding - size = 100 - faces1, faces2, labels = set_loader.load_testset(size) - - # Create victims' embeddings using Facenet itself - graph = tf.get_default_graph() - phase_train_placeholder = graph.get_tensor_by_name("phase_train:0") - feed_dict = {model.face_input: faces2, - phase_train_placeholder: False} - victims_embeddings = sess.run( - model.embedding_output, feed_dict=feed_dict) - - # Define FGSM for the model - steps = 1 - eps = 0.01 - alpha = eps / steps - fgsm = FastGradientMethod(model) - fgsm_params = {'eps': alpha, - 'clip_min': 0., - 'clip_max': 1.} - adv_x = fgsm.generate(model.face_input, **fgsm_params) - - # Run FGSM - adv = faces1 - for i in range(steps): - print("FGSM step " + str(i + 1)) - feed_dict = {model.face_input: adv, - model.victim_embedding_input: victims_embeddings, - phase_train_placeholder: False} - adv = sess.run(adv_x, feed_dict=feed_dict) - - # Test accuracy of the model - batch_size = graph.get_tensor_by_name("batch_size:0") - - feed_dict = {model.face_input: faces1, - model.victim_embedding_input: victims_embeddings, - phase_train_placeholder: False, - batch_size: 64} - real_labels = sess.run(model.softmax_output, feed_dict=feed_dict) - - accuracy = np.mean( - (np.argmax(labels, axis=-1)) == (np.argmax(real_labels, axis=-1)) - ) - print('Accuracy: ' + str(accuracy * 100) + '%') - - # Test accuracy against adversarial examples - feed_dict = {model.face_input: adv, - model.victim_embedding_input: victims_embeddings, - phase_train_placeholder: False, - batch_size: 64} - adversarial_labels = sess.run( - model.softmax_output, feed_dict=feed_dict) - - same_faces_index = np.where((np.argmax(labels, axis=-1) == 0)) - different_faces_index = np.where((np.argmax(labels, axis=-1) == 1)) - - accuracy = np.mean( - (np.argmax(labels[same_faces_index], axis=-1)) == - (np.argmax(adversarial_labels[same_faces_index], axis=-1)) - ) - print('Accuracy against adversarial examples for ' - + 'same person faces (dodging): ' - + str(accuracy * 100) - + '%') - - accuracy = np.mean( - (np.argmax(labels[different_faces_index], axis=-1)) == ( - np.argmax(adversarial_labels[different_faces_index], axis=-1)) - ) - print('Accuracy against adversarial examples for ' - + 'different people faces (impersonation): ' - + str(accuracy * 100) - + '%') - - # Save images to folder - set_loader.save_images(adv, faces1, faces2, size) \ No newline at end of file diff --git a/examples/facenet_adversarial_faces/set_loader.py b/examples/facenet_adversarial_faces/set_loader.py deleted file mode 100644 index ff9e45572..000000000 --- a/examples/facenet_adversarial_faces/set_loader.py +++ /dev/null @@ -1,72 +0,0 @@ -import shutil -import os - -import lfw -import facenet - -import numpy as np - -from PIL import Image - - -pairs_path = "datasets/lfw/pairs.txt" -testset_path = "datasets/lfw/lfw_mtcnnpy_160" -image_size = 160 - - -def save_images(adv, faces1, faces2, size): - save_images_to_folder(adv, size, 'images/adversarial/') - save_images_to_folder(0.5 + (adv - faces1), size, 'images/noise/') - save_images_to_folder(faces1, size, 'images/faces1/') - save_images_to_folder(faces2, size, 'images/faces2/') - - -def save_images_to_folder(images, size, path): - if os.path.isdir(path): - shutil.rmtree(path) - os.makedirs(path) - - for index in range(images.shape[0]): - if index < size: - image_array = (np.reshape(images[index], (160, 160, 3)) - * 255).astype(np.uint8) - Image.fromarray(image_array, 'RGB').save(path + str(index) + '.png') - - -def load_testset(size): - # Load images paths and labels - pairs = lfw.read_pairs(pairs_path) - paths, labels = lfw.get_paths(testset_path, pairs) - - # Random choice - permutation = np.random.choice(len(labels), size, replace=False) - paths_batch_1 = [] - paths_batch_2 = [] - - for index in permutation: - paths_batch_1.append(paths[index * 2]) - paths_batch_2.append(paths[index * 2 + 1]) - - labels = np.asarray(labels)[permutation] - paths_batch_1 = np.asarray(paths_batch_1) - paths_batch_2 = np.asarray(paths_batch_2) - - # Load images - faces1 = facenet.load_data(paths_batch_1, False, False, image_size) - faces2 = facenet.load_data(paths_batch_2, False, False, image_size) - - # Change pixel values to 0 to 1 values - min_pixel = min(np.min(faces1), np.min(faces2)) - max_pixel = max(np.max(faces1), np.max(faces2)) - faces1 = (faces1 - min_pixel) / (max_pixel - min_pixel) - faces2 = (faces2 - min_pixel) / (max_pixel - min_pixel) - - # Convert labels to one-hot vectors - onehot_labels = [] - for index in range(len(labels)): - if labels[index]: - onehot_labels.append([1, 0]) - else: - onehot_labels.append([0, 1]) - - return faces1, faces2, np.array(onehot_labels) diff --git a/examples/imagenet_featadvs/model.py b/examples/imagenet_featadvs/model.py deleted file mode 100644 index ac599e850..000000000 --- a/examples/imagenet_featadvs/model.py +++ /dev/null @@ -1,38 +0,0 @@ -# pylint: disable=missing-docstring -import functools -import tensorflow as tf - -from cleverhans.initializers import HeReLuNormalInitializer -from cleverhans.model import Model - - -class ModelImageNetCNN(Model): - def __init__(self, scope, nb_classes=1000, **kwargs): - del kwargs - Model.__init__(self, scope, nb_classes, locals()) - - def fprop(self, x, **kwargs): - del kwargs - my_conv = functools.partial(tf.layers.conv2d, - kernel_size=3, - strides=2, - padding='valid', - activation=tf.nn.relu, - kernel_initializer=HeReLuNormalInitializer) - my_dense = functools.partial( - tf.layers.dense, kernel_initializer=HeReLuNormalInitializer) - - with tf.variable_scope(self.scope, reuse=tf.AUTO_REUSE): - for depth in [96, 256, 384, 384, 256]: - x = my_conv(x, depth) - y = tf.layers.flatten(x) - y = my_dense(y, 4096, tf.nn.relu) - y = fc7 = my_dense(y, 4096, tf.nn.relu) - y = my_dense(y, 1000) - return {'fc7': fc7, - self.O_LOGITS: y, - self.O_PROBS: tf.nn.softmax(logits=y)} - - -def make_imagenet_cnn(input_shape=(None, 224, 224, 3)): - return ModelImageNetCNN('imagenet') diff --git a/examples/madry_lab_challenges/cifar10/attack_model.py b/examples/madry_lab_challenges/cifar10/attack_model.py deleted file mode 100644 index 09fdd72fb..000000000 --- a/examples/madry_lab_challenges/cifar10/attack_model.py +++ /dev/null @@ -1,130 +0,0 @@ -"""Runs CleverHans attacks on the Madry Lab CIFAR-10 challenge model - -""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import os -import sys -import time -import numpy as np -import logging - -import tensorflow as tf -from tensorflow.python.platform import app, flags -from cleverhans.utils import set_log_level, to_categorical -from cleverhans.utils_tf import model_eval -import cifar10_input - - -FLAGS = flags.FLAGS - - -def main(argv): - - model_file = tf.train.latest_checkpoint(FLAGS.checkpoint_dir) - - if model_file is None: - print('No model found') - sys.exit() - - cifar = cifar10_input.CIFAR10Data(FLAGS.dataset_dir) - - nb_classes = 10 - X_test = cifar.eval_data.xs - Y_test = to_categorical(cifar.eval_data.ys, nb_classes) - assert Y_test.shape[1] == 10. - - set_log_level(logging.DEBUG) - - with tf.Session() as sess: - - x = tf.placeholder(tf.float32, shape=(None, 32, 32, 3)) - y = tf.placeholder(tf.float32, shape=(None, 10)) - - from cleverhans.model_zoo.madry_lab_challenges.cifar10_model import make_wresnet - model = make_wresnet() - - saver = tf.train.Saver() - - # Restore the checkpoint - saver.restore(sess, model_file) - - nb_samples = FLAGS.nb_samples - - attack_params = {'batch_size': FLAGS.batch_size, - 'clip_min': 0., 'clip_max': 255.} - - if FLAGS.attack_type == 'cwl2': - from cleverhans.attacks import CarliniWagnerL2 - attacker = CarliniWagnerL2(model, sess=sess) - attack_params.update({'binary_search_steps': 1, - 'max_iterations': 100, - 'learning_rate': 0.1, - 'initial_const': 10, - 'batch_size': 10 - }) - - else: # eps and eps_iter in range 0-255 - attack_params.update({'eps': 8, 'ord': np.inf}) - if FLAGS.attack_type == 'fgsm': - from cleverhans.attacks import FastGradientMethod - attacker = FastGradientMethod(model, sess=sess) - - elif FLAGS.attack_type == 'pgd': - attack_params.update({'eps_iter': 2, 'nb_iter': 20}) - from cleverhans.attacks import MadryEtAl - attacker = MadryEtAl(model, sess=sess) - - eval_par = {'batch_size': FLAGS.batch_size} - - if FLAGS.sweep: - max_eps = 16 - epsilons = np.linspace(1, max_eps, max_eps) - for e in epsilons: - t1 = time.time() - attack_params.update({'eps': e}) - x_adv = attacker.generate(x, **attack_params) - preds_adv = model.get_probs(x_adv) - acc = model_eval(sess, x, y, preds_adv, X_test[ - :nb_samples], Y_test[:nb_samples], args=eval_par) - print('Epsilon %.2f, accuracy on adversarial' % e, - 'examples %0.4f\n' % acc) - t2 = time.time() - else: - t1 = time.time() - x_adv = attacker.generate(x, **attack_params) - preds_adv = model.get_probs(x_adv) - acc = model_eval(sess, x, y, preds_adv, X_test[ - :nb_samples], Y_test[:nb_samples], args=eval_par) - t2 = time.time() - print('Test accuracy on adversarial examples %0.4f\n' % acc) - print("Took", t2 - t1, "seconds") - - -if __name__ == '__main__': - - if "CIFAR10_CHALLENGE_DIR" in os.environ: - cifar10_root = os.environ['CIFAR10_CHALLENGE_DIR'] - default_ckpt_dir = os.path.join(cifar10_root, 'models/adv_trained') - default_data_dir = os.path.join(cifar10_root, 'cifar10_data') - - flags.DEFINE_integer('batch_size', 100, "Batch size") - - flags.DEFINE_integer('nb_samples', 1000, "Number of samples to test") - - flags.DEFINE_string('attack_type', 'fgsm', ("Attack type: 'fgsm'->'fast " - "gradient sign method', " - "'pgd'->'projected " - "gradient descent', 'cwl2'->" - "'Carlini & Wagner L2'")) - flags.DEFINE_string('checkpoint_dir', default_ckpt_dir, - 'Checkpoint directory to load') - - flags.DEFINE_string('dataset_dir', default_data_dir, 'Dataset directory') - - flags.DEFINE_bool('sweep', False, 'Sweep epsilon or single epsilon?') - - app.run(main) diff --git a/examples/madry_lab_challenges/mnist/attack_model.py b/examples/madry_lab_challenges/mnist/attack_model.py deleted file mode 100644 index 0f7cc883f..000000000 --- a/examples/madry_lab_challenges/mnist/attack_model.py +++ /dev/null @@ -1,99 +0,0 @@ -"""Runs CleverHans attacks on the Madry Lab MNIST challenge model - -""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import os -import time - -import tensorflow as tf -from tensorflow.examples.tutorials.mnist import input_data -from tensorflow.python.platform import app -from tensorflow.python.platform import flags -from madry_mnist_model import MadryMNIST -from cleverhans.utils_tf import model_eval -from cleverhans.attacks import FastGradientMethod -from cleverhans.attacks import BasicIterativeMethod -from cleverhans.utils_mnist import data_mnist - - -FLAGS = flags.FLAGS - - -def main(argv): - checkpoint = tf.train.latest_checkpoint(FLAGS.checkpoint_dir) - - if checkpoint is None: - raise ValueError("Couldn't find latest checkpoint in " + - FLAGS.checkpoint_dir) - - train_start = 0 - train_end = 60000 - test_start = 0 - test_end = 10000 - X_train, Y_train, X_test, Y_test = data_mnist(train_start=train_start, - train_end=train_end, - test_start=test_start, - test_end=test_end) - - assert Y_train.shape[1] == 10 - - # NOTE: for compatibility with Madry Lab downloadable checkpoints, - # we cannot enclose this in a scope or do anything else that would - # change the automatic naming of the variables. - model = MadryMNIST() - - x_input = tf.placeholder(tf.float32, shape=[None, 784]) - x_image = tf.placeholder(tf.float32, shape=[None, 28, 28, 1]) - y = tf.placeholder(tf.float32, shape=[None, 10]) - - if FLAGS.attack_type == 'fgsm': - fgsm = FastGradientMethod(model) - fgsm_params = {'eps': 0.3, 'clip_min': 0., 'clip_max': 1.} - adv_x = fgsm.generate(x_image, **fgsm_params) - elif FLAGS.attack_type == 'bim': - bim = BasicIterativeMethod(model) - bim_params = {'eps': 0.3, 'clip_min': 0., 'clip_max': 1., - 'nb_iter': 50, - 'eps_iter': .01} - adv_x = bim.generate(x_image, **bim_params) - else: - raise ValueError(FLAGS.attack_type) - preds_adv = model.get_probs(adv_x) - - saver = tf.train.Saver() - - with tf.Session() as sess: - # Restore the checkpoint - saver.restore(sess, checkpoint) - - # Evaluate the accuracy of the MNIST model on adversarial examples - eval_par = {'batch_size': FLAGS.batch_size} - t1 = time.time() - acc = model_eval( - sess, x_image, y, preds_adv, X_test, Y_test, args=eval_par) - t2 = time.time() - print("Took", t2 - t1, "seconds") - print('Test accuracy on adversarial examples: %0.4f\n' % acc) - - -if __name__ == '__main__': - - dirs = ['models', 'adv_trained'] - if "MNIST_CHALLENGE_DIR" in os.environ: - dirs.insert(0, os.environ['MNIST_CHALLENGE_DIR']) - default_checkpoint_dir = os.path.join(*dirs) - - flags.DEFINE_integer('batch_size', 128, "batch size") - flags.DEFINE_float( - 'label_smooth', 0.1, ("Amount to subtract from correct label " - "and distribute among other labels")) - flags.DEFINE_string( - 'attack_type', 'fgsm', ("Attack type: 'fgsm'->fast gradient sign" - "method, 'bim'->'basic iterative method'")) - flags.DEFINE_string('checkpoint_dir', default_checkpoint_dir, - 'Checkpoint directory to load') - app.run(main) diff --git a/examples/madry_lab_challenges/mnist/madry_mnist_model.py b/examples/madry_lab_challenges/mnist/madry_mnist_model.py deleted file mode 100644 index 88c580c70..000000000 --- a/examples/madry_lab_challenges/mnist/madry_mnist_model.py +++ /dev/null @@ -1,92 +0,0 @@ -"""cleverhans.model.Model implementation of mnist_challenge.model.Model - -This re-implementation factors variable creation apart from forward -propagation so it is possible to run forward propagation more than once -in the same model. -""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -from collections import OrderedDict -import tensorflow as tf -from cleverhans.model import Model -from cleverhans.utils import deterministic_dict -from cleverhans.dataset import Factory, MNIST - - -class MadryMNIST(Model): - - def __init__(self, nb_classes=10): - # NOTE: for compatibility with Madry Lab downloadable checkpoints, - # we cannot use scopes, give these variables names, etc. - self.W_conv1 = self._weight_variable([5, 5, 1, 32]) - self.b_conv1 = self._bias_variable([32]) - self.W_conv2 = self._weight_variable([5, 5, 32, 64]) - self.b_conv2 = self._bias_variable([64]) - self.W_fc1 = self._weight_variable([7 * 7 * 64, 1024]) - self.b_fc1 = self._bias_variable([1024]) - self.W_fc2 = self._weight_variable([1024, nb_classes]) - self.b_fc2 = self._bias_variable([nb_classes]) - Model.__init__(self, '', nb_classes, {}) - self.dataset_factory = Factory(MNIST, {"center": False}) - - def get_params(self): - return [ - self.W_conv1, - self.b_conv1, - self.W_conv2, - self.b_conv2, - self.W_fc1, - self.b_fc1, - self.W_fc2, - self.b_fc2, - ] - - def fprop(self, x): - - output = OrderedDict() - # first convolutional layer - h_conv1 = tf.nn.relu(self._conv2d(x, self.W_conv1) + self.b_conv1) - h_pool1 = self._max_pool_2x2(h_conv1) - - # second convolutional layer - h_conv2 = tf.nn.relu( - self._conv2d(h_pool1, self.W_conv2) + self.b_conv2) - h_pool2 = self._max_pool_2x2(h_conv2) - - # first fully connected layer - - h_pool2_flat = tf.reshape(h_pool2, [-1, 7 * 7 * 64]) - h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, self.W_fc1) + self.b_fc1) - - # output layer - logits = tf.matmul(h_fc1, self.W_fc2) + self.b_fc2 - - output = deterministic_dict(locals()) - del output["self"] - output[self.O_PROBS] = tf.nn.softmax(logits=logits) - - return output - - @staticmethod - def _weight_variable(shape): - initial = tf.truncated_normal(shape, stddev=0.1) - return tf.Variable(initial) - - @staticmethod - def _bias_variable(shape): - initial = tf.constant(0.1, shape=shape) - return tf.Variable(initial) - - @staticmethod - def _conv2d(x, W): - return tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding='SAME') - - @staticmethod - def _max_pool_2x2(x): - return tf.nn.max_pool(x, - ksize=[1, 2, 2, 1], - strides=[1, 2, 2, 1], - padding='SAME') diff --git a/examples/multigpu_advtrain/attacks_multigpu.py b/examples/multigpu_advtrain/attacks_multigpu.py deleted file mode 100644 index 22c16a76d..000000000 --- a/examples/multigpu_advtrain/attacks_multigpu.py +++ /dev/null @@ -1,149 +0,0 @@ -# pylint: disable=missing-docstring -from collections import OrderedDict - -import tensorflow as tf - -from cleverhans.attacks import MadryEtAl -from cleverhans.utils_tf import clip_eta - -from model import clone_variable - - -class MadryEtAlMultiGPU(MadryEtAl): - - """ - A multi-GPU version of the Projected Gradient Descent Attack - (Madry et al. 2017). - Paper link: https://arxiv.org/pdf/1706.06083.pdf - - This attack is designed to run on multiple GPUs for generating adversarial - examples. - Comparing to data parallelism, using this parallelization we can get - very close to optimal n times speed up using n GPUs. The current - implementation gets close to 6x speed up on 8 GPUs. - """ - - def __init__(self, *args, **kwargs): - """ - Create a MadryEtAlMultiGPU instance. - """ - super(MadryEtAlMultiGPU, self).__init__(*args, **kwargs) - self.structural_kwargs += ['ngpu'] - - def get_or_guess_labels(self, x, kwargs): - device_name = '/gpu:0' - self.model.set_device(device_name) - with tf.device(device_name): - with tf.variable_scope('model_pred'): - ret = super(MadryEtAlMultiGPU, self).get_or_guess_labels( - x, kwargs) - return ret - - def attack(self, x, y_p, **kwargs): - """ - This method creates a symoblic graph of the MadryEtAl attack on - multiple GPUs. The graph is created on the first n GPUs. - - Stop gradient is needed to get the speed-up. This prevents us from - being able to back-prop through the attack. - - :param x: A tensor with the input image. - :param y_p: Ground truth label or predicted label. - :return: Two lists containing the input and output tensors of each GPU. - """ - inputs = [] - outputs = [] - - # Create the initial random perturbation - device_name = '/gpu:0' - self.model.set_device(device_name) - with tf.device(device_name): - with tf.variable_scope('init_rand'): - if self.rand_init: - eta = tf.random_uniform(tf.shape(x), -self.eps, self.eps) - eta = clip_eta(eta, self.ord, self.eps) - eta = tf.stop_gradient(eta) - else: - eta = tf.zeros_like(x) - - # TODO: Break the graph only nGPU times instead of nb_iter times. - # The current implementation by the time an adversarial example is - # used for training, the weights of the model have changed nb_iter - # times. This can cause slower convergence compared to the single GPU - # adversarial training. - for i in range(self.nb_iter): - # Create the graph for i'th step of attack - inputs += [OrderedDict()] - outputs += [OrderedDict()] - device_name = x.device - self.model.set_device(device_name) - with tf.device(device_name): - with tf.variable_scope('step%d' % i): - if i > 0: - # Clone the variables to separate the graph of 2 GPUs - x = clone_variable('x', x) - y_p = clone_variable('y_p', y_p) - eta = clone_variable('eta', eta) - - inputs[i]['x'] = x - inputs[i]['y_p'] = y_p - outputs[i]['x'] = x - outputs[i]['y_p'] = y_p - inputs[i]['eta'] = eta - - eta = self.attack_single_step(x, eta, y_p) - - if i < self.nb_iter-1: - outputs[i]['eta'] = eta - else: - # adv_x, not eta is the output of the last step - adv_x = x + eta - if (self.clip_min is not None and self.clip_max is not None): - adv_x = tf.clip_by_value(adv_x, self.clip_min, self.clip_max) - adv_x = tf.stop_gradient(adv_x, name='adv_x') - outputs[i]['adv_x'] = adv_x - - return inputs, outputs - - def generate_np(self, x_val, **kwargs): - """ - Facilitates testing this attack. - """ - _, feedable, _feedable_types, hash_key = self.construct_variables(kwargs) - - if hash_key not in self.graphs: - with tf.variable_scope(None, 'attack_%d' % len(self.graphs)): - # x is a special placeholder we always want to have - with tf.device('/gpu:0'): - x = tf.placeholder(tf.float32, shape=x_val.shape, name='x') - - inputs, outputs = self.generate(x, **kwargs) - - from runner import RunnerMultiGPU - runner = RunnerMultiGPU(inputs, outputs, sess=self.sess) - self.graphs[hash_key] = runner - - runner = self.graphs[hash_key] - feed_dict = {'x': x_val} - for name in feedable: - feed_dict[name] = feedable[name] - fvals = runner.run(feed_dict) - while not runner.is_finished(): - fvals = runner.run() - - return fvals['adv_x'] - - def parse_params(self, ngpu=1, **kwargs): - """ - Take in a dictionary of parameters and applies attack-specific checks - before saving them as attributes. - - Attack-specific parameters: - :param ngpu: (required int) the number of GPUs available. - :param kwargs: A dictionary of parameters for MadryEtAl attack. - """ - - return_status = super(MadryEtAlMultiGPU, self).parse_params(**kwargs) - self.ngpu = ngpu - - return return_status diff --git a/examples/multigpu_advtrain/evaluator.py b/examples/multigpu_advtrain/evaluator.py deleted file mode 100644 index c3015825b..000000000 --- a/examples/multigpu_advtrain/evaluator.py +++ /dev/null @@ -1,218 +0,0 @@ -""" -Simplifying the evaluation of a model. Multiple attacks are initialized and -run against a model at every evaluation step. -""" -import logging - -import tensorflow as tf - -from cleverhans.utils_tf import model_eval -from cleverhans.attacks import FastGradientMethod -from cleverhans.attacks import MadryEtAl - -from attacks_multigpu import MadryEtAlMultiGPU - - -def create_adv_by_name(model, x, attack_type, sess, dataset, y=None, **kwargs): - """ - Creates the symbolic graph of an adversarial example given the name of - an attack. Simplifies creating the symbolic graph of an attack by defining - dataset-specific parameters. - Dataset-specific default parameters are used unless a different value is - given in kwargs. - - :param model: an object of Model class - :param x: Symbolic input to the attack. - :param attack_type: A string that is the name of an attack. - :param sess: Tensorflow session. - :param dataset: The name of the dataset as a string to use for default - params. - :param y: (optional) a symbolic variable for the labels. - :param kwargs: (optional) additional parameters to be passed to the attack. - """ - # TODO: black box attacks - attack_names = {'FGSM': FastGradientMethod, - 'MadryEtAl': MadryEtAl, - 'MadryEtAl_y': MadryEtAl, - 'MadryEtAl_multigpu': MadryEtAlMultiGPU, - 'MadryEtAl_y_multigpu': MadryEtAlMultiGPU - } - - if attack_type not in attack_names: - raise Exception('Attack %s not defined.' % attack_type) - - attack_params_shared = { - 'mnist': {'eps': .3, 'eps_iter': 0.01, 'clip_min': 0., 'clip_max': 1., - 'nb_iter': 40}, - 'cifar10': {'eps': 8./255, 'eps_iter': 0.01, 'clip_min': 0., - 'clip_max': 1., 'nb_iter': 20} - } - - with tf.variable_scope(attack_type): - attack_class = attack_names[attack_type] - attack = attack_class(model, sess=sess) - - # Extract feedable and structural keyword arguments from kwargs - fd_kwargs = attack.feedable_kwargs.keys() + attack.structural_kwargs - params = attack_params_shared[dataset].copy() - params.update({k: v for k, v in kwargs.items() if v is not None}) - params = {k: v for k, v in params.items() if k in fd_kwargs} - - if '_y' in attack_type: - params['y'] = y - logging.info(params) - adv_x = attack.generate(x, **params) - - return adv_x - - -class Evaluator(object): - """ - This class evaluates a model against multiple attacks. - """ - - def __init__(self, sess, model, batch_size, x_pre, x, y, data, writer, - hparams=None): - """ - :param sess: Tensorflow session. - :param model: an object of Model class - :param batch_size: batch_size for evaluation. - :param x_pre: placeholder for input before preprocessing. - :param x: symbolic input to model. - :param y: symbolic variable for the label. - :param data: a tuple with training and test data in the form - (X_train, Y_train, X_test, Y_test). - :param writer: Tensorflow summary writer. - :param hparams: Flags to control the evaluation. - """ - if hparams is None: - hparams = {} - model.set_training(False) - self.preds = model.get_probs(x) - self.sess = sess - self.batch_size = batch_size - self.x_pre = x_pre - self.x = x - self.y = y - self.X_train, self.Y_train, self.X_test, self.Y_test = data - self.writer = writer - self.hparams = hparams - - # Evaluate on a fixed subsampled set of the train data - self.eval_params = {'batch_size': batch_size} - - self.epoch = 0 - - self.attack_type_train = hparams.attack_type_train - self.attack_type_test = [] - for att_type in hparams.attack_type_test.split(','): - if att_type == '': - continue - self.attack_type_test += [att_type] - self.attacks = {} - - # Initialize the attack object and graph - for att_type in self.attack_type_test: - logging.info('Intializing attack %s' % att_type) - adv_x = create_adv_by_name(model, x, att_type, sess, - dataset=hparams.dataset, y=y) - - model.set_training(False) - preds_adv = model.get_probs(adv_x) - self.attacks[att_type] = (adv_x, preds_adv) - # visualize adversarial image - tf.summary.image(att_type, adv_x, max_outputs=10) - self.sum_op = tf.summary.merge_all() - - def log_value(self, tag, val, desc=''): - """ - Log values to standard output and Tensorflow summary. - - :param tag: summary tag. - :param val: (required float or numpy array) value to be logged. - :param desc: (optional) additional description to be printed. - """ - logging.info('%s (%s): %.4f' % (desc, tag, val)) - self.summary.value.add(tag=tag, simple_value=val) - - def eval_advs(self, x, y, preds_adv, X_test, Y_test, att_type): - """ - Evaluate the accuracy of the model on adversarial examples - - :param x: symbolic input to model. - :param y: symbolic variable for the label. - :param preds_adv: symbolic variable for the prediction on an - adversarial example. - :param X_test: NumPy array of test set inputs. - :param Y_test: NumPy array of test set labels. - :param att_type: name of the attack. - """ - end = (len(X_test) // self.batch_size) * self.batch_size - - if self.hparams.fast_tests: - end = 10*self.batch_size - - acc = model_eval(self.sess, x, y, preds_adv, X_test[:end], - Y_test[:end], args=self.eval_params) - self.log_value('test_accuracy_%s' % att_type, acc, - 'Test accuracy on adversarial examples') - return acc - - def eval_multi(self, inc_epoch=True): - """ - Run the evaluation on multiple attacks. - """ - sess = self.sess - preds = self.preds - x = self.x_pre - y = self.y - X_train = self.X_train - Y_train = self.Y_train - X_test = self.X_test - Y_test = self.Y_test - writer = self.writer - - self.summary = tf.Summary() - report = {} - - # Evaluate on train set - subsample_factor = 100 - X_train_subsampled = X_train[::subsample_factor] - Y_train_subsampled = Y_train[::subsample_factor] - acc_train = model_eval(sess, x, y, preds, X_train_subsampled, - Y_train_subsampled, args=self.eval_params) - self.log_value('train_accuracy_subsampled', acc_train, - 'Clean accuracy, subsampled train') - report['train'] = acc_train - - # Evaluate on the test set - acc = model_eval(sess, x, y, preds, X_test, Y_test, - args=self.eval_params) - self.log_value('test_accuracy_natural', acc, - 'Clean accuracy, natural test') - report['test'] = acc - - # Evaluate against adversarial attacks - if self.epoch % self.hparams.eval_iters == 0: - for att_type in self.attack_type_test: - _, preds_adv = self.attacks[att_type] - acc = self.eval_advs(x, y, preds_adv, X_test, Y_test, att_type) - report[att_type] = acc - - if self.writer: - writer.add_summary(self.summary, self.epoch) - - # Add examples of adversarial examples to the summary - if self.writer and self.epoch % 20 == 0 and self.sum_op is not None: - sm_val = self.sess.run(self.sum_op, - feed_dict={x: X_test[:self.batch_size], - y: Y_test[:self.batch_size]}) - if self.writer: - writer.add_summary(sm_val) - - self.epoch += 1 if inc_epoch else 0 - - return report - - def __call__(self, **kwargs): - return self.eval_multi(**kwargs) diff --git a/examples/multigpu_advtrain/make_model.py b/examples/multigpu_advtrain/make_model.py deleted file mode 100644 index 5448fcb99..000000000 --- a/examples/multigpu_advtrain/make_model.py +++ /dev/null @@ -1,66 +0,0 @@ -# pylint: disable=missing-docstring -from model import Conv2D, ReLU, Flatten, Linear, Softmax, MLP -from model import MLPnGPU -from model import Conv2DnGPU -from model import LinearnGPU -from model import MaxPool - -from resnet_tf import ResNetTF - - -def make_basic_cnn(nb_filters=64, nb_classes=10, - input_shape=(None, 28, 28, 1)): - layers = [Conv2D(nb_filters, (8, 8), (2, 2), "SAME"), - ReLU(), - Conv2D(nb_filters * 2, (6, 6), (2, 2), "VALID"), - ReLU(), - Conv2D(nb_filters * 2, (5, 5), (1, 1), "VALID"), - ReLU(), - Flatten(), - Linear(nb_classes), - Softmax()] - - model = MLP(nb_classes, layers, input_shape) - return model - - -def make_basic_ngpu(nb_classes=10, input_shape=(None, 28, 28, 1), **kwargs): - """ - Create a multi-GPU model similar to the basic cnn in the tutorials. - """ - model = make_basic_cnn() - layers = model.layers - - model = MLPnGPU(nb_classes, layers, input_shape) - return model - - -def make_madry_ngpu(nb_classes=10, input_shape=(None, 28, 28, 1), **kwargs): - """ - Create a multi-GPU model similar to Madry et al. (arXiv:1706.06083). - """ - layers = [Conv2DnGPU(32, (5, 5), (1, 1), "SAME"), - ReLU(), - MaxPool((2, 2), (2, 2), "SAME"), - Conv2DnGPU(64, (5, 5), (1, 1), "SAME"), - ReLU(), - MaxPool((2, 2), (2, 2), "SAME"), - Flatten(), - LinearnGPU(1024), - ReLU(), - LinearnGPU(nb_classes), - Softmax()] - - model = MLPnGPU(nb_classes, layers, input_shape) - return model - - -def make_model(model_type='madry', **kwargs): - if model_type == 'basic': - return make_basic_ngpu(**kwargs) - elif model_type == 'madry': - return make_madry_ngpu(**kwargs) - elif model_type == 'resnet_tf': - return ResNetTF(**kwargs) - else: - raise Exception('model type not defined.') diff --git a/examples/multigpu_advtrain/model.py b/examples/multigpu_advtrain/model.py deleted file mode 100644 index 09eac7dfc..000000000 --- a/examples/multigpu_advtrain/model.py +++ /dev/null @@ -1,424 +0,0 @@ -""" -MultiGPU model similar to the one used in model tutorials. The model keeps -one copy of the weights on each device and handles syncing the parameters -across devices. -""" -# pylint: disable=missing-docstring -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import numpy as np -import tensorflow as tf - -from cleverhans.model import Model - - -def clone_variable(name, x, trainable=False): - return tf.get_variable(name, shape=x.shape, dtype=x.dtype, - trainable=trainable) - - -def unify_device_name(dname): - """Converts TensorFlow device names in the format /Device:GPU0 to /gpu:0. - """ - if dname is None: - return None - return dname.lower().replace('device:', '') - - -class MLP(Model): - """ - An example of a bare bones multilayer perceptron (MLP) class. - """ - - def __init__(self, nb_classes, layers, input_shape): - super(MLP, self).__init__('MLP', nb_classes=nb_classes, hparams={}) - - self.layer_names = [] - self.layers = layers - self.input_shape = input_shape - if isinstance(layers[-1], Softmax): - layers[-1].name = 'probs' - layers[-2].name = 'logits' - else: - layers[-1].name = 'logits' - for i, layer in enumerate(self.layers): - if hasattr(layer, 'name'): - name = layer.name - else: - name = layer.__class__.__name__ + str(i) - layer.name = name - self.layer_names.append(name) - - layer.set_input_shape(input_shape) - input_shape = layer.get_output_shape() - - def fprop(self, x, set_ref=False): - states = [] - for layer in self.layers: - if set_ref: - layer.ref = x - x = layer.fprop(x) - assert x is not None - states.append(x) - states = dict(zip(self.layer_names, states)) - return states - - def get_params(self): - out = [] - for layer in self.layers: - for param in layer.get_params(): - if param not in out: - out.append(param) - return out - - -class Layer(object): - - def get_output_shape(self): - return self.output_shape - - -class Linear(Layer): - - def __init__(self, num_hid): - self.num_hid = num_hid - - def set_input_shape(self, input_shape): - batch_size, dim = input_shape - self.input_shape = [batch_size, dim] - self.output_shape = [batch_size, self.num_hid] - init = tf.random_normal([dim, self.num_hid], dtype=tf.float32) - init = init / tf.sqrt(1e-7 + tf.reduce_sum(tf.square(init), axis=0, - keep_dims=True)) - self.W = tf.Variable(init) - self.b = tf.Variable(np.zeros((self.num_hid,)).astype('float32')) - - def fprop(self, x): - return tf.matmul(x, self.W) + self.b - - def get_params(self): - return [self.W, self.b] - - -class Conv2D(Layer): - - def __init__(self, output_channels, kernel_shape, strides, padding): - self.__dict__.update(locals()) - del self.self - - def set_input_shape(self, input_shape): - batch_size, _, __, input_channels = input_shape - kernel_shape = tuple(self.kernel_shape) + (input_channels, - self.output_channels) - assert len(kernel_shape) == 4 - assert all(isinstance(e, int) for e in kernel_shape), kernel_shape - init = tf.random_normal(kernel_shape, dtype=tf.float32) - init = init / tf.sqrt(1e-7 + tf.reduce_sum(tf.square(init), - axis=(0, 1, 2))) - self.kernels = tf.Variable(init) - self.b = tf.Variable( - np.zeros((self.output_channels,)).astype('float32')) - input_shape = list(input_shape) - input_shape[0] = 1 - dummy_batch = tf.zeros(input_shape) - dummy_output = self.fprop(dummy_batch) - output_shape = [int(e) for e in dummy_output.get_shape()] - output_shape[0] = batch_size - self.output_shape = tuple(output_shape) - - def fprop(self, x): - return tf.nn.conv2d(x, self.kernels, (1,) + tuple(self.strides) + (1,), - self.padding) + self.b - - def get_params(self): - return [self.kernels, self.b] - - -class ReLU(Layer): - - def __init__(self): - pass - - def set_input_shape(self, shape): - self.input_shape = shape - self.output_shape = shape - - def fprop(self, x): - return tf.nn.relu(x) - - def get_params(self): - return [] - - -class Softmax(Layer): - - def __init__(self): - pass - - def set_input_shape(self, shape): - self.input_shape = shape - self.output_shape = shape - - def fprop(self, x): - return tf.nn.softmax(x) - - def get_params(self): - return [] - - -class Flatten(Layer): - - def __init__(self): - pass - - def set_input_shape(self, shape): - self.input_shape = shape - output_width = 1 - for factor in shape[1:]: - output_width *= factor - self.output_width = output_width - self.output_shape = [shape[0], output_width] - - def fprop(self, x): - return tf.reshape(x, [-1, self.output_width]) - - def get_params(self): - return [] - - -class MLPnGPU(MLP): - """ - A multi layer perceptron that can be copied over multiple GPUs. Only one - copy of the weights is created on each device. - """ - - def __init__(self, nb_classes, layers, input_shape): - super(MLPnGPU, self).__init__(nb_classes, layers, input_shape) - self.scope = 'MLPnGPU' - - def fprop(self, x): - with tf.variable_scope(self.scope): - states = super(MLPnGPU, self).fprop(x) - return states - - def set_device(self, device_name): - """ - Set the device before the next fprop to create a new graph on the - specified device. - """ - device_name = unify_device_name(device_name) - self.device_name = device_name - for layer in self.layers: - layer.device_name = device_name - - def create_sync_ops(self, host_device): - """ - Return a list of assignment operations that syncs the parameters - of all model copies with the one on host_device. - :param host_device: (required str) the name of the device with latest - parameters - """ - host_device = unify_device_name(host_device) - sync_ops = [] - for layer in self.layers: - if isinstance(layer, LayernGPU): - sync_ops += layer.create_sync_ops(host_device) - return sync_ops - - def set_training(self, training=False): - for layer in self.layers: - if isinstance(layer, LayernGPU): - layer.set_training(training) - - -class LayernGPU(Layer): - """ - A layer that has separate copies of model parameters on each GPU. - """ - - def __init__(self): - """ - :param input_shape: a tuple or list as the input shape to layer - """ - self.input_shape = None - self.params_device = {} - self.params_names = None - self.device_name = '/gpu:0' - self.training = True - - def set_training(self, training=False): - self.training = training - - def get_variable(self, name, initializer): - """ - Create and initialize a variable using a numpy array and set trainable. - :param name: (required str) name of the variable - :param initializer: a numpy array or a tensor - """ - v = tf.get_variable(name, shape=initializer.shape, - initializer=(lambda shape, dtype, partition_info: - initializer), - trainable=self.training) - return v - - def set_input_shape_ngpu(self, new_input_shape): - """ - Create and initialize layer parameters on the device previously set - in self.device_name. - - :param new_input_shape: a list or tuple for the shape of the input. - """ - assert self.device_name, "Device name has not been set." - - device_name = self.device_name - if self.input_shape is None: - # First time setting the input shape - self.input_shape = [None] + [int(d) for d in list(new_input_shape)] - - if device_name in self.params_device: - # There is a copy of weights on this device - self.__dict__.update(self.params_device[device_name]) - return - - # Stop recursion - self.params_device[device_name] = {} - - # Initialize weights on this device - with tf.device(device_name): - self.set_input_shape(self.input_shape) - keys_after = self.__dict__.keys() - if self.params_names is None: - # Prevent overriding training - self.params_names = [k for k in keys_after if isinstance( - self.__dict__[k], tf.Variable)] - params = {k: self.__dict__[k] for k in self.params_names} - self.params_device[device_name] = params - - def create_sync_ops(self, host_device): - """Create an assignment operation for each weight on all devices. The - weight is assigned the value of the copy on the `host_device'. - """ - sync_ops = [] - host_params = self.params_device[host_device] - for device, params in (self.params_device).iteritems(): - if device == host_device: - continue - for k in self.params_names: - if isinstance(params[k], tf.Variable): - sync_ops += [tf.assign(params[k], host_params[k])] - return sync_ops - - def fprop(self, x): - if self.name is None: - self.set_input_shape_ngpu(x.shape[1:]) - return self.fprop_noscope(x) - else: - with tf.variable_scope(self.name): - self.set_input_shape_ngpu(x.shape[1:]) - return self.fprop_noscope(x) - - -class LinearnGPU(LayernGPU): - - def __init__(self, num_hid, w_name='W'): - super(LinearnGPU, self).__init__() - self.num_hid = num_hid - self.w_name = w_name - - def set_input_shape(self, input_shape): - batch_size, dim = input_shape - self.input_shape = [batch_size, dim] - self.output_shape = [batch_size, self.num_hid] - shape = [dim, self.num_hid] - with tf.variable_scope(self.name): - init = tf.truncated_normal(shape, stddev=0.1) - self.W = self.get_variable(self.w_name, init) - self.b = self.get_variable('b', .1 + np.zeros( - (self.num_hid,)).astype('float32')) - - def fprop_noscope(self, x): - return tf.matmul(x, self.W) + self.b - - -class Conv2DnGPU(LayernGPU): - - def __init__(self, output_channels, kernel_shape, strides, padding, - w_name='kernels'): - super(Conv2DnGPU, self).__init__() - self.__dict__.update(locals()) - del self.self - self.w_name = w_name - - def set_input_shape(self, input_shape): - assert len(input_shape) == 4 - input_channels = input_shape[3] - kernel_shape = tuple(self.kernel_shape) + (input_channels, - self.output_channels) - assert len(kernel_shape) == 4 - assert all(isinstance(e, int) for e in kernel_shape), kernel_shape - with tf.variable_scope(self.name): - init = tf.truncated_normal(kernel_shape, stddev=0.1) - self.kernels = self.get_variable(self.w_name, init) - self.b = self.get_variable( - 'b', .1 + np.zeros((self.output_channels,)).astype('float32')) - input_shape = list(input_shape) - self.input_shape = input_shape - input_shape[0] = 1 - dummy_batch = tf.zeros(input_shape) - dummy_output = self.fprop(dummy_batch) - output_shape = [int(e) for e in dummy_output.get_shape()] - output_shape[0] = 1 - self.output_shape = tuple(output_shape) - - def fprop_noscope(self, x): - return tf.nn.conv2d(x, self.kernels, (1,) + tuple(self.strides) + - (1,), self.padding) + self.b - - -class MaxPool(LayernGPU): - def __init__(self, ksize, strides, padding): - super(MaxPool, self).__init__() - self.__dict__.update(locals()) - del self.self - - def set_input_shape(self, input_shape): - input_shape = list(input_shape) - input_shape[0] = 1 - dummy_batch = tf.zeros(input_shape) - dummy_output = self.fprop(dummy_batch) - output_shape = [int(e) for e in dummy_output.get_shape()] - output_shape[0] = 1 - self.output_shape = tuple(output_shape) - - def fprop_noscope(self, x): - return tf.nn.max_pool(x, - ksize=(1,) + tuple(self.ksize) + (1,), - strides=(1,) + tuple(self.strides) + (1,), - padding=self.padding) - - -class LayerNorm(LayernGPU): - - def set_input_shape(self, input_shape): - self.input_shape = list(input_shape) - params_shape = [input_shape[-1]] - self.params_shape = params_shape - - self.beta = tf.get_variable( - 'beta', params_shape, tf.float32, - initializer=tf.constant_initializer(0.0, tf.float32), - trainable=self.training) - self.gamma = tf.get_variable( - 'gamma', params_shape, tf.float32, - initializer=tf.constant_initializer(1.0, tf.float32), - trainable=self.training) - - def fprop_noscope(self, x): - mean = tf.reduce_mean(x, (1, 2), keep_dims=True) - x = x - mean - std = tf.sqrt(1e-7 + - tf.reduce_mean(tf.square(x), (1, 2), keep_dims=True)) - x = x / std - return x * self.gamma + self.beta diff --git a/examples/multigpu_advtrain/resnet_tf.py b/examples/multigpu_advtrain/resnet_tf.py deleted file mode 100644 index c04d7542c..000000000 --- a/examples/multigpu_advtrain/resnet_tf.py +++ /dev/null @@ -1,327 +0,0 @@ -# https://github.com/tensorflow/models/blob/master/research/resnet/resnet_model.py -# -# Copyright 2016 The TensorFlow Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# ============================================================================== - -"""ResNet model. - -Related papers: -https://arxiv.org/pdf/1603.05027v2.pdf -https://arxiv.org/pdf/1512.03385v1.pdf -https://arxiv.org/pdf/1605.07146v1.pdf -""" -from collections import namedtuple - -import tensorflow as tf -import six - -from model import MLPnGPU -from model import Conv2DnGPU -from model import LinearnGPU -from model import LayerNorm - -HParams = namedtuple('HParams', - 'batch_size, nb_classes, min_lrn_rate, lrn_rate, ' - 'num_residual_units, use_bottleneck, weight_decay_rate, ' - 'relu_leakiness, momentum') - - -class ResNetTF(MLPnGPU): - """ResNet model.""" - - def __init__(self, batch_size=None, name=None, **kwargs): - NB_CLASSES = 10 - super(ResNetTF, self).__init__(nb_classes=NB_CLASSES, layers=[], - input_shape=None) - self.global_step = tf.contrib.framework.get_or_create_global_step() - self.hps = HParams(batch_size=batch_size, - nb_classes=NB_CLASSES, - min_lrn_rate=0.0001, - lrn_rate=0.1, - num_residual_units=5, - use_bottleneck=False, - weight_decay_rate=0.0002, - relu_leakiness=0.1, - momentum=.9) - self.layers = [] - self.layer_idx = 0 - self.init_layers = True - self.decay_cost = None - self.training = None - self.device_name = None - - def set_training(self, training=False): - super(ResNetTF, self).set_training(training) - self.training = training - - def fprop(self, x): - self.layer_idx = 0 - with tf.variable_scope('Resnet'): - logits, probs = self._build_model(x) - self.init_layers = False - states = {'logits': logits, 'probs': probs} - return states - - def _stride_arr(self, stride): - """Map a stride scalar to the stride array for tf.nn.conv2d.""" - return [1, stride, stride, 1] - - def _build_model(self, x): - """Build the core model within the graph.""" - with tf.variable_scope('init'): - x = self._conv('init_conv', x, 3, x.shape[3], 16, - self._stride_arr(1)) - - strides = [1, 2, 2] - activate_before_residual = [True, False, False] - if self.hps.use_bottleneck: - res_func = self._bottleneck_residual - filters = [16, 64, 128, 256] - else: - res_func = self._residual - filters = [16, 16, 32, 64] - # Uncomment the following codes to use w28-10 wide residual - # network. - # It is more memory efficient than very deep residual network and - # has - # comparably good performance. - # https://arxiv.org/pdf/1605.07146v1.pdf - # filters = [16, 160, 320, 640] - # Update hps.num_residual_units to 4 - - with tf.variable_scope('unit_1_0'): - x = res_func(x, filters[0], filters[1], - self._stride_arr(strides[0]), - activate_before_residual[0]) - for i in six.moves.range(1, self.hps.num_residual_units): - with tf.variable_scope('unit_1_%d' % i): - x = res_func(x, filters[1], filters[1], - self._stride_arr(1), False) - - with tf.variable_scope('unit_2_0'): - x = res_func(x, filters[1], filters[2], - self._stride_arr(strides[1]), - activate_before_residual[1]) - for i in six.moves.range(1, self.hps.num_residual_units): - with tf.variable_scope('unit_2_%d' % i): - x = res_func(x, filters[2], filters[2], - self._stride_arr(1), False) - - with tf.variable_scope('unit_3_0'): - x = res_func(x, filters[2], filters[3], - self._stride_arr(strides[2]), - activate_before_residual[2]) - for i in six.moves.range(1, self.hps.num_residual_units): - with tf.variable_scope('unit_3_%d' % i): - x = res_func(x, filters[3], filters[3], - self._stride_arr(1), False) - - with tf.variable_scope('unit_last'): - x = self._layer_norm('final_bn', x) - x = self._relu(x, self.hps.relu_leakiness) - x = self._global_avg_pool(x) - - with tf.variable_scope('logit'): - logits = self._fully_connected(x, self.hps.nb_classes) - predictions = tf.nn.softmax(logits) - - return logits, predictions - - def build_cost(self, labels, logits): - """ - Build the graph for cost from the logits if logits are provided. - If predictions are provided, logits are extracted from the operation. - """ - op = logits.op - if "softmax" in str(op).lower(): - logits, = op.inputs - - with tf.variable_scope('costs'): - xent = tf.nn.softmax_cross_entropy_with_logits( - logits=logits, labels=labels) - cost = tf.reduce_mean(xent, name='xent') - cost += self._decay() - cost = cost - - return cost - - def build_train_op_from_cost(self, cost): - """Build training specific ops for the graph.""" - self.lrn_rate = tf.constant(self.hps.lrn_rate, tf.float32, - name='learning_rate') - self.momentum = tf.constant(self.hps.momentum, tf.float32, - name='momentum') - - trainable_variables = tf.trainable_variables() - grads = tf.gradients(cost, trainable_variables) - devs = {v.device for v in trainable_variables} - assert len(devs) == 1, ('There should be no trainable variables' - ' on any device other than the last GPU.') - - optimizer = tf.train.MomentumOptimizer(self.lrn_rate, self.momentum) - - gv_pairs = zip(grads, trainable_variables) - gv_pairs = [gv for gv in gv_pairs if gv[0] is not None] - devs = {gv[1].device for gv in gv_pairs} - assert len(devs) == 1, ('There should be no gradients wrt' - ' vars on other GPUs.') - - apply_op = optimizer.apply_gradients( - gv_pairs, - global_step=self.global_step, name='train_step') - - train_ops = [apply_op] - train_op = tf.group(*train_ops) - return train_op - - def _layer_norm(self, name, x): - """Layer normalization.""" - if self.init_layers: - bn = LayerNorm() - bn.name = name - self.layers += [bn] - else: - bn = self.layers[self.layer_idx] - self.layer_idx += 1 - bn.device_name = self.device_name - bn.set_training(self.training) - x = bn.fprop(x) - return x - - def _residual(self, x, in_filter, out_filter, stride, - activate_before_residual=False): - """Residual unit with 2 sub layers.""" - if activate_before_residual: - with tf.variable_scope('shared_activation'): - x = self._layer_norm('init_bn', x) - x = self._relu(x, self.hps.relu_leakiness) - orig_x = x - else: - with tf.variable_scope('residual_only_activation'): - orig_x = x - x = self._layer_norm('init_bn', x) - x = self._relu(x, self.hps.relu_leakiness) - - with tf.variable_scope('sub1'): - x = self._conv('conv1', x, 3, in_filter, out_filter, stride) - - with tf.variable_scope('sub2'): - x = self._layer_norm('bn2', x) - x = self._relu(x, self.hps.relu_leakiness) - x = self._conv('conv2', x, 3, out_filter, out_filter, [1, 1, 1, 1]) - - with tf.variable_scope('sub_add'): - if in_filter != out_filter: - orig_x = tf.nn.avg_pool(orig_x, stride, stride, 'VALID') - orig_x = tf.pad( - orig_x, [[0, 0], [0, 0], [0, 0], - [(out_filter - in_filter) // 2, - (out_filter - in_filter) // 2]]) - x += orig_x - - return x - - def _bottleneck_residual(self, x, in_filter, out_filter, stride, - activate_before_residual=False): - """Bottleneck residual unit with 3 sub layers.""" - if activate_before_residual: - with tf.variable_scope('common_bn_relu'): - x = self._layer_norm('init_bn', x) - x = self._relu(x, self.hps.relu_leakiness) - orig_x = x - else: - with tf.variable_scope('residual_bn_relu'): - orig_x = x - x = self._layer_norm('init_bn', x) - x = self._relu(x, self.hps.relu_leakiness) - - with tf.variable_scope('sub1'): - x = self._conv('conv1', x, 1, in_filter, out_filter / 4, stride) - - with tf.variable_scope('sub2'): - x = self._layer_norm('bn2', x) - x = self._relu(x, self.hps.relu_leakiness) - x = self._conv('conv2', x, 3, out_filter / 4, - out_filter / 4, [1, 1, 1, 1]) - - with tf.variable_scope('sub3'): - x = self._layer_norm('bn3', x) - x = self._relu(x, self.hps.relu_leakiness) - x = self._conv('conv3', x, 1, out_filter / - 4, out_filter, [1, 1, 1, 1]) - - with tf.variable_scope('sub_add'): - if in_filter != out_filter: - orig_x = self._conv('project', orig_x, 1, - in_filter, out_filter, stride) - x += orig_x - - return x - - def _decay(self): - """L2 weight decay loss.""" - if self.decay_cost is not None: - return self.decay_cost - - costs = [] - if self.device_name is None: - for var in tf.trainable_variables(): - if var.op.name.find(r'DW') > 0: - costs.append(tf.nn.l2_loss(var)) - else: - for layer in self.layers: - for var in layer.params_device[self.device_name].values(): - if (isinstance(var, tf.Variable) and var.op.name.find(r'DW') > 0): - costs.append(tf.nn.l2_loss(var)) - - self.decay_cost = tf.multiply(self.hps.weight_decay_rate, - tf.add_n(costs)) - return self.decay_cost - - def _conv(self, name, x, filter_size, in_filters, out_filters, strides): - """Convolution.""" - if self.init_layers: - conv = Conv2DnGPU(out_filters, - (filter_size, filter_size), - strides[1:3], 'SAME', w_name='DW') - conv.name = name - self.layers += [conv] - else: - conv = self.layers[self.layer_idx] - self.layer_idx += 1 - conv.device_name = self.device_name - conv.set_training(self.training) - return conv.fprop(x) - - def _relu(self, x, leakiness=0.0): - """Relu, with optional leaky support.""" - return tf.where(tf.less(x, 0.0), leakiness * x, x, name='leaky_relu') - - def _fully_connected(self, x, out_dim): - """FullyConnected layer for final output.""" - if self.init_layers: - fc = LinearnGPU(out_dim, w_name='DW') - fc.name = 'logits' - self.layers += [fc] - else: - fc = self.layers[self.layer_idx] - self.layer_idx += 1 - fc.device_name = self.device_name - fc.set_training(self.training) - return fc.fprop(x) - - def _global_avg_pool(self, x): - assert x.get_shape().ndims == 4 - return tf.reduce_mean(x, [1, 2]) diff --git a/examples/multigpu_advtrain/run_multigpu.py b/examples/multigpu_advtrain/run_multigpu.py deleted file mode 100644 index 3364c915b..000000000 --- a/examples/multigpu_advtrain/run_multigpu.py +++ /dev/null @@ -1,84 +0,0 @@ -""" -This script adversarially trains a model using iterative attacks on multiple -GPUs. -""" -# pylint: disable=missing-docstring -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - -import logging -from collections import namedtuple - -from cleverhans.compat import app, flags - -from trainer import TrainerMultiGPU -from trainer import TrainerSingleGPU - - -def run_trainer(hparams): - logging.basicConfig(format='%(asctime)s %(message)s', level=logging.INFO) - - if 'multigpu' in hparams.attack_type_train: - logging.info('Multi GPU Trainer.') - trainer = TrainerMultiGPU(hparams) - else: - logging.info('Single GPU Trainer.') - trainer = TrainerSingleGPU(hparams) - trainer.model_train() - trainer.eval(inc_epoch=False) - - return trainer.finish() - - -def main(argv=None): - f = {x: flags.FLAGS[x].value for x in dir(flags.FLAGS)} - HParams = namedtuple('HParams', f.keys()) - hparams = HParams(**f) - run_trainer(hparams) - - -if __name__ == '__main__': - flags.DEFINE_integer('train_start', 0, - 'Index of first training set example.') - flags.DEFINE_integer('train_end', 60000, - 'Index of last training set example.') - flags.DEFINE_integer('test_start', 0, - 'Index of first test set example.') - flags.DEFINE_integer('test_end', 10000, - 'Index of last test set example.') - flags.DEFINE_integer('nb_epochs', 6, 'Number of epochs to train model.') - flags.DEFINE_integer('batch_size', 128, 'Size of training batches.') - flags.DEFINE_boolean('adv_train', False, - 'Whether to do adversarial training.') - flags.DEFINE_boolean('save', True, - 'Whether to save from a checkpoint.') - flags.DEFINE_string('save_dir', 'runs/X', - 'Location to store logs/model.') - flags.DEFINE_string('model_type', 'madry', - 'Model type: basic|madry|resnet_tf.') - flags.DEFINE_string('attack_type_train', 'MadryEtAl_y_multigpu', - 'Attack type for adversarial training:\ - FGSM|MadryEtAl{,_y}{,_multigpu}.') - flags.DEFINE_string('attack_type_test', 'FGSM', - 'Attack type for test: FGSM|MadryEtAl{,_y}.') - flags.DEFINE_string('dataset', 'mnist', 'Dataset mnist|cifar10.') - flags.DEFINE_boolean('only_adv_train', False, - 'Do not train with clean examples when adv training.') - flags.DEFINE_integer('save_steps', 50, 'Save model per X steps.') - flags.DEFINE_integer('attack_nb_iter_train', None, - 'Number of iterations of training attack.') - flags.DEFINE_integer('eval_iters', 1, 'Evaluate every X steps.') - flags.DEFINE_integer('lrn_step', 30000, 'Step to decrease learning rate' - 'for ResNet.') - flags.DEFINE_float('adam_lrn', 0.001, 'Learning rate for Adam Optimizer.') - flags.DEFINE_float('mom_lrn', 0.1, - 'Learning rate for Momentum Optimizer.') - flags.DEFINE_integer('ngpu', 1, 'Number of gpus.') - flags.DEFINE_integer('sync_step', 1, 'Sync params frequency.') - flags.DEFINE_boolean('fast_tests', False, 'Fast tests against attacks.') - flags.DEFINE_string('data_path', './datasets/', 'Path to datasets.' - 'Each dataset should be in a subdirectory.') - - app.run() diff --git a/examples/multigpu_advtrain/runner.py b/examples/multigpu_advtrain/runner.py deleted file mode 100644 index abed2eee5..000000000 --- a/examples/multigpu_advtrain/runner.py +++ /dev/null @@ -1,167 +0,0 @@ -"""Wrappers to TensorFlow Session.run(). -""" -# pylint: disable=missing-docstring -from collections import OrderedDict - - -class Runner(object): - """ - Wrap TensorFlow Session.run() by adding preprocessing and postprocessing - steps. - """ - - def __init__(self, inputs, outputs, sess=None): - self.sess = sess - self.inputs = inputs - self.outputs = outputs - self.feed_dict = {} - - def run(self, X_batch=None): - fetches, feed_dict = self.set_input(X_batch) - fvals = self.sess.run(fetches, feed_dict=feed_dict) - return self.proc_fvals(fvals) - - def set_input(self, X_batch=None): - raise NotImplementedError('set_input not implemented.') - - def proc_fvals(self, fvals): - raise NotImplementedError('proc_fvals not implemented.') - - def is_finished(self): - raise NotImplementedError('is_finished not implemented.') - - -class RunnerMultiGPU(Runner): - """ - Runs a graph with sub-graphs that need to run sequentially. Each sub-graph - takes its inputs from the outputs of the previous sub-graph. - """ - - def __init__(self, *args, **kwargs): - super(RunnerMultiGPU, self).__init__(*args, **kwargs) - self.assert_inputs_outputs() - self.next_vals = [None] * len(self.inputs) - - def assert_inputs_outputs(self): - inputs = self.inputs - outputs = self.outputs - assert len(inputs) == len(outputs), ( - 'Inputs and Outputs should match in length.') - for i in range(len(inputs)): - device = inputs[i].values()[0].device - for _k, v in inputs[i].iteritems(): - assert v.device == device, ( - 'Inputs should be on the same device.') - for _k, v in outputs[i].iteritems(): - assert v.device == device, ( - 'Outputs should be on the same device.') - if i > 0: - ikeys = inputs[i].keys() - okeys = outputs[i-1].keys() - # The actual requirement is looser, only the last output keys - # should always be returned in the same order. - assert all(ikeys[j] == okeys[j] for j in range(len(ikeys))), ( - 'Inputs and outputs keys should be in the same order.') - - def set_input(self, X_batch=None): - """ - Preprocessing the inputs before calling session.run() - - :param X_batch: A dictionary of inputs to the first sub-graph - :return: A tuple, `(fetches, fd)`, with `fetches` being a list of - Tensors to be fetches and `fd` the feed dictionary. - """ - inputs = self.inputs - outputs = self.outputs - - # data for first gpu - fd = {} - if X_batch is not None: - self.next_vals[0] = OrderedDict() - for i, vname in enumerate(self.inputs[0]): - if vname in X_batch: - self.next_vals[0][vname] = X_batch[vname] - else: - self.next_vals[0][vname] = None - else: - self.next_vals[0] = None - - # Set `feed_dict` for each GPU. If there is something to run for that - # GPU, collect outputs to be fetched. - fetches = [] - self.active_gpus = [] - for i in range(len(outputs)): - if self.next_vals[i] is None: - self.active_gpus += [False] - continue - self.active_gpus += [True] - for k in inputs[i]: - if self.next_vals[i][k] is not None: - fd[inputs[i][k]] = self.next_vals[i][k] - for k, v in outputs[i].iteritems(): - fetches += [v] - - fd.update(self.feed_dict) - - return fetches, fd - - def proc_fvals(self, fvals): - """ - Postprocess the outputs of the Session.run(). Move the outputs of - sub-graphs to next ones and return the output of the last sub-graph. - - :param fvals: A list of fetched values returned by Session.run() - :return: A dictionary of fetched values returned by the last sub-graph. - """ - inputs = self.inputs - outputs = self.outputs - - # Move data to the next sub-graph for the next step - cur = 0 - for i in range(len(inputs)-1): - if not self.active_gpus[i]: - self.next_vals[i+1] = None - continue - self.next_vals[i+1] = OrderedDict() - for k in outputs[i]: - self.next_vals[i+1][k] = fvals[cur] - cur += 1 - if i == 0: - self.next_vals[0] = None - - # Return the output of the last sub-graph - last_fvals = OrderedDict() - if self.active_gpus[-1]: - assert cur+len(outputs[-1]) == len(fvals) - for k in outputs[-1]: - last_fvals[k] = fvals[cur] - cur += 1 - return last_fvals - - def is_finished(self): - return all(v is None for v in self.next_vals) - - -class RunnerSingleGPU(Runner): - def __init__(self, *args, **kwargs): - super(RunnerSingleGPU, self).__init__(*args, **kwargs) - - def set_input(self, X_batch=None): - fd = {} - for vname, v in self.inputs[0].iteritems(): - if vname in X_batch: - fd[v] = X_batch[vname] - fetches = self.outputs - return fetches, fd - - def proc_fvals(self, fvals): - """ - Nothing to post-process on single GPU. - """ - return True - - def is_finished(self): - """ - Single GPU trainer has no cache. - """ - return True diff --git a/examples/multigpu_advtrain/test_attack_multigpu.py b/examples/multigpu_advtrain/test_attack_multigpu.py deleted file mode 100644 index a241aec6a..000000000 --- a/examples/multigpu_advtrain/test_attack_multigpu.py +++ /dev/null @@ -1,84 +0,0 @@ -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - -import unittest -import numpy as np -import tensorflow as tf - -from attacks_multigpu import MadryEtAlMultiGPU -from model import MLPnGPU -from model import LayernGPU - - -import sys -import os -sys.path.insert(0, os.path.abspath('../../tests_tf/')) -from test_attacks import TestMadryEtAl # NOQA - - -class TestMadryEtAlMultiGPU(TestMadryEtAl): - """ - By inherting from `TestMadryEtAl`, the attack `MadryEtAlMultiGPU` can be - tested against all tests of the base attack. - """ - - def setUp(self): - super(TestMadryEtAlMultiGPU, self).setUp() - - class SimpleLayer(LayernGPU): - - def set_input_shape(self, input_shape): - self.input_shape = input_shape - self.output_shape = input_shape - self.W1 = tf.constant([[1.5, .3], [-2, 0.3]], dtype=tf.float32) - self.W2 = tf.constant([[-2.4, 1.2], [0.5, -2.3]], - dtype=tf.float32) - - def fprop_noscope(self, x): - h1 = tf.nn.sigmoid(tf.matmul(x, self.W1)) - res = tf.matmul(h1, self.W2) - return res - - input_shape = (None, 2) - self.model_ngpu = MLPnGPU([SimpleLayer()], input_shape) - - self.attack_single_gpu = self.attack - self.attack_multi_gpu = MadryEtAlMultiGPU(self.model_ngpu, - sess=self.sess) - self.attack = self.attack_multi_gpu - - def test_single_vs_multi_gpu(self): - """ - Compare the strength of the single GPU and multi-GPU implementations. - """ - x_val = np.random.rand(100, 2) - x_val = np.array(x_val, dtype=np.float32) - - def multi_attack(attack): - flags = {'ngpu': 1, 'eps': 1.0, 'eps_iter': 0.01, - 'clip_min': 0.5, 'clip_max': 0.7, 'nb_iter': 2, - 'rand_init': True} - - orig_labs = np.argmax(self.sess.run(self.model(x_val)), axis=1) - new_labs_multi = orig_labs.copy() - # Generate multiple adversarial examples - for i in range(40): - x_adv = attack.generate_np(x_val, **flags) - new_labs = np.argmax(self.sess.run(self.model(x_adv)), axis=1) - - # Examples for which we have not found adversarial examples - indices = (orig_labs == new_labs_multi) - new_labs_multi[indices] = new_labs[indices] - - return np.mean(orig_labs == new_labs_multi) - - acc_s = multi_attack(self.attack_single_gpu) - acc_m = multi_attack(self.attack_multi_gpu) - - self.assertClose(acc_s, acc_m, atol=1e-2) - - -if __name__ == '__main__': - unittest.main() diff --git a/examples/multigpu_advtrain/test_run_multigpu.py b/examples/multigpu_advtrain/test_run_multigpu.py deleted file mode 100644 index f6771c0ac..000000000 --- a/examples/multigpu_advtrain/test_run_multigpu.py +++ /dev/null @@ -1,160 +0,0 @@ -# pylint: disable=missing-docstring -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - -from collections import namedtuple -import unittest - -import numpy as np -import tensorflow as tf - -from cleverhans.utils import AccuracyReport -from cleverhans.devtools.checks import CleverHansTest -from run_multigpu import run_trainer - - -class TestRunMultiGPU(CleverHansTest): - def helper_run_multi_gpu_madryetal(self, extra_flags=None): - """ - Compare the single GPU performance to multiGPU performance. - """ - # Run the trainers on a dataset of reduced size - flags = {'train_start': 0, - 'train_end': 5000, - 'test_start': 0, - 'test_end': 333, - 'nb_epochs': 5, - 'testing': True} - - # Run the multi-gpu trainer for adversarial training - flags.update({'batch_size': 128, 'adam_lrn': 0.001, - 'dataset': 'mnist', 'only_adv_train': False, - 'eval_iters': 1, 'fast_tests': True, - 'save_dir': None, 'save_steps': 10000, - 'attack_nb_iter_train': 10, 'sync_step': None, - 'adv_train': True, - 'save': False, - 'model_type': 'basic', - 'attack_type_test': 'MadryEtAl_y'}) - if extra_flags is not None: - flags.update(extra_flags) - - # Run the multi-gpu trainer for adversarial training using 2 gpus - # trainer_multigpu by default sets `allow_soft_placement=True` - flags.update({'ngpu': 2, - 'attack_type_train': 'MadryEtAl_y_multigpu', - 'sync_step': 1}) - HParams = namedtuple('HParams', flags.keys()) - - hparams = HParams(**flags) - np.random.seed(42) - tf.set_random_seed(42) - with tf.variable_scope(None, 'runner'): - report_dict = run_trainer(hparams) - report_m = AccuracyReport() - report_m.train_adv_train_clean_eval = report_dict['train'] - report_m.adv_train_clean_eval = report_dict['test'] - report_m.adv_train_adv_eval = report_dict['MadryEtAl_y'] - - flags.update({'ngpu': 1, 'attack_type_train': 'MadryEtAl_y'}) - hparams = HParams(**flags) - np.random.seed(42) - tf.set_random_seed(42) - with tf.variable_scope(None, 'runner'): - report_dict = run_trainer(hparams) - report_s = AccuracyReport() - report_s.train_adv_train_clean_eval = report_dict['train'] - report_s.adv_train_clean_eval = report_dict['test'] - report_s.adv_train_adv_eval = report_dict['MadryEtAl_y'] - - self.assertClose(report_s.train_adv_train_clean_eval, - report_m.train_adv_train_clean_eval, - atol=5e-2) - self.assertClose(report_s.adv_train_clean_eval, - report_m.adv_train_clean_eval, - atol=2e-2) - self.assertClose(report_s.adv_train_adv_eval, - report_m.adv_train_adv_eval, - atol=5e-2) - - def test_run_single_gpu_fgsm(self): - """ - Test the basic single GPU performance by comparing to the FGSM - tutorial. - """ - from cleverhans_tutorials import mnist_tutorial_tf - - # Run the MNIST tutorial on a dataset of reduced size - flags = {'train_start': 0, - 'train_end': 5000, - 'test_start': 0, - 'test_end': 333, - 'nb_epochs': 5, - 'testing': True} - report = mnist_tutorial_tf.mnist_tutorial(**flags) - - # Run the multi-gpu trainer for clean training - flags.update({'batch_size': 128, 'adam_lrn': 0.001, - 'dataset': 'mnist', 'only_adv_train': False, - 'eval_iters': 1, 'ngpu': 1, 'fast_tests': False, - 'attack_type_train': '', - 'save_dir': None, 'save_steps': 10000, - 'attack_nb_iter_train': None, 'save': False, - 'model_type': 'basic', 'attack_type_test': 'FGSM'}) - - flags.update({'adv_train': False}) - HParams = namedtuple('HParams', flags.keys()) - - hparams = HParams(**flags) - np.random.seed(42) - tf.set_random_seed(42) - with tf.variable_scope(None, 'runner'): - report_dict = run_trainer(hparams) - report_2 = AccuracyReport() - report_2.train_clean_train_clean_eval = report_dict['train'] - report_2.clean_train_clean_eval = report_dict['test'] - report_2.clean_train_adv_eval = report_dict['FGSM'] - - # Run the multi-gpu trainer for adversarial training - flags.update({'adv_train': True, 'attack_type_train': 'FGSM'}) - HParams = namedtuple('HParams', flags.keys()) - - hparams = HParams(**flags) - np.random.seed(42) - tf.set_random_seed(42) - with tf.variable_scope(None, 'runner'): - report_dict = run_trainer(hparams) - report_2.train_adv_train_clean_eval = report_dict['train'] - report_2.adv_train_clean_eval = report_dict['test'] - report_2.adv_train_adv_eval = report_dict['FGSM'] - - self.assertClose(report.train_clean_train_clean_eval, - report_2.train_clean_train_clean_eval, - atol=5e-2) - self.assertClose(report.clean_train_clean_eval, - report_2.clean_train_clean_eval, - atol=2e-2) - self.assertClose(report.clean_train_adv_eval, - report_2.clean_train_adv_eval, - atol=5e-2) - self.assertClose(report.train_adv_train_clean_eval, - report_2.train_adv_train_clean_eval, - atol=1e-1) - self.assertClose(report.adv_train_clean_eval, - report_2.adv_train_clean_eval, - atol=2e-2) - self.assertClose(report.adv_train_adv_eval, - report_2.adv_train_adv_eval, - atol=1e-1) - - def test_run_multi_gpu_madryetal(self): - self.helper_run_multi_gpu_madryetal() - - def test_run_multi_gpu_naive(self): - self.helper_run_multi_gpu_madryetal({'adv_train': False}) - - -if __name__ == '__main__': - unittest.main() diff --git a/examples/multigpu_advtrain/test_runner.py b/examples/multigpu_advtrain/test_runner.py deleted file mode 100644 index 410e79207..000000000 --- a/examples/multigpu_advtrain/test_runner.py +++ /dev/null @@ -1,73 +0,0 @@ -# pylint: disable=missing-docstring -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - -import unittest -import numpy as np -import tensorflow as tf - -from cleverhans.devtools.checks import CleverHansTest - -from runner import RunnerMultiGPU - - -class TestRunnerMultiGPU(CleverHansTest): - def setUp(self): - super(TestRunnerMultiGPU, self).setUp() - self.sess = tf.Session() - - inputs = [] - outputs = [] - self.niter = 10 - niter = self.niter - # A Simple graph with `niter` sub-graphs. - with tf.variable_scope(None, 'runner'): - for i in range(niter): - v = tf.get_variable('v%d' % i, shape=(100, 10)) - w = tf.get_variable('w%d' % i, shape=(100, 1)) - - inputs += [{'v': v, 'w': w}] - outputs += [{'v': v, 'w': w}] - - self.runner = RunnerMultiGPU(inputs, outputs, sess=self.sess) - - def help_test_runner(self, ninputs, niter): - """ - Tests the MultiGPU runner by feeding in random Tensors for `ninputs` - steps. Then validating the output after `niter-1` steps. - """ - v_val = [] - w_val = [] - for i in range(ninputs): - v_val += [np.random.rand(100, 10)] - w_val += [np.random.rand(100, 1)] - fvals = self.runner.run({'v': v_val[i], 'w': w_val[i]}) - self.assertTrue(len(fvals) == 0) - self.assertFalse(self.runner.is_finished()) - - for i in range(niter-ninputs-1): - self.assertFalse(self.runner.is_finished()) - fvals = self.runner.run() - self.assertTrue(len(fvals) == 0) - self.assertFalse(self.runner.is_finished()) - - for i in range(ninputs): - self.assertFalse(self.runner.is_finished()) - fvals = self.runner.run() - self.assertTrue('v' in fvals and 'w' in fvals) - self.assertTrue(np.allclose(fvals['v'], v_val[i])) - self.assertTrue(np.allclose(fvals['w'], w_val[i])) - - self.assertTrue(self.runner.is_finished()) - - def test_queue_full(self): - self.help_test_runner(self.niter-1, self.niter) - - def test_queue_half(self): - self.help_test_runner(self.niter//2, self.niter) - - -if __name__ == '__main__': - unittest.main() diff --git a/examples/multigpu_advtrain/trainer.py b/examples/multigpu_advtrain/trainer.py deleted file mode 100644 index fd243a4d6..000000000 --- a/examples/multigpu_advtrain/trainer.py +++ /dev/null @@ -1,479 +0,0 @@ -""" -This module provides Trainer classes that given a set of flags, create, -initialize and train a model. These classes use Runner objects to handle -multigpu/singlegpu training. -""" -# pylint: disable=missing-docstring -from collections import OrderedDict -import logging -import math -import time -import os - -import numpy as np -import six -import tensorflow as tf - -from cleverhans.utils_tf import batch_indices -from cleverhans.utils_mnist import data_mnist -import utils_cifar as cifar_input -import utils_svhn as svhn_input -from utils import preprocess_batch - -from make_model import make_model -from evaluator import Evaluator -from evaluator import create_adv_by_name -from model import clone_variable - - -class TrainManager(object): - """ - The base trainer class. Given an object of `hparams`, a trainer - creates and initializes a model. After initialization, the method - `model_train` can be used to train the model. - """ - - def __init__(self, hparams): - """ - :param hparams: An instance of collections.namedtuple specifying the - model type and training configs. The parameters are - documented in `run_multigpu.py`. - """ - self.hparams = hparams - self.batch_size = hparams.batch_size - self.evaluate = None - self.step_num = 0 - self.report = None - self._init_session() - self._init_data() - self._init_inputs() - self._init_model() - self._create_train_graph() - self._init_eval() - self.runner = None - - def _init_session(self): - # Set TF random seed to improve reproducibility - self.rng = np.random.RandomState([2017, 8, 30]) - tf.set_random_seed(1234) - - # Create TF session - self.sess = tf.Session( - config=tf.ConfigProto(allow_soft_placement=True)) - - # Object used to keep track of (and return) key accuracies - if self.hparams.save: - self.writer = tf.summary.FileWriter(self.hparams.save_dir, - flush_secs=10) - else: - self.writer = None - - def _init_data(self): - hparams = self.hparams - batch_size = hparams.batch_size - if hparams.dataset == 'mnist': - # Get MNIST test data - X_train, Y_train, X_test, Y_test = data_mnist( - train_start=hparams.train_start, - train_end=hparams.train_end, - test_start=hparams.test_start, - test_end=hparams.test_end) - input_shape = (batch_size, 28, 28, 1) - preproc_func = None - elif hparams.dataset == 'cifar10': - X_train, Y_train, X_test, Y_test = cifar_input.read_CIFAR10( - os.path.join(hparams.data_path, hparams.dataset)) - input_shape = (batch_size, 32, 32, 3) - preproc_func = cifar_input.cifar_tf_preprocess - elif hparams.dataset == 'svhn': - X_train, Y_train, X_test, Y_test = svhn_input.read_SVHN( - os.path.join(hparams.data_path, hparams.dataset)) - input_shape = (batch_size, 32, 32, 3) - preproc_func = svhn_input.svhn_tf_preprocess - - # Use label smoothing - assert Y_train.shape[1] == 10. - label_smooth = .1 - Y_train = Y_train.clip(label_smooth / 9., 1. - label_smooth) - - self.X_train = X_train - self.Y_train = Y_train - self.X_test = X_test - self.Y_test = Y_test - self.data = (X_train, Y_train, X_test, Y_test) - self.input_shape = input_shape - self.preproc_func = preproc_func - - def _init_inputs(self): - preproc_func = self.preproc_func - input_shape = self.input_shape - # Define input TF placeholder - with tf.device('/gpu:0'): - x_pre = tf.placeholder(tf.float32, shape=input_shape, name='x') - x = preprocess_batch(x_pre, preproc_func) - y = tf.placeholder(tf.float32, shape=(self.batch_size, 10), - name='y') - - self.g0_inputs = {'x_pre': x_pre, 'x': x, 'y': y} - - def _init_model(self): - flags = self.hparams.__dict__ - # Define TF model graph - model = make_model(input_shape=self.input_shape, **flags) - model.set_device(None) - self.model = model - - def _init_eval(self): - logging.info("Init eval") - x_pre, x, y = [self.g0_inputs[k] for k in ['x_pre', 'x', 'y']] - self.model.set_device('/gpu:0') - self.evaluate = Evaluator(self.sess, self.model, self.batch_size, - x_pre, x, y, - self.data, - self.writer, - self.hparams) - - def eval(self, **kwargs): - if self.evaluate is not None: - self.report = self.evaluate.eval_multi() - - def finish(self): - if self.writer: - self.writer.close() - return self.report - - def _update_learning_params(self): - model = self.model - hparams = self.hparams - fd = self.runner.feed_dict - step_num = self.step_num - - if hparams.model_type == 'resnet_tf': - if step_num < hparams.lrn_step: - lrn_rate = hparams.mom_lrn - elif step_num < 30000: - lrn_rate = hparams.mom_lrn/10 - elif step_num < 35000: - lrn_rate = hparams.mom_lrn/100 - else: - lrn_rate = hparams.mom_lrn/1000 - - fd[model.lrn_rate] = lrn_rate - - def _build_train_op(self, predictions, y, predictions_adv): - model = self.model - hparams = self.hparams - if hparams.model_type == 'resnet_tf': - build_train_op = model.build_cost - else: - raise NotImplementedError("this configuration of this example is no longer maintained") - - # Define loss - with tf.variable_scope('train_loss'): - if predictions_adv is not None: - if hparams.only_adv_train: - loss = build_train_op(y, predictions_adv) - else: - loss = build_train_op(y, predictions) - adv_loss = build_train_op(y, predictions_adv) - loss = (loss + adv_loss) / 2 - else: - loss = build_train_op(y, predictions) - - if hparams.model_type == 'resnet_tf': - train_step = model.build_train_op_from_cost(loss) - else: - optim = tf.train.AdamOptimizer(learning_rate=hparams.adam_lrn) - train_step = optim.minimize(loss) - - return train_step - - def model_train(self): - """ - Train a TF graph - :param sess: TF session to use when training the graph - :param x: input placeholder - :param y: output placeholder (for labels) - :param predictions: model output predictions - :param X_train: numpy array with training inputs - :param Y_train: numpy array with training outputs - :param hparams.save: boolean controlling the save operation - :param predictions_adv: if set with the adversarial example tensor, - will run adversarial training - :param evaluate: function that is run after each training iteration - (typically to display the test/validation accuracy). - """ - - assert self.runner is not None, ( - """Runner is not initialized. TrainerSingleGPU or TrainerMultiGPU - instantiate a Runner object at initialization time.""") - hparams = self.hparams - batch_size = hparams.batch_size - nb_epochs = hparams.nb_epochs - train_dir = hparams.save_dir - filename = 'model.ckpt' - X_train = self.X_train - Y_train = self.Y_train - - sess = self.sess - - with sess.as_default(): - X_batch = X_train[:batch_size] - Y_batch = Y_train[:batch_size] - self._init_tf(X_batch, Y_batch) - - for epoch in six.moves.xrange(nb_epochs): - logging.info("Epoch " + str(epoch)) - - # Compute number of batches - nb_batches = int(math.ceil(float(len(X_train)) / batch_size)) - assert nb_batches * batch_size >= len(X_train) - - # Indices to shuffle training set - index_shuf = list(range(len(X_train))) - self.rng.shuffle(index_shuf) - - prev = time.time() - for batch in range(nb_batches): - # Compute batch start and end indices - start, end = batch_indices( - batch, len(X_train), batch_size) - - # Perform one training step - self._update_learning_params() - - # Train step - X_batch = X_train[index_shuf[start:end]] - Y_batch = Y_train[index_shuf[start:end]] - - self._run({'x_pre': X_batch, 'y': Y_batch}) - self._sync_params() - - # Clean up the queue - while not self.runner.is_finished(): - self._run() - - self._sync_params(forced=True) - - assert end >= len(X_train), ( - 'Not all training examples are used.') - cur = time.time() - logging.info("\tEpoch took " + str(cur - prev) + " seconds") - prev = cur - - self.eval() - - # Save model - cond = ((epoch+1) % hparams.save_steps == 0 - or epoch == nb_epochs) - if hparams.save and cond: - save_path = os.path.join(train_dir, filename) - saver = tf.train.Saver() - saver.save(sess, save_path) - logging.info("Model saved at: " + str(save_path)) - logging.info("Completed model training.") - - def _init_tf(self, X_batch, Y_batch): - x_pre = self.g0_inputs['x_pre'] - y = self.g0_inputs['y'] - fd = {x_pre: X_batch, y: Y_batch} - init_op = tf.global_variables_initializer() - self.sess.run(init_op, feed_dict=fd) - - def _run(self, X_batch=None): - last_fvals = self.runner.run(X_batch) - self.step_num += 1 - return last_fvals - - def _sync_params(self, forced=False): - raise NotImplementedError('sync_params should be implemented.') - - def _create_train_graph(self): - """ - The evaluation graph must be initialized after the train graph is - fully initialized, otherwise, some of the variables will be created - untrainable. - """ - assert self.evaluate is None, ("""Evaluation graph should be initialzed - after the train graph""") - - -class TrainerMultiGPU(TrainManager): - """ - This class uses a `RunnerMultiGPU` object to train a model on multiple - GPUs. It mainly overrides the `_create_train_graph` to create a graph - for adversarial training on multiple GPUs. - """ - - def __init__(self, *args, **kwargs): - super(TrainerMultiGPU, self).__init__(*args, **kwargs) - from runner import RunnerMultiGPU - self.runner = RunnerMultiGPU(self.inputs, self.outputs, sess=self.sess) - - def clone_g0_inputs_on_ngpus(self, inputs, outputs, g0_inputs): - """ - Clone variables unused by the attack on all GPUs. Specifically, the - ground-truth label, y, has to be preserved until the training step. - - :param inputs: A list of dictionaries as the inputs to each step. - :param outputs: A list of dictionaries as the outputs of each step. - :param g0_inputs: Initial variables to be cloned. - :return: Updated inputs and outputs. - """ - assert len(inputs) == len(outputs), ( - 'Inputs and outputs should have the same number of elements.') - - inputs[0].update(g0_inputs) - outputs[0].update(g0_inputs) - - # Copy g0_inputs forward - for i in range(1, len(inputs)): - # Create the graph for i'th step of attack - device_name = inputs[i]['x'].device - with tf.device(device_name): - with tf.variable_scope('step%d' % i): - for k, v in g0_inputs.iteritems(): - if k not in inputs[i]: - v_copy = clone_variable(k, v) - inputs[i][k] = v_copy - outputs[i][k] = v_copy - - return inputs, outputs - - def _create_train_graph(self): - super(TrainerMultiGPU, self)._create_train_graph() - assert '_multigpu' in self.hparams.attack_type_train - - hparams = self.hparams - model = self.model - sess = self.sess - - # Create trainable variables on last gpu. - # Variables are set to trainable or non-trainable first time they are - # created. This caused a bug when the last gpu is used both for attack - # generation and training. With this bug the result of naive training - # was affected by the length of the unused adversarial generation - # graph. - device_name = '/gpu:%d' % (hparams.ngpu-1) - model.set_device(device_name) - with tf.device(device_name): - x = clone_variable('x', self.g0_inputs['x']) - model.set_training(training=True) - preds = model.get_probs(x) - - # Generates steps on gpus - model.set_training(training=False) - logging.info("Initializing train attack %s" % - hparams.attack_type_train) - inputs, outputs = create_adv_by_name( - model, self.g0_inputs['x'], hparams.attack_type_train, - sess, y=self.g0_inputs['y'], nb_iter=hparams.attack_nb_iter_train, - dataset=hparams.dataset, ngpu=hparams.ngpu) - - inputs, outputs = self.clone_g0_inputs_on_ngpus( - inputs, outputs, self.g0_inputs) - - # Train step on last gpu - device_name = '/gpu:%d' % (hparams.ngpu-1) - model.set_device(device_name) - with tf.device(device_name): - with tf.variable_scope('last'): - inputs += [OrderedDict()] - for k, v in outputs[-1].iteritems(): - v_copy = clone_variable(k, v) - inputs[-1][k] = v_copy - x = inputs[-1]['x'] - adv_x = inputs[-1]['adv_x'] - y = inputs[-1]['y'] - if not hparams.adv_train: - model.set_training(training=True) - preds = model.get_probs(x) - preds_adv = None - elif not hparams.only_adv_train: - model.set_training(training=True) - preds = model.get_probs(x) - model.set_training(training=True) - preds_adv = model.get_probs(adv_x) - else: - preds = None - model.set_training(training=True) - preds_adv = model.get_probs(adv_x) - train_fetches = self._build_train_op(preds, y, preds_adv) - - outputs += [{'fetches': train_fetches}] - - # Create the sync operation - device_name = '/gpu:%d' % (hparams.ngpu-1) - model.set_device(device_name) - with tf.device(device_name): - sync_ops = model.create_sync_ops(host_device=device_name) - - self.inputs = inputs - self.outputs = outputs - self.sync_ops = sync_ops - - def _sync_params(self, forced=False): - if forced or (self.step_num % self.hparams.sync_step == 0): - self.sess.run(self.sync_ops) - - -class TrainerSingleGPU(TrainManager): - """ - This class uses a `RunnerSingleGPU` object to train a model on a single - GPU. - """ - - def __init__(self, *args, **kwargs): - super(TrainerSingleGPU, self).__init__(*args, **kwargs) - from runner import RunnerSingleGPU - self.runner = RunnerSingleGPU(self.inputs, self.outputs, - sess=self.sess) - - def _create_train_graph(self): - super(TrainerSingleGPU, self)._create_train_graph() - self.model.set_device('/gpu:0') - hparams = self.hparams - model = self.model - x = self.g0_inputs['x'] - y = self.g0_inputs['y'] - sess = self.sess - - # Create trainable variables. - model.set_training(training=True) - preds = model.get_probs(x) - - if not hparams.adv_train: - logging.info("Naive training") - - model.set_training(training=True) - preds = model.get_probs(x) - preds_adv = None - else: - logging.info("Adversarial training") - logging.info("Initializing train attack %s" % - hparams.attack_type_train) - - model.set_training(training=False) - adv_x = create_adv_by_name( - model, x, hparams.attack_type_train, sess, - y=y, nb_iter=hparams.attack_nb_iter_train, - dataset=hparams.dataset) - if hparams.only_adv_train: - preds = None - model.set_training(training=True) - preds_adv = model.get_probs(adv_x) - else: - model.set_training(training=True) - preds = model.get_probs(x) - model.set_training(training=True) - preds_adv = model.get_probs(adv_x) - train_fetches = self._build_train_op(preds, y, preds_adv) - - self.inputs = [self.g0_inputs] - self.outputs = [train_fetches] - - def _sync_params(self, forced=False): - """ - Nothing to sync on single GPU. - """ - return True diff --git a/examples/multigpu_advtrain/utils.py b/examples/multigpu_advtrain/utils.py deleted file mode 100644 index 4e0483423..000000000 --- a/examples/multigpu_advtrain/utils.py +++ /dev/null @@ -1,25 +0,0 @@ -# pylint: disable=missing-docstring -import tensorflow as tf - - -def preprocess_batch(images_batch, preproc_func=None): - """ - Creates a preprocessing graph for a batch given a function that processes - a single image. - - :param images_batch: A tensor for an image batch. - :param preproc_func: (optional function) A function that takes in a - tensor and returns a preprocessed input. - """ - if preproc_func is None: - return images_batch - - with tf.variable_scope('preprocess'): - images_list = tf.split(images_batch, int(images_batch.shape[0])) - result_list = [] - for img in images_list: - reshaped_img = tf.reshape(img, img.shape[1:]) - processed_img = preproc_func(reshaped_img) - result_list.append(tf.expand_dims(processed_img, axis=0)) - result_images = tf.concat(result_list, axis=0) - return result_images diff --git a/examples/multigpu_advtrain/utils_cifar.py b/examples/multigpu_advtrain/utils_cifar.py deleted file mode 100644 index 91c395cf9..000000000 --- a/examples/multigpu_advtrain/utils_cifar.py +++ /dev/null @@ -1,177 +0,0 @@ -""" -https://github.com/renmengye/revnet-public/blob/master/resnet/data/cifar_input.py - -MIT License - -Copyright (c) 2017 Mengye Ren - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" -# pylint: disable=missing-docstring -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import os -import cPickle as pkl - -import numpy as np -from six.moves import range -import tensorflow as tf - -# Global constants describing the CIFAR-10 data set. -IMAGE_HEIGHT = 32 -IMAGE_WIDTH = 32 -NUM_CLASSES = 10 -NUM_CHANNEL = 3 -NUM_TRAIN_IMG = 50000 -NUM_TEST_IMG = 10000 - - -def unpickle(file): - fo = open(file, 'rb') - dict = pkl.load(fo) - fo.close() - return dict - - -def read_CIFAR10(data_folder): - """ Reads and parses examples from CIFAR10 data files """ - - train_img = [] - train_label = [] - test_img = [] - test_label = [] - - train_file_list = [ - "data_batch_1", "data_batch_2", "data_batch_3", "data_batch_4", - "data_batch_5" - ] - test_file_list = ["test_batch"] - - if "CIFAR10_PATH" in os.environ: - cifar10_path = os.environ["CIFAR10_PATH"] - else: - cifar10_path = 'cifar-10-batches-py' - - for i in range(len(train_file_list)): - tmp_dict = unpickle(os.path.join(data_folder, cifar10_path, - train_file_list[i])) - - train_img.append(tmp_dict["data"]) - train_label.append(tmp_dict["labels"]) - - tmp_dict = unpickle( - os.path.join(data_folder, cifar10_path, test_file_list[0])) - test_img.append(tmp_dict["data"]) - test_label.append(tmp_dict["labels"]) - - train_img = np.concatenate(train_img) - train_label = np.concatenate(train_label) - test_img = np.concatenate(test_img) - test_label = np.concatenate(test_label) - - train_img = np.reshape( - train_img, [NUM_TRAIN_IMG, NUM_CHANNEL, IMAGE_HEIGHT, IMAGE_WIDTH]) - test_img = np.reshape( - test_img, [NUM_TEST_IMG, NUM_CHANNEL, IMAGE_HEIGHT, IMAGE_WIDTH]) - - # change format from [B, C, H, W] to [B, H, W, C] for feeding to Tensorflow - train_img = np.transpose(train_img, [0, 2, 3, 1]) - test_img = np.transpose(test_img, [0, 2, 3, 1]) - - mean_img = np.mean(np.concatenate([train_img, test_img]), axis=0) - - CIFAR10_data = {} - CIFAR10_data["train_img"] = train_img - mean_img - CIFAR10_data["test_img"] = test_img - mean_img - CIFAR10_data["train_label"] = train_label - CIFAR10_data["test_label"] = test_label - - train_img = train_img - mean_img - test_img = test_img - mean_img - train_label = train_label - test_label = test_label - - train_label = np.eye(10)[train_label] - test_label = np.eye(10)[test_label] - - return train_img, train_label, test_img, test_label - - -def read_CIFAR100(data_folder): - """ Reads and parses examples from CIFAR100 python data files """ - - train_img = [] - train_label = [] - test_img = [] - test_label = [] - - train_file_list = ["cifar-100-python/train"] - test_file_list = ["cifar-100-python/test"] - - tmp_dict = unpickle(os.path.join(data_folder, train_file_list[0])) - train_img.append(tmp_dict["data"]) - train_label.append(tmp_dict["fine_labels"]) - - tmp_dict = unpickle(os.path.join(data_folder, test_file_list[0])) - test_img.append(tmp_dict["data"]) - test_label.append(tmp_dict["fine_labels"]) - - train_img = np.concatenate(train_img) - train_label = np.concatenate(train_label) - test_img = np.concatenate(test_img) - test_label = np.concatenate(test_label) - - train_img = np.reshape( - train_img, [NUM_TRAIN_IMG, NUM_CHANNEL, IMAGE_HEIGHT, IMAGE_WIDTH]) - test_img = np.reshape( - test_img, [NUM_TEST_IMG, NUM_CHANNEL, IMAGE_HEIGHT, IMAGE_WIDTH]) - - # change format from [B, C, H, W] to [B, H, W, C] for feeding to Tensorflow - train_img = np.transpose(train_img, [0, 2, 3, 1]) - test_img = np.transpose(test_img, [0, 2, 3, 1]) - mean_img = np.mean(np.concatenate([train_img, test_img]), axis=0) - - CIFAR100_data = {} - CIFAR100_data["train_img"] = train_img - mean_img - CIFAR100_data["test_img"] = test_img - mean_img - CIFAR100_data["train_label"] = train_label - CIFAR100_data["test_label"] = test_label - - return CIFAR100_data - - -def cifar_tf_preprocess(inp, random_crop=True, random_flip=True, whiten=True, - br_sat_con=False): - image_size = 32 - image = inp - if random_crop: - image = tf.image.resize_image_with_crop_or_pad(inp, image_size + 4, - image_size + 4) - image = tf.random_crop(image, [image_size, image_size, 3]) - if random_flip: - image = tf.image.random_flip_left_right(image) - # Brightness/saturation/constrast provides small gains .2%~.5% on cifar. - if br_sat_con: - image = tf.image.random_brightness(image, max_delta=63. / 255.) - image = tf.image.random_saturation(image, lower=0.5, upper=1.5) - image = tf.image.random_contrast(image, lower=0.2, upper=1.8) - if whiten: - image = tf.image.per_image_standardization(image) - return image diff --git a/examples/multigpu_advtrain/utils_svhn.py b/examples/multigpu_advtrain/utils_svhn.py deleted file mode 100644 index f84bc896f..000000000 --- a/examples/multigpu_advtrain/utils_svhn.py +++ /dev/null @@ -1,101 +0,0 @@ -""" -Reading the SVHN dataset. It is derived from CIFAR10 scripts in RevNets code. - -https://github.com/renmengye/revnet-public/blob/master/resnet/data/cifar_input.py - -MIT License - -Copyright (c) 2017 Mengye Ren - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" -# pylint: disable=missing-docstring -from __future__ import (absolute_import, division, print_function, - unicode_literals) - -import os - -import numpy as np -from six.moves import range -import tensorflow as tf -import scipy.io as sio - -# Global constants describing the SVHN data set. -IMAGE_HEIGHT = 32 -IMAGE_WIDTH = 32 -NUM_CLASSES = 10 -NUM_CHANNEL = 3 -NUM_TRAIN_IMG = 73257+531131 -NUM_TEST_IMG = 26032 - - -def read_SVHN(data_folder): - """ Reads and parses examples from SVHN data files """ - - train_img = [] - train_label = [] - test_img = [] - test_label = [] - - train_file_list = [ - 'train_32x32.mat', 'extra_32x32.mat' - ] - test_file_list = ["test_32x32.mat"] - - for i in range(len(train_file_list)): - tmp_dict = sio.loadmat(os.path.join(data_folder, train_file_list[i])) - train_img.append(tmp_dict["X"]) - train_label.append(tmp_dict["y"]) - - tmp_dict = sio.loadmat( - os.path.join(data_folder, test_file_list[0])) - test_img.append(tmp_dict["X"]) - test_label.append(tmp_dict["y"]) - - train_img = np.concatenate(train_img, axis=-1) - train_label = np.concatenate(train_label).flatten() - test_img = np.concatenate(test_img, axis=-1) - test_label = np.concatenate(test_label).flatten() - - # change format from [H, W, C, B] to [B, H, W, C] for feeding to Tensorflow - train_img = np.transpose(train_img, [3, 0, 1, 2]) - test_img = np.transpose(test_img, [3, 0, 1, 2]) - - mean_img = np.mean(np.concatenate([train_img, test_img]), axis=0) - - train_img = train_img - mean_img - test_img = test_img - mean_img - train_y = train_label - 1 # 0-based label - test_y = test_label - 1 # 0-based label - - train_label = np.eye(10)[train_y] - test_label = np.eye(10)[test_y] - - return train_img, train_label, test_img, test_label - - -def svhn_tf_preprocess(inp, random_crop=True): - image_size = 32 - image = inp - if random_crop: - print("Apply random cropping") - image = tf.image.resize_image_with_crop_or_pad(inp, image_size + 4, - image_size + 4) - image = tf.random_crop(image, [image_size, image_size, 3]) - return inp, image diff --git a/examples/nips17_adversarial_competition/dataset/download_images.py b/examples/nips17_adversarial_competition/dataset/download_images.py deleted file mode 100644 index 10036922d..000000000 --- a/examples/nips17_adversarial_competition/dataset/download_images.py +++ /dev/null @@ -1,152 +0,0 @@ -"""Script which downloads dataset images. - -Usage: - python download_images.py --input_file=INPUT_FILE --output_dir=IMAGES_DIR - -where: - INPUT_FILE is input csv file with dataset description, i.e. dev_dataset.csv - IMAGES_DIR is output directory where all images should be downloaded - -Example: - # create directory for images - mkdir images - # download images declared in dev_dataset.csv - python download_images.py --input_file=dev_dataset.csv --output_dir=images - - -Dependencies: - Python 2.7 or higher. - Pillow library: https://python-pillow.org/ -""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import argparse -import csv -import os -import sys -from functools import partial -from io import BytesIO -import multiprocessing -from multiprocessing.dummy import Pool as ThreadPool - -from PIL import Image - -try: - from urllib.request import urlopen -except ImportError: - from urllib2 import urlopen - - -def parse_args(): - """Parses command line arguments.""" - parser = argparse.ArgumentParser( - description='Tool to download dataset images.') - parser.add_argument('--input_file', required=True, - help='Location of dataset.csv') - parser.add_argument('--output_dir', required=True, - help='Output path to download images') - parser.add_argument('--threads', default=multiprocessing.cpu_count() + 1, - help='Number of threads to use') - args = parser.parse_args() - return args.input_file, args.output_dir, int(args.threads) - - -def get_image(row, output_dir): - """Downloads the image that corresponds to the given row. - Prints a notification if the download fails.""" - if not download_image(image_id=row[0], - url=row[1], - x1=float(row[2]), - y1=float(row[3]), - x2=float(row[4]), - y2=float(row[5]), - output_dir=output_dir): - print("Download failed: " + str(row[0])) - - -def download_image(image_id, url, x1, y1, x2, y2, output_dir): - """Downloads one image, crops it, resizes it and saves it locally.""" - output_filename = os.path.join(output_dir, image_id + '.png') - if os.path.exists(output_filename): - # Don't download image if it's already there - return True - try: - # Download image - url_file = urlopen(url) - if url_file.getcode() != 200: - return False - image_buffer = url_file.read() - # Crop, resize and save image - image = Image.open(BytesIO(image_buffer)).convert('RGB') - w = image.size[0] - h = image.size[1] - image = image.crop((int(x1 * w), int(y1 * h), int(x2 * w), - int(y2 * h))) - image = image.resize((299, 299), resample=Image.ANTIALIAS) - image.save(output_filename) - except IOError: - return False - return True - - -def main(): - input_filename, output_dir, n_threads = parse_args() - - if not os.path.isdir(output_dir): - print("Output directory {} does not exist".format(output_dir)) - sys.exit() - - with open(input_filename) as input_file: - reader = csv.reader(input_file) - header_row = next(reader) - rows = list(reader) - try: - row_idx_image_id = header_row.index('ImageId') - row_idx_url = header_row.index('URL') - row_idx_x1 = header_row.index('x1') - row_idx_y1 = header_row.index('y1') - row_idx_x2 = header_row.index('x2') - row_idx_y2 = header_row.index('y2') - except ValueError as e: - print('One of the columns was not found in the source file: ', - e.message) - - rows = [(row[row_idx_image_id], row[row_idx_url], float(row[row_idx_x1]), - float(row[row_idx_y1]), float(row[row_idx_x2]), - float(row[row_idx_y2])) for row in rows] - - if n_threads > 1: - pool = ThreadPool(n_threads) - partial_get_images = partial(get_image, output_dir=output_dir) - for i, _ in enumerate(pool.imap_unordered(partial_get_images, rows), - 1): - sys.stderr.write('\rDownloaded {0} images'.format(i + 1)) - pool.close() - pool.join() - else: - failed_to_download = set() - for idx in range(len(rows)): - row = rows[idx] - if not download_image(image_id=row[0], - url=row[1], - x1=float(row[2]), - y1=float(row[3]), - x2=float(row[4]), - y2=float(row[5]), - output_dir=output_dir): - failed_to_download.add(row[row_idx_image_id]) - sys.stdout.write('\rDownloaded {0} images'.format(idx + 1)) - sys.stdout.flush() - - print() - if failed_to_download: - print('\nUnable to download images with the following IDs:') - for image_id in failed_to_download: - print(image_id) - - -if __name__ == '__main__': - main() diff --git a/examples/nips17_adversarial_competition/dev_toolkit/run_attacks_and_defenses.py b/examples/nips17_adversarial_competition/dev_toolkit/run_attacks_and_defenses.py deleted file mode 100644 index ed88603b8..000000000 --- a/examples/nips17_adversarial_competition/dev_toolkit/run_attacks_and_defenses.py +++ /dev/null @@ -1,551 +0,0 @@ -"""Tool which runs all attacks against all defenses and computes results.""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import argparse -import csv -import json -import os -import subprocess -import numpy as np -from PIL import Image - - -def parse_args(): - """Parses command line arguments.""" - parser = argparse.ArgumentParser( - description='Tool to run attacks and defenses.') - parser.add_argument('--attacks_dir', required=True, - help='Location of all attacks.') - parser.add_argument('--targeted_attacks_dir', required=True, - help='Location of all targeted attacks.') - parser.add_argument('--defenses_dir', required=True, - help='Location of all defenses.') - parser.add_argument('--dataset_dir', required=True, - help='Location of the dataset.') - parser.add_argument('--dataset_metadata', required=True, - help='Location of the dataset metadata.') - parser.add_argument('--intermediate_results_dir', required=True, - help='Directory to store intermediate results.') - parser.add_argument('--output_dir', required=True, - help=('Output directory.')) - parser.add_argument('--epsilon', required=False, type=int, default=16, - help='Maximum allowed size of adversarial perturbation') - parser.add_argument('--gpu', dest='use_gpu', action='store_true') - parser.add_argument('--nogpu', dest='use_gpu', action='store_false') - parser.set_defaults(use_gpu=False) - parser.add_argument('--save_all_classification', - dest='save_all_classification', action='store_true') - parser.add_argument('--nosave_all_classification', - dest='save_all_classification', action='store_false') - parser.set_defaults(save_all_classification=False) - return parser.parse_args() - - -class Submission(object): - """Base class for all submissions.""" - - def __init__(self, directory, container, entry_point, use_gpu): - """Initializes instance of Submission class. - - Args: - directory: location of the submission. - container: URL of Docker container which should be used to run submission. - entry_point: entry point script, which invokes submission. - use_gpu: whether to use Docker with GPU or not. - """ - self.name = os.path.basename(directory) - self.directory = directory - self.container = container - self.entry_point = entry_point - self.use_gpu = use_gpu - - def docker_binary(self): - """Returns appropriate Docker binary to use.""" - return 'nvidia-docker' if self.use_gpu else 'docker' - - -class Attack(Submission): - """Class which stores and runs attack.""" - - def run(self, input_dir, output_dir, epsilon): - """Runs attack inside Docker. - - Args: - input_dir: directory with input (dataset). - output_dir: directory where output (adversarial images) should be written. - epsilon: maximum allowed size of adversarial perturbation, - should be in range [0, 255]. - """ - print('Running attack ', self.name) - cmd = [self.docker_binary(), 'run', - '-v', '{0}:/input_images'.format(input_dir), - '-v', '{0}:/output_images'.format(output_dir), - '-v', '{0}:/code'.format(self.directory), - '-w', '/code', - self.container, - './' + self.entry_point, - '/input_images', - '/output_images', - str(epsilon)] - print(' '.join(cmd)) - subprocess.call(cmd) - - -class Defense(Submission): - """Class which stores and runs defense.""" - - def run(self, input_dir, output_dir): - """Runs defense inside Docker. - - Args: - input_dir: directory with input (adversarial images). - output_dir: directory to write output (classification result). - """ - print('Running defense ', self.name) - cmd = [self.docker_binary(), 'run', - '-v', '{0}:/input_images'.format(input_dir), - '-v', '{0}:/output_data'.format(output_dir), - '-v', '{0}:/code'.format(self.directory), - '-w', '/code', - self.container, - './' + self.entry_point, - '/input_images', - '/output_data/result.csv'] - print(' '.join(cmd)) - subprocess.call(cmd) - - -def read_submissions_from_directory(dirname, use_gpu): - """Scans directory and read all submissions. - - Args: - dirname: directory to scan. - use_gpu: whether submissions should use GPU. This argument is - used to pick proper Docker container for each submission and create - instance of Attack or Defense class. - - Returns: - List with submissions (subclasses of Submission class). - """ - result = [] - for sub_dir in os.listdir(dirname): - submission_path = os.path.join(dirname, sub_dir) - try: - if not os.path.isdir(submission_path): - continue - if not os.path.exists(os.path.join(submission_path, 'metadata.json')): - continue - with open(os.path.join(submission_path, 'metadata.json')) as f: - metadata = json.load(f) - if use_gpu and ('container_gpu' in metadata): - container = metadata['container_gpu'] - else: - container = metadata['container'] - entry_point = metadata['entry_point'] - submission_type = metadata['type'] - if submission_type == 'attack' or submission_type == 'targeted_attack': - submission = Attack(submission_path, container, entry_point, use_gpu) - elif submission_type == 'defense': - submission = Defense(submission_path, container, entry_point, use_gpu) - else: - raise ValueError('Invalid type of submission: %s' % submission_type) - result.append(submission) - except (IOError, KeyError, ValueError): - print('Failed to read submission from directory ', submission_path) - return result - - -class AttacksOutput(object): - """Helper class to store data about images generated by attacks.""" - - def __init__(self, - dataset_dir, - attacks_output_dir, - targeted_attacks_output_dir, - all_adv_examples_dir, - epsilon): - """Initializes instance of AttacksOutput class. - - Args: - dataset_dir: location of the dataset. - attacks_output_dir: where to write results of attacks. - targeted_attacks_output_dir: where to write results of targeted attacks. - all_adv_examples_dir: directory to copy all adversarial examples from - all attacks. - epsilon: maximum allowed size of adversarial perturbation. - """ - self.attacks_output_dir = attacks_output_dir - self.targeted_attacks_output_dir = targeted_attacks_output_dir - self.all_adv_examples_dir = all_adv_examples_dir - self._load_dataset_clipping(dataset_dir, epsilon) - self._output_image_idx = 0 - self._output_to_attack_mapping = {} - self._attack_image_count = 0 - self._targeted_attack_image_count = 0 - self._attack_names = set() - self._targeted_attack_names = set() - - def _load_dataset_clipping(self, dataset_dir, epsilon): - """Helper method which loads dataset and determines clipping range. - - Args: - dataset_dir: location of the dataset. - epsilon: maximum allowed size of adversarial perturbation. - """ - self.dataset_max_clip = {} - self.dataset_min_clip = {} - self._dataset_image_count = 0 - for fname in os.listdir(dataset_dir): - if not fname.endswith('.png'): - continue - image_id = fname[:-4] - image = np.array( - Image.open(os.path.join(dataset_dir, fname)).convert('RGB')) - image = image.astype('int32') - self._dataset_image_count += 1 - self.dataset_max_clip[image_id] = np.clip(image + epsilon, - 0, - 255).astype('uint8') - self.dataset_min_clip[image_id] = np.clip(image - epsilon, - 0, - 255).astype('uint8') - - def clip_and_copy_attack_outputs(self, attack_name, is_targeted): - """Clips results of attack and copy it to directory with all images. - - Args: - attack_name: name of the attack. - is_targeted: if True then attack is targeted, otherwise non-targeted. - """ - if is_targeted: - self._targeted_attack_names.add(attack_name) - else: - self._attack_names.add(attack_name) - attack_dir = os.path.join(self.targeted_attacks_output_dir - if is_targeted - else self.attacks_output_dir, - attack_name) - for fname in os.listdir(attack_dir): - if not (fname.endswith('.png') or fname.endswith('.jpg')): - continue - image_id = fname[:-4] - if image_id not in self.dataset_max_clip: - continue - image_max_clip = self.dataset_max_clip[image_id] - image_min_clip = self.dataset_min_clip[image_id] - adversarial_image = np.array( - Image.open(os.path.join(attack_dir, fname)).convert('RGB')) - clipped_adv_image = np.clip(adversarial_image, - image_min_clip, - image_max_clip) - output_basename = '{0:08d}'.format(self._output_image_idx) - self._output_image_idx += 1 - self._output_to_attack_mapping[output_basename] = (attack_name, - is_targeted, - image_id) - if is_targeted: - self._targeted_attack_image_count += 1 - else: - self._attack_image_count += 1 - Image.fromarray(clipped_adv_image).save( - os.path.join(self.all_adv_examples_dir, output_basename + '.png')) - - @property - def attack_names(self): - """Returns list of all non-targeted attacks.""" - return self._attack_names - - @property - def targeted_attack_names(self): - """Returns list of all targeted attacks.""" - return self._targeted_attack_names - - @property - def attack_image_count(self): - """Returns number of all images generated by non-targeted attacks.""" - return self._attack_image_count - - @property - def dataset_image_count(self): - """Returns number of all images in the dataset.""" - return self._dataset_image_count - - @property - def targeted_attack_image_count(self): - """Returns number of all images generated by targeted attacks.""" - return self._targeted_attack_image_count - - def image_by_base_filename(self, filename): - """Returns information about image based on it's filename.""" - return self._output_to_attack_mapping[filename] - - -class DatasetMetadata(object): - """Helper class which loads and stores dataset metadata.""" - - def __init__(self, filename): - """Initializes instance of DatasetMetadata.""" - self._true_labels = {} - self._target_classes = {} - with open(filename) as f: - reader = csv.reader(f) - header_row = next(reader) - try: - row_idx_image_id = header_row.index('ImageId') - row_idx_true_label = header_row.index('TrueLabel') - row_idx_target_class = header_row.index('TargetClass') - except ValueError: - raise IOError('Invalid format of dataset metadata.') - for row in reader: - if len(row) < len(header_row): - # skip partial or empty lines - continue - try: - image_id = row[row_idx_image_id] - self._true_labels[image_id] = int(row[row_idx_true_label]) - self._target_classes[image_id] = int(row[row_idx_target_class]) - except (IndexError, ValueError): - raise IOError('Invalid format of dataset metadata') - - def get_true_label(self, image_id): - """Returns true label for image with given ID.""" - return self._true_labels[image_id] - - def get_target_class(self, image_id): - """Returns target class for image with given ID.""" - return self._target_classes[image_id] - - def save_target_classes(self, filename): - """Saves target classed for all dataset images into given file.""" - with open(filename, 'w') as f: - for k, v in self._target_classes.items(): - f.write('{0}.png,{1}\n'.format(k, v)) - - -def load_defense_output(filename): - """Loads output of defense from given file.""" - result = {} - with open(filename) as f: - for row in csv.reader(f): - try: - image_filename = row[0] - if image_filename.endswith('.png') or image_filename.endswith('.jpg'): - image_filename = image_filename[:image_filename.rfind('.')] - label = int(row[1]) - except (IndexError, ValueError): - continue - result[image_filename] = label - return result - - -def compute_and_save_scores_and_ranking(attacks_output, - defenses_output, - dataset_meta, - output_dir, - save_all_classification=False): - """Computes scores and ranking and saves it. - - Args: - attacks_output: output of attacks, instance of AttacksOutput class. - defenses_output: outputs of defenses. Dictionary of dictionaries, key in - outer dictionary is name of the defense, key of inner dictionary is - name of the image, value of inner dictionary is classification label. - dataset_meta: dataset metadata, instance of DatasetMetadata class. - output_dir: output directory where results will be saved. - save_all_classification: If True then classification results of all - defenses on all images produces by all attacks will be saved into - all_classification.csv file. Useful for debugging. - - This function saves following files into output directory: - accuracy_on_attacks.csv: matrix with number of correctly classified images - for each pair of defense and attack. - accuracy_on_targeted_attacks.csv: matrix with number of correctly classified - images for each pair of defense and targeted attack. - hit_target_class.csv: matrix with number of times defense classified image - as specified target class for each pair of defense and targeted attack. - defense_ranking.csv: ranking and scores of all defenses. - attack_ranking.csv: ranking and scores of all attacks. - targeted_attack_ranking.csv: ranking and scores of all targeted attacks. - all_classification.csv: results of classification of all defenses on - all images produced by all attacks. Only saved if save_all_classification - argument is True. - """ - def write_ranking(filename, header, names, scores): - """Helper method which saves submissions' scores and names.""" - order = np.argsort(scores)[::-1] - with open(filename, 'w') as f: - writer = csv.writer(f) - writer.writerow(header) - for idx in order: - writer.writerow([names[idx], scores[idx]]) - - def write_score_matrix(filename, scores, row_names, column_names): - """Helper method which saves score matrix.""" - result = np.pad(scores, ((1, 0), (1, 0)), 'constant').astype(np.object) - result[0, 0] = '' - result[1:, 0] = row_names - result[0, 1:] = column_names - np.savetxt(filename, result, fmt='%s', delimiter=',') - - attack_names = list(attacks_output.attack_names) - attack_names_idx = {name: index for index, name in enumerate(attack_names)} - targeted_attack_names = list(attacks_output.targeted_attack_names) - targeted_attack_names_idx = {name: index - for index, name - in enumerate(targeted_attack_names)} - defense_names = list(defenses_output.keys()) - defense_names_idx = {name: index for index, name in enumerate(defense_names)} - - # In the matrices below: rows - attacks, columns - defenses. - accuracy_on_attacks = np.zeros( - (len(attack_names), len(defense_names)), dtype=np.int32) - accuracy_on_targeted_attacks = np.zeros( - (len(targeted_attack_names), len(defense_names)), dtype=np.int32) - hit_target_class = np.zeros( - (len(targeted_attack_names), len(defense_names)), dtype=np.int32) - - for defense_name, defense_result in defenses_output.items(): - for image_filename, predicted_label in defense_result.items(): - attack_name, is_targeted, image_id = ( - attacks_output.image_by_base_filename(image_filename)) - true_label = dataset_meta.get_true_label(image_id) - defense_idx = defense_names_idx[defense_name] - if is_targeted: - target_class = dataset_meta.get_target_class(image_id) - if true_label == predicted_label: - attack_idx = targeted_attack_names_idx[attack_name] - accuracy_on_targeted_attacks[attack_idx, defense_idx] += 1 - if target_class == predicted_label: - attack_idx = targeted_attack_names_idx[attack_name] - hit_target_class[attack_idx, defense_idx] += 1 - else: - if true_label == predicted_label: - attack_idx = attack_names_idx[attack_name] - accuracy_on_attacks[attack_idx, defense_idx] += 1 - - # Save matrices. - write_score_matrix(os.path.join(output_dir, 'accuracy_on_attacks.csv'), - accuracy_on_attacks, attack_names, defense_names) - write_score_matrix( - os.path.join(output_dir, 'accuracy_on_targeted_attacks.csv'), - accuracy_on_targeted_attacks, targeted_attack_names, defense_names) - write_score_matrix(os.path.join(output_dir, 'hit_target_class.csv'), - hit_target_class, targeted_attack_names, defense_names) - - # Compute and save scores and ranking of attacks and defenses, - # higher scores are better. - defense_scores = (np.sum(accuracy_on_attacks, axis=0) - + np.sum(accuracy_on_targeted_attacks, axis=0)) - attack_scores = (attacks_output.dataset_image_count * len(defenses_output) - - np.sum(accuracy_on_attacks, axis=1)) - targeted_attack_scores = np.sum(hit_target_class, axis=1) - write_ranking(os.path.join(output_dir, 'defense_ranking.csv'), - ['DefenseName', 'Score'], defense_names, defense_scores) - write_ranking(os.path.join(output_dir, 'attack_ranking.csv'), - ['AttackName', 'Score'], attack_names, attack_scores) - write_ranking( - os.path.join(output_dir, 'targeted_attack_ranking.csv'), - ['AttackName', 'Score'], targeted_attack_names, targeted_attack_scores) - - if save_all_classification: - with open(os.path.join(output_dir, 'all_classification.csv'), 'w') as f: - writer = csv.writer(f) - writer.writerow(['AttackName', 'IsTargeted', 'DefenseName', 'ImageId', - 'PredictedLabel', 'TrueLabel', 'TargetClass']) - for defense_name, defense_result in defenses_output.items(): - for image_filename, predicted_label in defense_result.items(): - attack_name, is_targeted, image_id = ( - attacks_output.image_by_base_filename(image_filename)) - true_label = dataset_meta.get_true_label(image_id) - target_class = dataset_meta.get_target_class(image_id) - writer.writerow([attack_name, is_targeted, defense_name, image_id, - predicted_label, true_label, target_class]) - - -def main(): - """Run all attacks against all defenses and compute results. - """ - args = parse_args() - attacks_output_dir = os.path.join(args.intermediate_results_dir, - 'attacks_output') - targeted_attacks_output_dir = os.path.join(args.intermediate_results_dir, - 'targeted_attacks_output') - defenses_output_dir = os.path.join(args.intermediate_results_dir, - 'defenses_output') - all_adv_examples_dir = os.path.join(args.intermediate_results_dir, - 'all_adv_examples') - - # Load dataset metadata. - dataset_meta = DatasetMetadata(args.dataset_metadata) - - # Load attacks and defenses. - attacks = [ - a for a in read_submissions_from_directory(args.attacks_dir, - args.use_gpu) - if isinstance(a, Attack) - ] - targeted_attacks = [ - a for a in read_submissions_from_directory(args.targeted_attacks_dir, - args.use_gpu) - if isinstance(a, Attack) - ] - defenses = [ - d for d in read_submissions_from_directory(args.defenses_dir, - args.use_gpu) - if isinstance(d, Defense) - ] - print('Found attacks: ', [a.name for a in attacks]) - print('Found tageted attacks: ', [a.name for a in targeted_attacks]) - print('Found defenses: ', [d.name for d in defenses]) - - # Prepare subdirectories for intermediate results. - os.mkdir(attacks_output_dir) - os.mkdir(targeted_attacks_output_dir) - os.mkdir(defenses_output_dir) - os.mkdir(all_adv_examples_dir) - for a in attacks: - os.mkdir(os.path.join(attacks_output_dir, a.name)) - for a in targeted_attacks: - os.mkdir(os.path.join(targeted_attacks_output_dir, a.name)) - for d in defenses: - os.mkdir(os.path.join(defenses_output_dir, d.name)) - - # Run all non-targeted attacks. - attacks_output = AttacksOutput(args.dataset_dir, - attacks_output_dir, - targeted_attacks_output_dir, - all_adv_examples_dir, - args.epsilon) - for a in attacks: - a.run(args.dataset_dir, - os.path.join(attacks_output_dir, a.name), - args.epsilon) - attacks_output.clip_and_copy_attack_outputs(a.name, False) - - # Run all targeted attacks. - dataset_meta.save_target_classes(os.path.join(args.dataset_dir, - 'target_class.csv')) - for a in targeted_attacks: - a.run(args.dataset_dir, - os.path.join(targeted_attacks_output_dir, a.name), - args.epsilon) - attacks_output.clip_and_copy_attack_outputs(a.name, True) - - # Run all defenses. - defenses_output = {} - for d in defenses: - d.run(all_adv_examples_dir, os.path.join(defenses_output_dir, d.name)) - defenses_output[d.name] = load_defense_output( - os.path.join(defenses_output_dir, d.name, 'result.csv')) - - # Compute and save scoring. - compute_and_save_scores_and_ranking(attacks_output, defenses_output, - dataset_meta, args.output_dir, - args.save_all_classification) - - -if __name__ == '__main__': - main() diff --git a/examples/nips17_adversarial_competition/dev_toolkit/sample_attacks/fgsm/attack_fgsm.py b/examples/nips17_adversarial_competition/dev_toolkit/sample_attacks/fgsm/attack_fgsm.py deleted file mode 100644 index ee012ad89..000000000 --- a/examples/nips17_adversarial_competition/dev_toolkit/sample_attacks/fgsm/attack_fgsm.py +++ /dev/null @@ -1,161 +0,0 @@ -"""Implementation of sample attack.""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import os -import warnings - -import numpy as np -import tensorflow as tf -from tensorflow.contrib.slim.nets import inception -from PIL import Image - -from cleverhans.attacks import FastGradientMethod - -slim = tf.contrib.slim - - -tf.flags.DEFINE_string( - 'master', '', 'The address of the TensorFlow master to use.') - -tf.flags.DEFINE_string( - 'checkpoint_path', '', 'Path to checkpoint for inception network.') - -tf.flags.DEFINE_string( - 'input_dir', '', 'Input directory with images.') - -tf.flags.DEFINE_string( - 'output_dir', '', 'Output directory with images.') - -tf.flags.DEFINE_float( - 'max_epsilon', 16.0, 'Maximum size of adversarial perturbation.') - -tf.flags.DEFINE_integer( - 'image_width', 299, 'Width of each input images.') - -tf.flags.DEFINE_integer( - 'image_height', 299, 'Height of each input images.') - -tf.flags.DEFINE_integer( - 'batch_size', 16, 'How many images process at one time.') - -FLAGS = tf.flags.FLAGS - - -def load_images(input_dir, batch_shape): - """Read png images from input directory in batches. - - Args: - input_dir: input directory - batch_shape: shape of minibatch array, i.e. [batch_size, height, width, 3] - - Yields: - filenames: list file names without path of each image - Lenght of this list could be less than batch_size, in this case only - first few images of the result are elements of the minibatch. - images: array with all images from this batch - """ - images = np.zeros(batch_shape) - filenames = [] - idx = 0 - batch_size = batch_shape[0] - for filepath in tf.gfile.Glob(os.path.join(input_dir, '*.png')): - with tf.gfile.Open(filepath) as f: - image = np.array(Image.open(f).convert('RGB')).astype(np.float) / 255.0 - # Images for inception classifier are normalized to be in [-1, 1] interval. - images[idx, :, :, :] = image * 2.0 - 1.0 - filenames.append(os.path.basename(filepath)) - idx += 1 - if idx == batch_size: - yield filenames, images - filenames = [] - images = np.zeros(batch_shape) - idx = 0 - if idx > 0: - yield filenames, images - - -def save_images(images, filenames, output_dir): - """Saves images to the output directory. - - Args: - images: array with minibatch of images - filenames: list of filenames without path - If number of file names in this list less than number of images in - the minibatch then only first len(filenames) images will be saved. - output_dir: directory where to save images - """ - for i, filename in enumerate(filenames): - # Images for inception classifier are normalized to be in [-1, 1] interval, - # so rescale them back to [0, 1]. - with tf.gfile.Open(os.path.join(output_dir, filename), 'w') as f: - img = (((images[i, :, :, :] + 1.0) * 0.5) * 255.0).astype(np.uint8) - Image.fromarray(img).save(f, format='PNG') - - -class InceptionModel(object): - """Model class for CleverHans library.""" - - def __init__(self, nb_classes=None, num_classes=None): - if num_classes is not None: - if nb_classes is not None: - raise ValueError("Should not specify both nb_classes and its deprecated" - " alias, num_classes") - warnings.warn("`num_classes` is deprecated. Switch to `nb_classes`." - " `num_classes` may be removed on or after 2019-04-23.") - nb_classes = num_classes - del num_classes - self.nb_classes = nb_classes - self.built = False - - def __call__(self, x_input): - """Constructs model and return probabilities for given input.""" - reuse = True if self.built else None - with slim.arg_scope(inception.inception_v3_arg_scope()): - _, end_points = inception.inception_v3( - x_input, num_classes=self.nb_classes, is_training=False, - reuse=reuse) - self.built = True - output = end_points['Predictions'] - # Strip off the extra reshape op at the output - probs = output.op.inputs[0] - return probs - - -def main(_): - """Run the sample attack""" - # Images for inception classifier are normalized to be in [-1, 1] interval, - # eps is a difference between pixels so it should be in [0, 2] interval. - # Renormalizing epsilon from [0, 255] to [0, 2]. - eps = 2.0 * FLAGS.max_epsilon / 255.0 - batch_shape = [FLAGS.batch_size, FLAGS.image_height, FLAGS.image_width, 3] - nb_classes = 1001 - - tf.logging.set_verbosity(tf.logging.INFO) - - with tf.Graph().as_default(): - # Prepare graph - x_input = tf.placeholder(tf.float32, shape=batch_shape) - - model = InceptionModel(nb_classes) - - fgsm = FastGradientMethod(model) - x_adv = fgsm.generate(x_input, eps=eps, clip_min=-1., clip_max=1.) - - # Run computation - saver = tf.train.Saver(slim.get_model_variables()) - session_creator = tf.train.ChiefSessionCreator( - scaffold=tf.train.Scaffold(saver=saver), - checkpoint_filename_with_path=FLAGS.checkpoint_path, - master=FLAGS.master) - - with tf.train.MonitoredSession(session_creator=session_creator) as sess: - for filenames, images in load_images(FLAGS.input_dir, batch_shape): - adv_images = sess.run(x_adv, feed_dict={x_input: images}) - save_images(adv_images, filenames, FLAGS.output_dir) - - -if __name__ == '__main__': - tf.app.run() diff --git a/examples/nips17_adversarial_competition/dev_toolkit/sample_attacks/noop/attack_noop.py b/examples/nips17_adversarial_competition/dev_toolkit/sample_attacks/noop/attack_noop.py deleted file mode 100644 index 8a26c49df..000000000 --- a/examples/nips17_adversarial_competition/dev_toolkit/sample_attacks/noop/attack_noop.py +++ /dev/null @@ -1,94 +0,0 @@ -"""Implementation of sample attack.""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import os -import numpy as np - -from scipy.misc import imread -from scipy.misc import imsave - -import tensorflow as tf - - -tf.flags.DEFINE_string( - 'master', '', 'The address of the TensorFlow master to use.') - -tf.flags.DEFINE_string( - 'input_dir', '', 'Input directory with images.') - -tf.flags.DEFINE_string( - 'output_dir', '', 'Output directory with images.') - -tf.flags.DEFINE_float( - 'max_epsilon', 16.0, 'Maximum size of adversarial perturbation.') - -tf.flags.DEFINE_integer( - 'image_width', 299, 'Width of each input images.') - -tf.flags.DEFINE_integer( - 'image_height', 299, 'Height of each input images.') - -tf.flags.DEFINE_integer( - 'batch_size', 16, 'How many images process at one time.') - -FLAGS = tf.flags.FLAGS - - -def load_images(input_dir, batch_shape): - """Read png images from input directory in batches. - - Args: - input_dir: input directory - batch_shape: shape of minibatch array, i.e. [batch_size, height, width, 3] - - Yields: - filenames: list file names without path of each image - Length of this list could be less than batch_size, in this case only - first few images of the result are elements of the minibatch. - images: array with all images from this batch - """ - images = np.zeros(batch_shape) - filenames = [] - idx = 0 - batch_size = batch_shape[0] - for filepath in tf.gfile.Glob(os.path.join(input_dir, '*.png')): - with tf.gfile.Open(filepath) as f: - images[idx, :, :, :] = imread(f, mode='RGB').astype(np.float) / 255.0 - filenames.append(os.path.basename(filepath)) - idx += 1 - if idx == batch_size: - yield filenames, images - filenames = [] - images = np.zeros(batch_shape) - idx = 0 - if idx > 0: - yield filenames, images - - -def save_images(images, filenames, output_dir): - """Saves images to the output directory. - - Args: - images: array with minibatch of images - filenames: list of filenames without path - If number of file names in this list less than number of images in - the minibatch then only first len(filenames) images will be saved. - output_dir: directory where to save images - """ - for i, filename in enumerate(filenames): - with tf.gfile.Open(os.path.join(output_dir, filename), 'w') as f: - imsave(f, images[i, :, :, :], format='png') - - -def main(_): - """Run the sample attack""" - batch_shape = [FLAGS.batch_size, FLAGS.image_height, FLAGS.image_width, 3] - for filenames, images in load_images(FLAGS.input_dir, batch_shape): - save_images(images, filenames, FLAGS.output_dir) - - -if __name__ == '__main__': - tf.app.run() diff --git a/examples/nips17_adversarial_competition/dev_toolkit/sample_attacks/random_noise/attack_random_noise.py b/examples/nips17_adversarial_competition/dev_toolkit/sample_attacks/random_noise/attack_random_noise.py deleted file mode 100644 index 828b1dfce..000000000 --- a/examples/nips17_adversarial_competition/dev_toolkit/sample_attacks/random_noise/attack_random_noise.py +++ /dev/null @@ -1,103 +0,0 @@ -"""Implementation of sample attack.""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import os -import numpy as np - -from scipy.misc import imread -from scipy.misc import imsave - -import tensorflow as tf - - -tf.flags.DEFINE_string( - 'master', '', 'The address of the TensorFlow master to use.') - -tf.flags.DEFINE_string( - 'input_dir', '', 'Input directory with images.') - -tf.flags.DEFINE_string( - 'output_dir', '', 'Output directory with images.') - -tf.flags.DEFINE_float( - 'max_epsilon', 16.0, 'Maximum size of adversarial perturbation.') - -tf.flags.DEFINE_integer( - 'image_width', 299, 'Width of each input images.') - -tf.flags.DEFINE_integer( - 'image_height', 299, 'Height of each input images.') - -tf.flags.DEFINE_integer( - 'batch_size', 16, 'How many images process at one time.') - -FLAGS = tf.flags.FLAGS - - -def load_images(input_dir, batch_shape): - """Read png images from input directory in batches. - - Args: - input_dir: input directory - batch_shape: shape of minibatch array, i.e. [batch_size, height, width, 3] - - Yields: - filenames: list file names without path of each image - Lenght of this list could be less than batch_size, in this case only - first few images of the result are elements of the minibatch. - images: array with all images from this batch - """ - images = np.zeros(batch_shape) - filenames = [] - idx = 0 - batch_size = batch_shape[0] - for filepath in tf.gfile.Glob(os.path.join(input_dir, '*.png')): - with tf.gfile.Open(filepath) as f: - images[idx, :, :, :] = imread(f, mode='RGB').astype(np.float) / 255.0 - filenames.append(os.path.basename(filepath)) - idx += 1 - if idx == batch_size: - yield filenames, images - filenames = [] - images = np.zeros(batch_shape) - idx = 0 - if idx > 0: - yield filenames, images - - -def save_images(images, filenames, output_dir): - """Saves images to the output directory. - - Args: - images: array with minibatch of images - filenames: list of filenames without path - If number of file names in this list less than number of images in - the minibatch then only first len(filenames) images will be saved. - output_dir: directory where to save images - """ - for i, filename in enumerate(filenames): - with tf.gfile.Open(os.path.join(output_dir, filename), 'w') as f: - imsave(f, images[i, :, :, :], format='png') - - -def main(_): - """Run the sample attack""" - eps = FLAGS.max_epsilon / 255.0 - batch_shape = [FLAGS.batch_size, FLAGS.image_height, FLAGS.image_width, 3] - - with tf.Graph().as_default(): - x_input = tf.placeholder(tf.float32, shape=batch_shape) - noisy_images = x_input + eps * tf.sign(tf.random_normal(batch_shape)) - x_output = tf.clip_by_value(noisy_images, 0.0, 1.0) - - with tf.Session(FLAGS.master) as sess: - for filenames, images in load_images(FLAGS.input_dir, batch_shape): - out_images = sess.run(x_output, feed_dict={x_input: images}) - save_images(out_images, filenames, FLAGS.output_dir) - - -if __name__ == '__main__': - tf.app.run() diff --git a/examples/nips17_adversarial_competition/dev_toolkit/sample_defenses/adv_inception_v3/defense.py b/examples/nips17_adversarial_competition/dev_toolkit/sample_defenses/adv_inception_v3/defense.py deleted file mode 100644 index 1a27c492d..000000000 --- a/examples/nips17_adversarial_competition/dev_toolkit/sample_defenses/adv_inception_v3/defense.py +++ /dev/null @@ -1,112 +0,0 @@ -"""Implementation of sample defense. - -This defense loads inception v3 checkpoint and classifies all images -using loaded checkpoint. -""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import os - -import numpy as np -from scipy.misc import imread - -import tensorflow as tf -from tensorflow.contrib.slim.nets import inception - -slim = tf.contrib.slim - - -tf.flags.DEFINE_string( - 'master', '', 'The address of the TensorFlow master to use.') - -tf.flags.DEFINE_string( - 'checkpoint_path', '', 'Path to checkpoint for inception network.') - -tf.flags.DEFINE_string( - 'input_dir', '', 'Input directory with images.') - -tf.flags.DEFINE_string( - 'output_file', '', 'Output file to save labels.') - -tf.flags.DEFINE_integer( - 'image_width', 299, 'Width of each input images.') - -tf.flags.DEFINE_integer( - 'image_height', 299, 'Height of each input images.') - -tf.flags.DEFINE_integer( - 'batch_size', 16, 'How many images process at one time.') - -FLAGS = tf.flags.FLAGS - - -def load_images(input_dir, batch_shape): - """Read png images from input directory in batches. - - Args: - input_dir: input directory - batch_shape: shape of minibatch array, i.e. [batch_size, height, width, 3] - - Yields: - filenames: list file names without path of each image - Lenght of this list could be less than batch_size, in this case only - first few images of the result are elements of the minibatch. - images: array with all images from this batch - """ - images = np.zeros(batch_shape) - filenames = [] - idx = 0 - batch_size = batch_shape[0] - for filepath in tf.gfile.Glob(os.path.join(input_dir, '*.png')): - with tf.gfile.Open(filepath) as f: - image = imread(f, mode='RGB').astype(np.float) / 255.0 - # Images for inception classifier are normalized to be in [-1, 1] interval. - images[idx, :, :, :] = image * 2.0 - 1.0 - filenames.append(os.path.basename(filepath)) - idx += 1 - if idx == batch_size: - yield filenames, images - filenames = [] - images = np.zeros(batch_shape) - idx = 0 - if idx > 0: - yield filenames, images - - -def main(_): - """Run the sample defense""" - batch_shape = [FLAGS.batch_size, FLAGS.image_height, FLAGS.image_width, 3] - nb_classes = 1001 - - tf.logging.set_verbosity(tf.logging.INFO) - - with tf.Graph().as_default(): - # Prepare graph - x_input = tf.placeholder(tf.float32, shape=batch_shape) - - with slim.arg_scope(inception.inception_v3_arg_scope()): - _, end_points = inception.inception_v3( - x_input, num_classes=nb_classes, is_training=False) - - predicted_labels = tf.argmax(end_points['Predictions'], 1) - - # Run computation - saver = tf.train.Saver(slim.get_model_variables()) - session_creator = tf.train.ChiefSessionCreator( - scaffold=tf.train.Scaffold(saver=saver), - checkpoint_filename_with_path=FLAGS.checkpoint_path, - master=FLAGS.master) - - with tf.train.MonitoredSession(session_creator=session_creator) as sess: - with tf.gfile.Open(FLAGS.output_file, 'w') as out_file: - for filenames, images in load_images(FLAGS.input_dir, batch_shape): - labels = sess.run(predicted_labels, feed_dict={x_input: images}) - for filename, label in zip(filenames, labels): - out_file.write('{0},{1}\n'.format(filename, label)) - - -if __name__ == '__main__': - tf.app.run() diff --git a/examples/nips17_adversarial_competition/dev_toolkit/sample_defenses/base_inception_model/defense.py b/examples/nips17_adversarial_competition/dev_toolkit/sample_defenses/base_inception_model/defense.py deleted file mode 100644 index 861b3e85b..000000000 --- a/examples/nips17_adversarial_competition/dev_toolkit/sample_defenses/base_inception_model/defense.py +++ /dev/null @@ -1,111 +0,0 @@ -"""Implementation of sample defense. - -This defense loads inception v3 checkpoint and classifies all images -using loaded checkpoint. -""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import os - -import numpy as np -from scipy.misc import imread - -import tensorflow as tf -from tensorflow.contrib.slim.nets import inception - -slim = tf.contrib.slim - - -tf.flags.DEFINE_string( - 'master', '', 'The address of the TensorFlow master to use.') - -tf.flags.DEFINE_string( - 'checkpoint_path', '', 'Path to checkpoint for inception network.') - -tf.flags.DEFINE_string( - 'input_dir', '', 'Input directory with images.') - -tf.flags.DEFINE_string( - 'output_file', '', 'Output file to save labels.') - -tf.flags.DEFINE_integer( - 'image_width', 299, 'Width of each input images.') - -tf.flags.DEFINE_integer( - 'image_height', 299, 'Height of each input images.') - -tf.flags.DEFINE_integer( - 'batch_size', 16, 'How many images process at one time.') - -FLAGS = tf.flags.FLAGS - - -def load_images(input_dir, batch_shape): - """Read png images from input directory in batches. - - Args: - input_dir: input directory - batch_shape: shape of minibatch array, i.e. [batch_size, height, width, 3] - - Yields: - filenames: list file names without path of each image - Lenght of this list could be less than batch_size, in this case only - first few images of the result are elements of the minibatch. - images: array with all images from this batch - """ - images = np.zeros(batch_shape) - filenames = [] - idx = 0 - batch_size = batch_shape[0] - for filepath in tf.gfile.Glob(os.path.join(input_dir, '*.png')): - with tf.gfile.Open(filepath) as f: - image = imread(f, mode='RGB').astype(np.float) / 255.0 - # Images for inception classifier are normalized to be in [-1, 1] interval. - images[idx, :, :, :] = image * 2.0 - 1.0 - filenames.append(os.path.basename(filepath)) - idx += 1 - if idx == batch_size: - yield filenames, images - filenames = [] - images = np.zeros(batch_shape) - idx = 0 - if idx > 0: - yield filenames, images - - -def main(_): - batch_shape = [FLAGS.batch_size, FLAGS.image_height, FLAGS.image_width, 3] - nb_classes = 1001 - - tf.logging.set_verbosity(tf.logging.INFO) - - with tf.Graph().as_default(): - # Prepare graph - x_input = tf.placeholder(tf.float32, shape=batch_shape) - - with slim.arg_scope(inception.inception_v3_arg_scope()): - _, end_points = inception.inception_v3( - x_input, num_classes=nb_classes, is_training=False) - - predicted_labels = tf.argmax(end_points['Predictions'], 1) - - # Run computation - saver = tf.train.Saver(slim.get_model_variables()) - session_creator = tf.train.ChiefSessionCreator( - scaffold=tf.train.Scaffold(saver=saver), - checkpoint_filename_with_path=FLAGS.checkpoint_path, - master=FLAGS.master) - - with tf.train.MonitoredSession(session_creator=session_creator) as sess: - with tf.gfile.Open(FLAGS.output_file, 'w') as out_file: - for filenames, images in load_images(FLAGS.input_dir, batch_shape): - labels = sess.run(predicted_labels, feed_dict={x_input: images}) - for filename, label in zip(filenames, labels): - out_file.write('{0},{1}\n'.format(filename, label)) - - -if __name__ == '__main__': - tf.app.run() diff --git a/examples/nips17_adversarial_competition/dev_toolkit/sample_defenses/ens_adv_inception_resnet_v2/defense.py b/examples/nips17_adversarial_competition/dev_toolkit/sample_defenses/ens_adv_inception_resnet_v2/defense.py deleted file mode 100644 index c257c6c90..000000000 --- a/examples/nips17_adversarial_competition/dev_toolkit/sample_defenses/ens_adv_inception_resnet_v2/defense.py +++ /dev/null @@ -1,113 +0,0 @@ -"""Implementation of sample defense. - -This defense loads inception resnet v2 checkpoint and classifies all images -using loaded checkpoint. -""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import os - -import numpy as np -from scipy.misc import imread - -import tensorflow as tf - -import inception_resnet_v2 - -slim = tf.contrib.slim - - -tf.flags.DEFINE_string( - 'master', '', 'The address of the TensorFlow master to use.') - -tf.flags.DEFINE_string( - 'checkpoint_path', '', 'Path to checkpoint for inception network.') - -tf.flags.DEFINE_string( - 'input_dir', '', 'Input directory with images.') - -tf.flags.DEFINE_string( - 'output_file', '', 'Output file to save labels.') - -tf.flags.DEFINE_integer( - 'image_width', 299, 'Width of each input images.') - -tf.flags.DEFINE_integer( - 'image_height', 299, 'Height of each input images.') - -tf.flags.DEFINE_integer( - 'batch_size', 16, 'How many images process at one time.') - -FLAGS = tf.flags.FLAGS - - -def load_images(input_dir, batch_shape): - """Read png images from input directory in batches. - - Args: - input_dir: input directory - batch_shape: shape of minibatch array, i.e. [batch_size, height, width, 3] - - Yields: - filenames: list file names without path of each image - Lenght of this list could be less than batch_size, in this case only - first few images of the result are elements of the minibatch. - images: array with all images from this batch - """ - images = np.zeros(batch_shape) - filenames = [] - idx = 0 - batch_size = batch_shape[0] - for filepath in tf.gfile.Glob(os.path.join(input_dir, '*.png')): - with tf.gfile.Open(filepath) as f: - image = imread(f, mode='RGB').astype(np.float) / 255.0 - # Images for inception classifier are normalized to be in [-1, 1] interval. - images[idx, :, :, :] = image * 2.0 - 1.0 - filenames.append(os.path.basename(filepath)) - idx += 1 - if idx == batch_size: - yield filenames, images - filenames = [] - images = np.zeros(batch_shape) - idx = 0 - if idx > 0: - yield filenames, images - - -def main(_): - """Classify all images using the sample defense.""" - batch_shape = [FLAGS.batch_size, FLAGS.image_height, FLAGS.image_width, 3] - nb_classes = 1001 - - tf.logging.set_verbosity(tf.logging.INFO) - - with tf.Graph().as_default(): - # Prepare graph - x_input = tf.placeholder(tf.float32, shape=batch_shape) - - with slim.arg_scope(inception_resnet_v2.inception_resnet_v2_arg_scope()): - _, end_points = inception_resnet_v2.inception_resnet_v2( - x_input, num_classes=nb_classes, is_training=False) - - predicted_labels = tf.argmax(end_points['Predictions'], 1) - - # Run computation - saver = tf.train.Saver(slim.get_model_variables()) - session_creator = tf.train.ChiefSessionCreator( - scaffold=tf.train.Scaffold(saver=saver), - checkpoint_filename_with_path=FLAGS.checkpoint_path, - master=FLAGS.master) - - with tf.train.MonitoredSession(session_creator=session_creator) as sess: - with tf.gfile.Open(FLAGS.output_file, 'w') as out_file: - for filenames, images in load_images(FLAGS.input_dir, batch_shape): - labels = sess.run(predicted_labels, feed_dict={x_input: images}) - for filename, label in zip(filenames, labels): - out_file.write('{0},{1}\n'.format(filename, label)) - - -if __name__ == '__main__': - tf.app.run() diff --git a/examples/nips17_adversarial_competition/dev_toolkit/sample_defenses/ens_adv_inception_resnet_v2/inception_resnet_v2.py b/examples/nips17_adversarial_competition/dev_toolkit/sample_defenses/ens_adv_inception_resnet_v2/inception_resnet_v2.py deleted file mode 100644 index 564a38a32..000000000 --- a/examples/nips17_adversarial_competition/dev_toolkit/sample_defenses/ens_adv_inception_resnet_v2/inception_resnet_v2.py +++ /dev/null @@ -1,384 +0,0 @@ -# Copyright 2017 The TensorFlow Authors All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# ============================================================================== - -"""Contains the definition of the Inception Resnet V2 architecture. - -As described in http://arxiv.org/abs/1602.07261. - - Inception-v4, Inception-ResNet and the Impact of Residual Connections - on Learning - Christian Szegedy, Sergey Ioffe, Vincent Vanhoucke, Alex Alemi -""" -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import warnings - -import tensorflow as tf - -slim = tf.contrib.slim - - -def block35(net, scale=1.0, activation_fn=tf.nn.relu, scope=None, reuse=None): - """Builds the 35x35 resnet block.""" - with tf.variable_scope(scope, 'Block35', [net], reuse=reuse): - with tf.variable_scope('Branch_0'): - tower_conv = slim.conv2d(net, 32, 1, scope='Conv2d_1x1') - with tf.variable_scope('Branch_1'): - tower_conv1_0 = slim.conv2d(net, 32, 1, scope='Conv2d_0a_1x1') - tower_conv1_1 = slim.conv2d(tower_conv1_0, 32, 3, scope='Conv2d_0b_3x3') - with tf.variable_scope('Branch_2'): - tower_conv2_0 = slim.conv2d(net, 32, 1, scope='Conv2d_0a_1x1') - tower_conv2_1 = slim.conv2d(tower_conv2_0, 48, 3, scope='Conv2d_0b_3x3') - tower_conv2_2 = slim.conv2d(tower_conv2_1, 64, 3, scope='Conv2d_0c_3x3') - mixed = tf.concat( - axis=3, values=[tower_conv, tower_conv1_1, tower_conv2_2]) - up = slim.conv2d(mixed, net.get_shape()[3], 1, normalizer_fn=None, - activation_fn=None, scope='Conv2d_1x1') - net += scale * up - if activation_fn: - net = activation_fn(net) - return net - - -def block17(net, scale=1.0, activation_fn=tf.nn.relu, scope=None, reuse=None): - """Builds the 17x17 resnet block.""" - with tf.variable_scope(scope, 'Block17', [net], reuse=reuse): - with tf.variable_scope('Branch_0'): - tower_conv = slim.conv2d(net, 192, 1, scope='Conv2d_1x1') - with tf.variable_scope('Branch_1'): - tower_conv1_0 = slim.conv2d(net, 128, 1, scope='Conv2d_0a_1x1') - tower_conv1_1 = slim.conv2d(tower_conv1_0, 160, [1, 7], - scope='Conv2d_0b_1x7') - tower_conv1_2 = slim.conv2d(tower_conv1_1, 192, [7, 1], - scope='Conv2d_0c_7x1') - mixed = tf.concat(axis=3, values=[tower_conv, tower_conv1_2]) - up = slim.conv2d(mixed, net.get_shape()[3], 1, normalizer_fn=None, - activation_fn=None, scope='Conv2d_1x1') - net += scale * up - if activation_fn: - net = activation_fn(net) - return net - - -def block8(net, scale=1.0, activation_fn=tf.nn.relu, scope=None, reuse=None): - """Builds the 8x8 resnet block.""" - with tf.variable_scope(scope, 'Block8', [net], reuse=reuse): - with tf.variable_scope('Branch_0'): - tower_conv = slim.conv2d(net, 192, 1, scope='Conv2d_1x1') - with tf.variable_scope('Branch_1'): - tower_conv1_0 = slim.conv2d(net, 192, 1, scope='Conv2d_0a_1x1') - tower_conv1_1 = slim.conv2d(tower_conv1_0, 224, [1, 3], - scope='Conv2d_0b_1x3') - tower_conv1_2 = slim.conv2d(tower_conv1_1, 256, [3, 1], - scope='Conv2d_0c_3x1') - mixed = tf.concat(axis=3, values=[tower_conv, tower_conv1_2]) - up = slim.conv2d(mixed, net.get_shape()[3], 1, normalizer_fn=None, - activation_fn=None, scope='Conv2d_1x1') - net += scale * up - if activation_fn: - net = activation_fn(net) - return net - - -def inception_resnet_v2_base(inputs, - final_endpoint='Conv2d_7b_1x1', - output_stride=16, - align_feature_maps=False, - scope=None): - """Inception model from http://arxiv.org/abs/1602.07261. - - Constructs an Inception Resnet v2 network from inputs to the given final - endpoint. This method can construct the network up to the final inception - block Conv2d_7b_1x1. - - Args: - inputs: a tensor of size [batch_size, height, width, channels]. - final_endpoint: specifies the endpoint to construct the network up to. It - can be one of ['Conv2d_1a_3x3', 'Conv2d_2a_3x3', 'Conv2d_2b_3x3', - 'MaxPool_3a_3x3', 'Conv2d_3b_1x1', 'Conv2d_4a_3x3', 'MaxPool_5a_3x3', - 'Mixed_5b', 'Mixed_6a', 'PreAuxLogits', 'Mixed_7a', 'Conv2d_7b_1x1'] - output_stride: A scalar that specifies the requested ratio of input to - output spatial resolution. Only supports 8 and 16. - align_feature_maps: When true, changes all the VALID paddings in the network - to SAME padding so that the feature maps are aligned. - scope: Optional variable_scope. - - Returns: - tensor_out: output tensor corresponding to the final_endpoint. - end_points: a set of activations for external use, for example summaries or - losses. - - Raises: - ValueError: if final_endpoint is not set to one of the predefined values, - or if the output_stride is not 8 or 16, or if the output_stride is 8 and - we request an end point after 'PreAuxLogits'. - """ - if output_stride != 8 and output_stride != 16: - raise ValueError('output_stride must be 8 or 16.') - - padding = 'SAME' if align_feature_maps else 'VALID' - - end_points = {} - - def add_and_check_final(name, net): - """ - TODO: write this - """ - end_points[name] = net - return name == final_endpoint - - with tf.variable_scope(scope, 'InceptionResnetV2', [inputs]): - with slim.arg_scope([slim.conv2d, slim.max_pool2d, slim.avg_pool2d], - stride=1, padding='SAME'): - # 149 x 149 x 32 - net = slim.conv2d(inputs, 32, 3, stride=2, padding=padding, - scope='Conv2d_1a_3x3') - if add_and_check_final('Conv2d_1a_3x3', net): - return net, end_points - - # 147 x 147 x 32 - net = slim.conv2d(net, 32, 3, padding=padding, - scope='Conv2d_2a_3x3') - if add_and_check_final('Conv2d_2a_3x3', net): - return net, end_points - # 147 x 147 x 64 - net = slim.conv2d(net, 64, 3, scope='Conv2d_2b_3x3') - if add_and_check_final('Conv2d_2b_3x3', net): - return net, end_points - # 73 x 73 x 64 - net = slim.max_pool2d(net, 3, stride=2, padding=padding, - scope='MaxPool_3a_3x3') - if add_and_check_final('MaxPool_3a_3x3', net): - return net, end_points - # 73 x 73 x 80 - net = slim.conv2d(net, 80, 1, padding=padding, - scope='Conv2d_3b_1x1') - if add_and_check_final('Conv2d_3b_1x1', net): - return net, end_points - # 71 x 71 x 192 - net = slim.conv2d(net, 192, 3, padding=padding, - scope='Conv2d_4a_3x3') - if add_and_check_final('Conv2d_4a_3x3', net): - return net, end_points - # 35 x 35 x 192 - net = slim.max_pool2d(net, 3, stride=2, padding=padding, - scope='MaxPool_5a_3x3') - if add_and_check_final('MaxPool_5a_3x3', net): - return net, end_points - - # 35 x 35 x 320 - with tf.variable_scope('Mixed_5b'): - with tf.variable_scope('Branch_0'): - tower_conv = slim.conv2d(net, 96, 1, scope='Conv2d_1x1') - with tf.variable_scope('Branch_1'): - tower_conv1_0 = slim.conv2d(net, 48, 1, scope='Conv2d_0a_1x1') - tower_conv1_1 = slim.conv2d(tower_conv1_0, 64, 5, - scope='Conv2d_0b_5x5') - with tf.variable_scope('Branch_2'): - tower_conv2_0 = slim.conv2d(net, 64, 1, scope='Conv2d_0a_1x1') - tower_conv2_1 = slim.conv2d(tower_conv2_0, 96, 3, - scope='Conv2d_0b_3x3') - tower_conv2_2 = slim.conv2d(tower_conv2_1, 96, 3, - scope='Conv2d_0c_3x3') - with tf.variable_scope('Branch_3'): - tower_pool = slim.avg_pool2d(net, 3, stride=1, padding='SAME', - scope='AvgPool_0a_3x3') - tower_pool_1 = slim.conv2d(tower_pool, 64, 1, - scope='Conv2d_0b_1x1') - net = tf.concat( - [tower_conv, tower_conv1_1, tower_conv2_2, tower_pool_1], 3) - - if add_and_check_final('Mixed_5b', net): - return net, end_points - # TODO(alemi): Register intermediate endpoints - net = slim.repeat(net, 10, block35, scale=0.17) - - # 17 x 17 x 1088 if output_stride == 8, - # 33 x 33 x 1088 if output_stride == 16 - use_atrous = output_stride == 8 - - with tf.variable_scope('Mixed_6a'): - with tf.variable_scope('Branch_0'): - tower_conv = slim.conv2d(net, 384, 3, stride=1 if use_atrous else 2, - padding=padding, - scope='Conv2d_1a_3x3') - with tf.variable_scope('Branch_1'): - tower_conv1_0 = slim.conv2d(net, 256, 1, scope='Conv2d_0a_1x1') - tower_conv1_1 = slim.conv2d(tower_conv1_0, 256, 3, - scope='Conv2d_0b_3x3') - tower_conv1_2 = slim.conv2d(tower_conv1_1, 384, 3, - stride=1 if use_atrous else 2, - padding=padding, - scope='Conv2d_1a_3x3') - with tf.variable_scope('Branch_2'): - tower_pool = slim.max_pool2d(net, 3, stride=1 if use_atrous else 2, - padding=padding, - scope='MaxPool_1a_3x3') - net = tf.concat([tower_conv, tower_conv1_2, tower_pool], 3) - - if add_and_check_final('Mixed_6a', net): - return net, end_points - - # TODO(alemi): register intermediate endpoints - with slim.arg_scope([slim.conv2d], rate=2 if use_atrous else 1): - net = slim.repeat(net, 20, block17, scale=0.10) - if add_and_check_final('PreAuxLogits', net): - return net, end_points - - if output_stride == 8: - # TODO(gpapan): Properly support output_stride for the rest of the net. - raise ValueError('output_stride==8 is only supported up to the ' - 'PreAuxlogits end_point for now.') - - # 8 x 8 x 2080 - with tf.variable_scope('Mixed_7a'): - with tf.variable_scope('Branch_0'): - tower_conv = slim.conv2d(net, 256, 1, scope='Conv2d_0a_1x1') - tower_conv_1 = slim.conv2d(tower_conv, 384, 3, stride=2, - padding=padding, - scope='Conv2d_1a_3x3') - with tf.variable_scope('Branch_1'): - tower_conv1 = slim.conv2d(net, 256, 1, scope='Conv2d_0a_1x1') - tower_conv1_1 = slim.conv2d(tower_conv1, 288, 3, stride=2, - padding=padding, - scope='Conv2d_1a_3x3') - with tf.variable_scope('Branch_2'): - tower_conv2 = slim.conv2d(net, 256, 1, scope='Conv2d_0a_1x1') - tower_conv2_1 = slim.conv2d(tower_conv2, 288, 3, - scope='Conv2d_0b_3x3') - tower_conv2_2 = slim.conv2d(tower_conv2_1, 320, 3, stride=2, - padding=padding, - scope='Conv2d_1a_3x3') - with tf.variable_scope('Branch_3'): - tower_pool = slim.max_pool2d(net, 3, stride=2, - padding=padding, - scope='MaxPool_1a_3x3') - net = tf.concat( - [tower_conv_1, tower_conv1_1, tower_conv2_2, tower_pool], 3) - - if add_and_check_final('Mixed_7a', net): - return net, end_points - - # TODO(alemi): register intermediate endpoints - net = slim.repeat(net, 9, block8, scale=0.20) - net = block8(net, activation_fn=None) - - # 8 x 8 x 1536 - net = slim.conv2d(net, 1536, 1, scope='Conv2d_7b_1x1') - if add_and_check_final('Conv2d_7b_1x1', net): - return net, end_points - - raise ValueError('final_endpoint (%s) not recognized' % final_endpoint) - - -def inception_resnet_v2(inputs, nb_classes=1001, is_training=True, - dropout_keep_prob=0.8, - reuse=None, - scope='InceptionResnetV2', - create_aux_logits=True, - num_classes=None): - """Creates the Inception Resnet V2 model. - - Args: - inputs: a 4-D tensor of size [batch_size, height, width, 3]. - nb_classes: number of predicted classes. - is_training: whether is training or not. - dropout_keep_prob: float, the fraction to keep before final layer. - reuse: whether or not the network and its variables should be reused. To be - able to reuse 'scope' must be given. - scope: Optional variable_scope. - create_aux_logits: Whether to include the auxilliary logits. - num_classes: depricated alias for nb_classes - - Returns: - logits: the logits outputs of the model. - end_points: the set of end_points from the inception model. - """ - if num_classes is not None: - warnings.warn("`num_classes` is deprecated. Switch to `nb_classes`." - " `num_classes` may be removed on or after 2019-04-23.") - nb_classes = num_classes - del num_classes - end_points = {} - - with tf.variable_scope(scope, 'InceptionResnetV2', [inputs, nb_classes], - reuse=reuse) as var_scope: - with slim.arg_scope([slim.batch_norm, slim.dropout], - is_training=is_training): - - net, end_points = inception_resnet_v2_base(inputs, scope=var_scope) - - if create_aux_logits: - with tf.variable_scope('AuxLogits'): - aux = end_points['PreAuxLogits'] - aux = slim.avg_pool2d(aux, 5, stride=3, padding='VALID', - scope='Conv2d_1a_3x3') - aux = slim.conv2d(aux, 128, 1, scope='Conv2d_1b_1x1') - aux = slim.conv2d(aux, 768, aux.get_shape()[1:3], - padding='VALID', scope='Conv2d_2a_5x5') - aux = slim.flatten(aux) - aux = slim.fully_connected(aux, nb_classes, activation_fn=None, - scope='Logits') - end_points['AuxLogits'] = aux - - with tf.variable_scope('Logits'): - net = slim.avg_pool2d(net, net.get_shape()[1:3], padding='VALID', - scope='AvgPool_1a_8x8') - net = slim.flatten(net) - - net = slim.dropout(net, dropout_keep_prob, is_training=is_training, - scope='Dropout') - - end_points['PreLogitsFlatten'] = net - logits = slim.fully_connected(net, nb_classes, activation_fn=None, - scope='Logits') - end_points['Logits'] = logits - end_points['Predictions'] = tf.nn.softmax(logits, name='Predictions') - - return logits, end_points - - -inception_resnet_v2.default_image_size = 299 - - -def inception_resnet_v2_arg_scope(weight_decay=0.00004, - batch_norm_decay=0.9997, - batch_norm_epsilon=0.001): - """Returns the scope with the default parameters for inception_resnet_v2. - - Args: - weight_decay: the weight decay for weights variables. - batch_norm_decay: decay for the moving average of batch_norm momentums. - batch_norm_epsilon: small float added to variance to avoid dividing by zero. - - Returns: - a arg_scope with the parameters needed for inception_resnet_v2. - """ - # Set weight_decay for weights in conv2d and fully_connected layers. - with slim.arg_scope([slim.conv2d, slim.fully_connected], - weights_regularizer=slim.l2_regularizer(weight_decay), - biases_regularizer=slim.l2_regularizer(weight_decay)): - - batch_norm_params = { - 'decay': batch_norm_decay, - 'epsilon': batch_norm_epsilon, - } - # Set activation_fn and parameters for batch_norm. - with slim.arg_scope([slim.conv2d], activation_fn=tf.nn.relu, - normalizer_fn=slim.batch_norm, - normalizer_params=batch_norm_params) as scope: - return scope diff --git a/examples/nips17_adversarial_competition/dev_toolkit/sample_targeted_attacks/iter_target_class/attack_iter_target_class.py b/examples/nips17_adversarial_competition/dev_toolkit/sample_targeted_attacks/iter_target_class/attack_iter_target_class.py deleted file mode 100644 index 4685f14c6..000000000 --- a/examples/nips17_adversarial_competition/dev_toolkit/sample_targeted_attacks/iter_target_class/attack_iter_target_class.py +++ /dev/null @@ -1,175 +0,0 @@ -"""Implementation of sample attack.""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import csv -import os - -import numpy as np -import tensorflow as tf -from tensorflow.contrib.slim.nets import inception -from scipy.misc import imread -from scipy.misc import imsave - - -slim = tf.contrib.slim - - -tf.flags.DEFINE_string( - 'master', '', 'The address of the TensorFlow master to use.') - -tf.flags.DEFINE_string( - 'checkpoint_path', '', 'Path to checkpoint for inception network.') - -tf.flags.DEFINE_string( - 'input_dir', '', 'Input directory with images.') - -tf.flags.DEFINE_string( - 'output_dir', '', 'Output directory with images.') - -tf.flags.DEFINE_float( - 'max_epsilon', 16.0, 'Maximum size of adversarial perturbation.') - -tf.flags.DEFINE_float( - 'iter_alpha', 1.0, 'Step size for one iteration.') - -tf.flags.DEFINE_integer( - 'num_iter', 20, 'Number of iterations.') - -tf.flags.DEFINE_integer( - 'image_width', 299, 'Width of each input images.') - -tf.flags.DEFINE_integer( - 'image_height', 299, 'Height of each input images.') - -tf.flags.DEFINE_integer( - 'batch_size', 16, 'How many images process at one time.') - -FLAGS = tf.flags.FLAGS - - -def load_target_class(input_dir): - """Loads target classes.""" - with tf.gfile.Open(os.path.join(input_dir, 'target_class.csv')) as f: - return {row[0]: int(row[1]) for row in csv.reader(f) if len(row) >= 2} - - -def load_images(input_dir, batch_shape): - """Read png images from input directory in batches. - - Args: - input_dir: input directory - batch_shape: shape of minibatch array, i.e. [batch_size, height, width, 3] - - Yields: - filenames: list file names without path of each image - Lenght of this list could be less than batch_size, in this case only - first few images of the result are elements of the minibatch. - images: array with all images from this batch - """ - images = np.zeros(batch_shape) - filenames = [] - idx = 0 - batch_size = batch_shape[0] - for filepath in tf.gfile.Glob(os.path.join(input_dir, '*.png')): - with tf.gfile.Open(filepath) as f: - image = imread(f, mode='RGB').astype(np.float) / 255.0 - # Images for inception classifier are normalized to be in [-1, 1] interval. - images[idx, :, :, :] = image * 2.0 - 1.0 - filenames.append(os.path.basename(filepath)) - idx += 1 - if idx == batch_size: - yield filenames, images - filenames = [] - images = np.zeros(batch_shape) - idx = 0 - if idx > 0: - yield filenames, images - - -def save_images(images, filenames, output_dir): - """Saves images to the output directory. - - Args: - images: array with minibatch of images - filenames: list of filenames without path - If number of file names in this list less than number of images in - the minibatch then only first len(filenames) images will be saved. - output_dir: directory where to save images - """ - for i, filename in enumerate(filenames): - # Images for inception classifier are normalized to be in [-1, 1] interval, - # so rescale them back to [0, 1]. - with tf.gfile.Open(os.path.join(output_dir, filename), 'w') as f: - imsave(f, (images[i, :, :, :] + 1.0) * 0.5, format='png') - - -def main(_): - """Run the sample attack""" - # Images for inception classifier are normalized to be in [-1, 1] interval, - # eps is a difference between pixels so it should be in [0, 2] interval. - # Renormalizing epsilon from [0, 255] to [0, 2]. - eps = 2.0 * FLAGS.max_epsilon / 255.0 - alpha = 2.0 * FLAGS.iter_alpha / 255.0 - num_iter = FLAGS.num_iter - batch_shape = [FLAGS.batch_size, FLAGS.image_height, FLAGS.image_width, 3] - nb_classes = 1001 - - tf.logging.set_verbosity(tf.logging.INFO) - - all_images_taget_class = load_target_class(FLAGS.input_dir) - - with tf.Graph().as_default(): - # Prepare graph - x_input = tf.placeholder(tf.float32, shape=batch_shape) - x_max = tf.clip_by_value(x_input + eps, -1.0, 1.0) - x_min = tf.clip_by_value(x_input - eps, -1.0, 1.0) - - with slim.arg_scope(inception.inception_v3_arg_scope()): - inception.inception_v3( - x_input, num_classes=nb_classes, is_training=False) - - x_adv = x_input - target_class_input = tf.placeholder(tf.int32, shape=[FLAGS.batch_size]) - one_hot_target_class = tf.one_hot(target_class_input, nb_classes) - - for _ in range(num_iter): - with slim.arg_scope(inception.inception_v3_arg_scope()): - logits, end_points = inception.inception_v3( - x_adv, num_classes=nb_classes, is_training=False, reuse=True) - cross_entropy = tf.losses.softmax_cross_entropy(one_hot_target_class, - logits, - label_smoothing=0.1, - weights=1.0) - cross_entropy += tf.losses.softmax_cross_entropy(one_hot_target_class, - end_points['AuxLogits'], - label_smoothing=0.1, - weights=0.4) - x_next = x_adv - alpha * tf.sign(tf.gradients(cross_entropy, x_adv)[0]) - x_next = tf.clip_by_value(x_next, x_min, x_max) - x_adv = x_next - - # Run computation - saver = tf.train.Saver(slim.get_model_variables()) - session_creator = tf.train.ChiefSessionCreator( - scaffold=tf.train.Scaffold(saver=saver), - checkpoint_filename_with_path=FLAGS.checkpoint_path, - master=FLAGS.master) - - with tf.train.MonitoredSession(session_creator=session_creator) as sess: - for filenames, images in load_images(FLAGS.input_dir, batch_shape): - target_class_for_batch = ( - [all_images_taget_class[n] for n in filenames] - + [0] * (FLAGS.batch_size - len(filenames))) - adv_images = sess.run(x_adv, - feed_dict={ - x_input: images, - target_class_input: target_class_for_batch - }) - save_images(adv_images, filenames, FLAGS.output_dir) - - -if __name__ == '__main__': - tf.app.run() diff --git a/examples/nips17_adversarial_competition/dev_toolkit/sample_targeted_attacks/step_target_class/attack_step_target_class.py b/examples/nips17_adversarial_competition/dev_toolkit/sample_targeted_attacks/step_target_class/attack_step_target_class.py deleted file mode 100644 index eced27f9a..000000000 --- a/examples/nips17_adversarial_competition/dev_toolkit/sample_targeted_attacks/step_target_class/attack_step_target_class.py +++ /dev/null @@ -1,157 +0,0 @@ -"""Implementation of sample attack.""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import csv -import os - -import numpy as np -import tensorflow as tf -from tensorflow.contrib.slim.nets import inception -from scipy.misc import imread -from scipy.misc import imsave - - -slim = tf.contrib.slim - - -tf.flags.DEFINE_string( - 'master', '', 'The address of the TensorFlow master to use.') - -tf.flags.DEFINE_string( - 'checkpoint_path', '', 'Path to checkpoint for inception network.') - -tf.flags.DEFINE_string( - 'input_dir', '', 'Input directory with images.') - -tf.flags.DEFINE_string( - 'output_dir', '', 'Output directory with images.') - -tf.flags.DEFINE_float( - 'max_epsilon', 16.0, 'Maximum size of adversarial perturbation.') - -tf.flags.DEFINE_integer( - 'image_width', 299, 'Width of each input images.') - -tf.flags.DEFINE_integer( - 'image_height', 299, 'Height of each input images.') - -tf.flags.DEFINE_integer( - 'batch_size', 16, 'How many images process at one time.') - -FLAGS = tf.flags.FLAGS - - -def load_target_class(input_dir): - """Loads target classes.""" - with tf.gfile.Open(os.path.join(input_dir, 'target_class.csv')) as f: - return {row[0]: int(row[1]) for row in csv.reader(f) if len(row) >= 2} - - -def load_images(input_dir, batch_shape): - """Read png images from input directory in batches. - - Args: - input_dir: input directory - batch_shape: shape of minibatch array, i.e. [batch_size, height, width, 3] - - Yields: - filenames: list file names without path of each image - Lenght of this list could be less than batch_size, in this case only - first few images of the result are elements of the minibatch. - images: array with all images from this batch - """ - images = np.zeros(batch_shape) - filenames = [] - idx = 0 - batch_size = batch_shape[0] - for filepath in tf.gfile.Glob(os.path.join(input_dir, '*.png')): - with tf.gfile.Open(filepath) as f: - image = imread(f, mode='RGB').astype(np.float) / 255.0 - # Images for inception classifier are normalized to be in [-1, 1] interval. - images[idx, :, :, :] = image * 2.0 - 1.0 - filenames.append(os.path.basename(filepath)) - idx += 1 - if idx == batch_size: - yield filenames, images - filenames = [] - images = np.zeros(batch_shape) - idx = 0 - if idx > 0: - yield filenames, images - - -def save_images(images, filenames, output_dir): - """Saves images to the output directory. - - Args: - images: array with minibatch of images - filenames: list of filenames without path - If number of file names in this list less than number of images in - the minibatch then only first len(filenames) images will be saved. - output_dir: directory where to save images - """ - for i, filename in enumerate(filenames): - # Images for inception classifier are normalized to be in [-1, 1] interval, - # so rescale them back to [0, 1]. - with tf.gfile.Open(os.path.join(output_dir, filename), 'w') as f: - imsave(f, (images[i, :, :, :] + 1.0) * 0.5, format='png') - - -def main(_): - # Images for inception classifier are normalized to be in [-1, 1] interval, - # eps is a difference between pixels so it should be in [0, 2] interval. - # Renormalizing epsilon from [0, 255] to [0, 2]. - eps = 2.0 * FLAGS.max_epsilon / 255.0 - batch_shape = [FLAGS.batch_size, FLAGS.image_height, FLAGS.image_width, 3] - nb_classes = 1001 - - tf.logging.set_verbosity(tf.logging.INFO) - - all_images_taget_class = load_target_class(FLAGS.input_dir) - - with tf.Graph().as_default(): - # Prepare graph - x_input = tf.placeholder(tf.float32, shape=batch_shape) - - with slim.arg_scope(inception.inception_v3_arg_scope()): - logits, end_points = inception.inception_v3( - x_input, num_classes=nb_classes, is_training=False) - - target_class_input = tf.placeholder(tf.int32, shape=[FLAGS.batch_size]) - one_hot_target_class = tf.one_hot(target_class_input, nb_classes) - cross_entropy = tf.losses.softmax_cross_entropy(one_hot_target_class, - logits, - label_smoothing=0.1, - weights=1.0) - cross_entropy += tf.losses.softmax_cross_entropy(one_hot_target_class, - end_points['AuxLogits'], - label_smoothing=0.1, - weights=0.4) - x_adv = x_input - eps * tf.sign(tf.gradients(cross_entropy, x_input)[0]) - x_adv = tf.clip_by_value(x_adv, -1.0, 1.0) - - # Run computation - saver = tf.train.Saver(slim.get_model_variables()) - session_creator = tf.train.ChiefSessionCreator( - scaffold=tf.train.Scaffold(saver=saver), - checkpoint_filename_with_path=FLAGS.checkpoint_path, - master=FLAGS.master) - - with tf.train.MonitoredSession(session_creator=session_creator) as sess: - for filenames, images in load_images(FLAGS.input_dir, batch_shape): - target_class_for_batch = ( - [all_images_taget_class[n] for n in filenames] - + [0] * (FLAGS.batch_size - len(filenames))) - adv_images = sess.run(x_adv, - feed_dict={ - x_input: images, - target_class_input: target_class_for_batch - }) - save_images(adv_images, filenames, FLAGS.output_dir) - - -if __name__ == '__main__': - tf.app.run() diff --git a/examples/nips17_adversarial_competition/dev_toolkit/validation_tool/submission_validator_lib.py b/examples/nips17_adversarial_competition/dev_toolkit/validation_tool/submission_validator_lib.py deleted file mode 100644 index 7fe003540..000000000 --- a/examples/nips17_adversarial_competition/dev_toolkit/validation_tool/submission_validator_lib.py +++ /dev/null @@ -1,411 +0,0 @@ -"""Helper library which performs validation of the submission.""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from builtins import int # long in python 2 - -import csv -import json -import logging -import os -import re -import subprocess - -import numpy as np -from six import iteritems -from PIL import Image - - - -EXTRACT_COMMAND = { - '.zip': ['unzip', '${src}', '-d', '${dst}'], - '.tar': ['tar', 'xvf', '${src}', '-C', '${dst}'], - '.tar.gz': ['tar', 'xvzf', '${src}', '-C', '${dst}'], -} - -ALLOWED_SUBMISSION_TYPES = ['attack', 'targeted_attack', 'defense'] - -REQUIRED_METADATA_JSON_FIELDS = ['entry_point', 'container', - 'container_gpu', 'type'] - -CMD_VARIABLE_RE = re.compile('^\\$\\{(\\w+)\\}$') - -BATCH_SIZE = 100 -IMAGE_NAME_PATTERN = 'IMG{0:04}.png' - -ALLOWED_EPS = [4, 8, 12, 16] - -MAX_SUBMISSION_SIZE_ZIPPED = 8*1024*1024*1024 # 8 GiB -MAX_SUBMISSION_SIZE_UNPACKED = 16*1024*1024*1024 # 16 GiB -MAX_DOCKER_IMAGE_SIZE = 8*1024*1024*1024 # 8 GiB - - -def get_extract_command_template(filename): - """Returns extraction command based on the filename extension.""" - for k, v in iteritems(EXTRACT_COMMAND): - if filename.endswith(k): - return v - return None - - -def shell_call(command, **kwargs): - """Calls shell command with parameter substitution. - - Args: - command: command to run as a list of tokens - **kwargs: dirctionary with substitutions - - Returns: - whether command was successful, i.e. returned 0 status code - - Example of usage: - shell_call(['cp', '${A}', '${B}'], A='src_file', B='dst_file') - will call shell command: - cp src_file dst_file - """ - command = list(command) - for i in range(len(command)): - m = CMD_VARIABLE_RE.match(command[i]) - if m: - var_id = m.group(1) - if var_id in kwargs: - command[i] = kwargs[var_id] - return subprocess.call(command) == 0 - - -def make_directory_writable(dirname): - """Makes directory readable and writable by everybody. - - Args: - dirname: name of the directory - - Returns: - True if operation was successfull - - If you run something inside Docker container and it writes files, then - these files will be written as root user with restricted permissions. - So to be able to read/modify these files outside of Docker you have to change - permissions to be world readable and writable. - """ - retval = shell_call(['docker', 'run', '-v', - '{0}:/output_dir'.format(dirname), - 'busybox:1.27.2', - 'chmod', '-R', 'a+rwx', '/output_dir']) - if not retval: - logging.error('Failed to change permissions on directory: %s', dirname) - return retval - - -def load_defense_output(filename): - """Loads output of defense from given file.""" - result = {} - with open(filename) as f: - for row in csv.reader(f): - try: - image_filename = row[0] - if not image_filename.endswith('.png'): - image_filename += '.png' - label = int(row[1]) - except (IndexError, ValueError): - continue - result[image_filename] = label - return result - - -class SubmissionValidator(object): - """Class which performs validation of the submission.""" - - def __init__(self, temp_dir, use_gpu): - """Initializes instance of SubmissionValidator. - - Args: - temp_dir: temporary working directory - use_gpu: whether to use GPU - """ - self._temp_dir = temp_dir - self._use_gpu = use_gpu - self._tmp_extracted_dir = os.path.join(self._temp_dir, 'tmp_extracted') - self._extracted_submission_dir = os.path.join(self._temp_dir, 'extracted') - self._sample_input_dir = os.path.join(self._temp_dir, 'input') - self._sample_output_dir = os.path.join(self._temp_dir, 'output') - - def _prepare_temp_dir(self): - """Cleans up and prepare temporary directory.""" - shell_call(['rm', '-rf', os.path.join(self._temp_dir, '*')]) - # NOTE: we do not create self._extracted_submission_dir - # this is intentional because self._tmp_extracted_dir or it's subdir - # will be renames into self._extracted_submission_dir - os.mkdir(self._tmp_extracted_dir) - os.mkdir(self._sample_input_dir) - os.mkdir(self._sample_output_dir) - # make output dir world writable - shell_call(['chmod', 'a+rwX', '-R', self._sample_output_dir]) - - def _extract_submission(self, filename): - """Extracts submission and moves it into self._extracted_submission_dir.""" - # verify filesize - file_size = os.path.getsize(filename) - if file_size > MAX_SUBMISSION_SIZE_ZIPPED: - logging.error('Submission archive size %d is exceeding limit %d', - file_size, MAX_SUBMISSION_SIZE_ZIPPED) - return False - # determime archive type - exctract_command_tmpl = get_extract_command_template(filename) - if not exctract_command_tmpl: - logging.error('Input file has to be zip, tar or tar.gz archive; however ' - 'found: %s', filename) - return False - # extract archive - submission_dir = os.path.dirname(filename) - submission_basename = os.path.basename(filename) - logging.info('Extracting archive %s', filename) - retval = shell_call( - ['docker', 'run', - '--network=none', - '-v', '{0}:/input_dir'.format(submission_dir), - '-v', '{0}:/output_dir'.format(self._tmp_extracted_dir), - 'busybox:1.27.2'] + exctract_command_tmpl, - src=os.path.join('/input_dir', submission_basename), - dst='/output_dir') - if not retval: - logging.error('Failed to extract submission from file %s', filename) - return False - if not make_directory_writable(self._tmp_extracted_dir): - return False - # find submission root - root_dir = self._tmp_extracted_dir - root_dir_content = [d for d in os.listdir(root_dir) if d != '__MACOSX'] - if (len(root_dir_content) == 1 - and os.path.isdir(os.path.join(root_dir, root_dir_content[0]))): - logging.info('Looks like submission root is in subdirectory "%s" of ' - 'the archive', root_dir_content[0]) - root_dir = os.path.join(root_dir, root_dir_content[0]) - # Move files to self._extracted_submission_dir. - # At this point self._extracted_submission_dir does not exist, - # so following command will simply rename root_dir into - # self._extracted_submission_dir - if not shell_call(['mv', root_dir, self._extracted_submission_dir]): - logging.error('Can''t move submission files from root directory') - return False - return True - - def _verify_submission_size(self): - submission_size = 0 - for dirname, _, filenames in os.walk(self._extracted_submission_dir): - for f in filenames: - submission_size += os.path.getsize(os.path.join(dirname, f)) - logging.info('Unpacked submission size: %d', submission_size) - if submission_size > MAX_SUBMISSION_SIZE_UNPACKED: - logging.error('Submission size exceeding limit %d', - MAX_SUBMISSION_SIZE_UNPACKED) - return submission_size <= MAX_SUBMISSION_SIZE_UNPACKED - - def _load_and_verify_metadata(self, submission_type): - """Loads and verifies metadata. - - Args: - submission_type: type of the submission - - Returns: - dictionaty with metadata or None if metadata not found or invalid - """ - metadata_filename = os.path.join(self._extracted_submission_dir, - 'metadata.json') - if not os.path.isfile(metadata_filename): - logging.error('metadata.json not found') - return None - try: - with open(metadata_filename, 'r') as f: - metadata = json.load(f) - except IOError as e: - logging.error('Failed to load metadata: %s', e) - return None - for field_name in REQUIRED_METADATA_JSON_FIELDS: - if field_name not in metadata: - logging.error('Field %s not found in metadata', field_name) - return None - # Verify submission type - if submission_type != metadata['type']: - logging.error('Invalid submission type in metadata, expected "%s", ' - 'actual "%s"', submission_type, metadata['type']) - return None - # Check submission entry point - entry_point = metadata['entry_point'] - if not os.path.isfile(os.path.join(self._extracted_submission_dir, - entry_point)): - logging.error('Entry point not found: %s', entry_point) - return None - if not entry_point.endswith('.sh'): - logging.warning('Entry point is not an .sh script. ' - 'This is not necessarily a problem, but if submission ' - 'won''t run double check entry point first: %s', - entry_point) - # Metadata verified - return metadata - - def _verify_docker_image_size(self, image_name): - """Verifies size of Docker image. - - Args: - image_name: name of the Docker image. - - Returns: - True if image size is withing the limits, False otherwise. - """ - shell_call(['docker', 'pull', image_name]) - try: - image_size = subprocess.check_output( - ['docker', 'inspect', '--format={{.Size}}', image_name]).strip() - image_size = int(image_size) - except (ValueError, subprocess.CalledProcessError) as e: - logging.error('Failed to determine docker image size: %s', e) - return False - logging.info('Size of docker image %s is %d', image_name, image_size) - if image_size > MAX_DOCKER_IMAGE_SIZE: - logging.error('Image size exceeds limit %d', MAX_DOCKER_IMAGE_SIZE) - return image_size <= MAX_DOCKER_IMAGE_SIZE - - def _prepare_sample_data(self, submission_type): - """Prepares sample data for the submission. - - Args: - submission_type: type of the submission. - """ - # write images - images = np.random.randint(0, 256, - size=[BATCH_SIZE, 299, 299, 3], dtype=np.uint8) - for i in range(BATCH_SIZE): - Image.fromarray(images[i, :, :, :]).save( - os.path.join(self._sample_input_dir, IMAGE_NAME_PATTERN.format(i))) - # write target class for targeted attacks - if submission_type == 'targeted_attack': - target_classes = np.random.randint(1, 1001, size=[BATCH_SIZE]) - target_class_filename = os.path.join(self._sample_input_dir, - 'target_class.csv') - with open(target_class_filename, 'w') as f: - for i in range(BATCH_SIZE): - f.write((IMAGE_NAME_PATTERN + ',{1}\n').format(i, target_classes[i])) - - def _run_submission(self, metadata): - """Runs submission inside Docker container. - - Args: - metadata: dictionary with submission metadata - - Returns: - True if status code of Docker command was success (i.e. zero), - False otherwise. - """ - if self._use_gpu: - docker_binary = 'nvidia-docker' - container_name = metadata['container_gpu'] - else: - docker_binary = 'docker' - container_name = metadata['container'] - if metadata['type'] == 'defense': - cmd = [docker_binary, 'run', - '--network=none', - '-m=24g', - '-v', '{0}:/input_images:ro'.format(self._sample_input_dir), - '-v', '{0}:/output_data'.format(self._sample_output_dir), - '-v', '{0}:/code'.format(self._extracted_submission_dir), - '-w', '/code', - container_name, - './' + metadata['entry_point'], - '/input_images', - '/output_data/result.csv'] - else: - epsilon = np.random.choice(ALLOWED_EPS) - cmd = [docker_binary, 'run', - '--network=none', - '-m=24g', - '-v', '{0}:/input_images:ro'.format(self._sample_input_dir), - '-v', '{0}:/output_images'.format(self._sample_output_dir), - '-v', '{0}:/code'.format(self._extracted_submission_dir), - '-w', '/code', - container_name, - './' + metadata['entry_point'], - '/input_images', - '/output_images', - str(epsilon)] - logging.info('Command to run submission: %s', ' '.join(cmd)) - return shell_call(cmd) - - def _verify_output(self, submission_type): - """Verifies correctness of the submission output. - - Args: - submission_type: type of the submission - - Returns: - True if output looks valid - """ - result = True - if submission_type == 'defense': - try: - image_classification = load_defense_output( - os.path.join(self._sample_output_dir, 'result.csv')) - expected_keys = [IMAGE_NAME_PATTERN.format(i) - for i in range(BATCH_SIZE)] - if set(image_classification.keys()) != set(expected_keys): - logging.error('Classification results are not saved for all images') - result = False - except IOError as e: - logging.error('Failed to read defense output file: %s', e) - result = False - else: - for i in range(BATCH_SIZE): - image_filename = os.path.join(self._sample_output_dir, - IMAGE_NAME_PATTERN.format(i)) - try: - img = np.array(Image.open(image_filename).convert('RGB')) - if list(img.shape) != [299, 299, 3]: - logging.error('Invalid image size %s for image %s', - str(img.shape), image_filename) - result = False - except IOError as e: - result = False - return result - - def validate_submission(self, filename, submission_type): - """Validates submission. - - Args: - filename: submission filename - submission_type: type of the submission, - one of 'attack', 'targeted_attack' or 'defense' - - Returns: - whether submission is valid - """ - if submission_type not in ALLOWED_SUBMISSION_TYPES: - logging.error('Invalid submission type: %s', submission_type) - return False - self._prepare_temp_dir() - # Convert filename to be absolute path, - # relative path might cause problems when monting directory in Docker - filename = os.path.abspath(filename) - # extract submission - if not self._extract_submission(filename): - return False - # verify submission size - if not self._verify_submission_size(): - return False - # Load metadata - metadata = self._load_and_verify_metadata(submission_type) - if not metadata: - return False - # verify docker container size - if not self._verify_docker_image_size(metadata['container_gpu']): - return False - # Try to run submission on sample data - self._prepare_sample_data(submission_type) - if not self._run_submission(metadata): - logging.error('Failure while running submission') - return False - if not self._verify_output(submission_type): - logging.warning('Some of the outputs of your submission are invalid or ' - 'missing. You submission still will be evaluation ' - 'but you might get lower score.') - return True diff --git a/examples/nips17_adversarial_competition/dev_toolkit/validation_tool/validate_submission.py b/examples/nips17_adversarial_competition/dev_toolkit/validation_tool/validate_submission.py deleted file mode 100644 index 6ce7a120c..000000000 --- a/examples/nips17_adversarial_competition/dev_toolkit/validation_tool/validate_submission.py +++ /dev/null @@ -1,89 +0,0 @@ -r"""Tool to validate submission for adversarial competition. - -Usage: - python validate_submission.py \ - --submission_filename=FILENAME \ - --submission_type=TYPE \ - [--use_gpu] - -Where: - FILENAME - filename of the submission - TYPE - type of the submission, one of the following without quotes: - "attack", "targeted_attack" or "defense" - --use_gpu - if argument specified then submission will be run on GPU using - nvidia-docker, otherwise will be run on CPU. - -""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import argparse -import logging -import random -import subprocess -import tempfile -import submission_validator_lib - - -def print_in_box(text): - """ - Prints `text` surrounded by a box made of *s - """ - print('') - print('*' * (len(text) + 6)) - print('** ' + text + ' **') - print('*' * (len(text) + 6)) - print('') - - -def main(args): - """ - Validates the submission. - """ - print_in_box('Validating submission ' + args.submission_filename) - random.seed() - temp_dir = args.temp_dir - delete_temp_dir = False - if not temp_dir: - temp_dir = tempfile.mkdtemp() - logging.info('Created temporary directory: %s', temp_dir) - delete_temp_dir = True - validator = submission_validator_lib.SubmissionValidator(temp_dir, - args.use_gpu) - if validator.validate_submission(args.submission_filename, - args.submission_type): - print_in_box('Submission is VALID!') - else: - print_in_box('Submission is INVALID, see log messages for details') - if delete_temp_dir: - logging.info('Deleting temporary directory: %s', temp_dir) - subprocess.call(['rm', '-rf', temp_dir]) - - -if __name__ == '__main__': - parser = argparse.ArgumentParser( - description='Submission validation script.') - parser.add_argument('--submission_filename', - required=True, - help='Filename of the submission.') - parser.add_argument('--submission_type', - required=True, - help='Type of the submission, ' - 'one of "attack", "targeted_attack" or "defense"') - parser.add_argument('--temp_dir', - required=False, - default='', - help='Temporary directory to extract and run submission. ' - 'If empty then temporary directory will be created ' - 'by the script and then deleted in the end.') - parser.add_argument('--use_gpu', dest='use_gpu', action='store_true') - parser.add_argument('--nouse_gpu', dest='use_gpu', action='store_false') - parser.set_defaults(use_gpu=False) - loggint_format = ('%(asctime)s - %(filename)s:%(lineno)d - %(levelname)s -- ' - '%(message)s') - logging.basicConfig(format=loggint_format, - level=logging.INFO, - datefmt='%Y-%m-%d %H:%M:%S') - main(parser.parse_args()) diff --git a/examples/nips17_adversarial_competition/eval_infra/code/eval_lib/classification_results.py b/examples/nips17_adversarial_competition/eval_infra/code/eval_lib/classification_results.py deleted file mode 100644 index debb719ee..000000000 --- a/examples/nips17_adversarial_competition/eval_infra/code/eval_lib/classification_results.py +++ /dev/null @@ -1,384 +0,0 @@ -"""Module with classes to compute, read and store classification results. -""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import csv -from io import BytesIO -from io import StringIO -import logging -import os -import pickle -import time -from six import iteritems -from six import iterkeys -from six import itervalues -from six import PY3 - -KIND_CLASSIFICATION_BATCH = u'ClassificationBatch' -CLASSIFICATION_BATCH_ID_PATTERN = u'CBATCH{:06}' - -CLASSIFICATION_BATCHES_SUBDIR = 'classification_batches' - -TO_STR_MAX_BATCHES = 10 - -MAX_ALLOWED_CLASSIFICATION_RESULT_SIZE = 10000 - - -def read_classification_results(storage_client, file_path): - """Reads classification results from the file in Cloud Storage. - - This method reads file with classification results produced by running - defense on singe batch of adversarial images. - - Args: - storage_client: instance of CompetitionStorageClient or None for local file - file_path: path of the file with results - - Returns: - dictionary where keys are image names or IDs and values are classification - labels - """ - if storage_client: - # file on Cloud - success = False - retry_count = 0 - while retry_count < 4: - try: - blob = storage_client.get_blob(file_path) - if not blob: - return {} - if blob.size > MAX_ALLOWED_CLASSIFICATION_RESULT_SIZE: - logging.warning('Skipping classification result because it''s too ' - 'big: %d bytes for %s', blob.size, file_path) - return None - buf = BytesIO() - blob.download_to_file(buf) - buf.seek(0) - success = True - break - except Exception: - retry_count += 1 - time.sleep(5) - if not success: - return None - else: - # local file - try: - with open(file_path, 'rb') as f: - buf = BytesIO(f.read()) - except IOError: - return None - result = {} - if PY3: - buf = StringIO(buf.read().decode('UTF-8')) - for row in csv.reader(buf): - try: - image_filename = row[0] - if image_filename.endswith('.png') or image_filename.endswith('.jpg'): - image_filename = image_filename[:image_filename.rfind('.')] - label = int(row[1]) - except (IndexError, ValueError): - continue - result[image_filename] = label - return result - - -def analyze_one_classification_result(storage_client, file_path, - adv_batch, dataset_batches, - dataset_meta): - """Reads and analyzes one classification result. - - This method reads file with classification result and counts - how many images were classified correctly and incorrectly, - how many times target class was hit and total number of images. - - Args: - storage_client: instance of CompetitionStorageClient - file_path: result file path - adv_batch: AversarialBatches.data[adv_batch_id] - adv_batch_id is stored in each ClassificationBatch entity - dataset_batches: instance of DatasetBatches - dataset_meta: instance of DatasetMetadata - - Returns: - Tuple of (count_correctly_classified, count_errors, count_hit_target_class, - num_images) - """ - class_result = read_classification_results(storage_client, file_path) - if class_result is None: - return 0, 0, 0, 0 - adv_images = adv_batch['images'] - dataset_batch_images = ( - dataset_batches.data[adv_batch['dataset_batch_id']]['images']) - count_correctly_classified = 0 - count_errors = 0 - count_hit_target_class = 0 - num_images = 0 - for adv_img_id, label in iteritems(class_result): - if adv_img_id not in adv_images: - continue - num_images += 1 - clean_image_id = adv_images[adv_img_id]['clean_image_id'] - dataset_image_id = ( - dataset_batch_images[clean_image_id]['dataset_image_id']) - if label == dataset_meta.get_true_label(dataset_image_id): - count_correctly_classified += 1 - else: - count_errors += 1 - if label == dataset_meta.get_target_class(dataset_image_id): - count_hit_target_class += 1 - return (count_correctly_classified, count_errors, - count_hit_target_class, num_images) - - -class ResultMatrix(object): - """Sparse matrix where rows and columns are indexed using string. - - This matrix is used to store resutls of the competition evaluation. - """ - - def __init__(self, default_value=0): - """Initializes empty matrix.""" - self._items = {} - self._dim0 = set() - self._dim1 = set() - self._default_value = default_value - - @property - def dim0(self): - """Returns set of rows.""" - return self._dim0 - - @property - def dim1(self): - """Returns set of columns.""" - return self._dim1 - - def __getitem__(self, key): - """Returns element of the matrix indexed by given key. - - Args: - key: tuple of (row_idx, column_idx) - - Returns: - Element of the matrix - - Raises: - IndexError: if key is invalid. - """ - if not isinstance(key, tuple) or len(key) != 2: - raise IndexError('Invalid index: {0}'.format(key)) - return self._items.get(key, self._default_value) - - def __setitem__(self, key, value): - """Sets element of the matrix at position indexed by key. - - Args: - key: tuple of (row_idx, column_idx) - value: new value of the element of the matrix - - Raises: - IndexError: if key is invalid. - """ - if not isinstance(key, tuple) or len(key) != 2: - raise IndexError('Invalid index: {0}'.format(key)) - self._dim0.add(key[0]) - self._dim1.add(key[1]) - self._items[key] = value - - def save_to_file(self, filename, remap_dim0=None, remap_dim1=None): - """Saves matrix to the file. - - Args: - filename: name of the file where to save matrix - remap_dim0: dictionary with mapping row indices to row names which should - be saved to file. If none then indices will be used as names. - remap_dim1: dictionary with mapping column indices to column names which - should be saved to file. If none then indices will be used as names. - """ - # rows - first index - # columns - second index - with open(filename, 'w') as fobj: - columns = list(sorted(self._dim1)) - for col in columns: - fobj.write(',') - fobj.write(str(remap_dim1[col] if remap_dim1 else col)) - fobj.write('\n') - for row in sorted(self._dim0): - fobj.write(str(remap_dim0[row] if remap_dim0 else row)) - for col in columns: - fobj.write(',') - fobj.write(str(self[row, col])) - fobj.write('\n') - - -class ClassificationBatches(object): - """Class which generates and stores classification batches. - - Each classification batch contains result of the classification of one - batch of adversarial images. - """ - - def __init__(self, datastore_client, storage_client, round_name): - """Initializes ClassificationBatches. - - Args: - datastore_client: instance of CompetitionDatastoreClient - storage_client: instance of CompetitionStorageClient - round_name: name of the round - """ - self._datastore_client = datastore_client - self._storage_client = storage_client - self._round_name = round_name - # Data is dict of dicts {CLASSIFICATION_BATCH_ID: { ... }} - self._data = {} - - def serialize(self, fobj): - """Serializes data stored in this class.""" - pickle.dump(self._data, fobj) - - def deserialize(self, fobj): - """Deserializes data from file into this class.""" - self._data = pickle.load(fobj) - - @property - def data(self): - """Returns dictionary with data.""" - return self._data - - def __getitem__(self, key): - """Returns one classification batch by given key.""" - return self._data[key] - - def init_from_adversarial_batches_write_to_datastore(self, submissions, - adv_batches): - """Populates data from adversarial batches and writes to datastore. - - Args: - submissions: instance of CompetitionSubmissions - adv_batches: instance of AversarialBatches - """ - # prepare classification batches - idx = 0 - for s_id in iterkeys(submissions.defenses): - for adv_id in iterkeys(adv_batches.data): - class_batch_id = CLASSIFICATION_BATCH_ID_PATTERN.format(idx) - idx += 1 - self.data[class_batch_id] = { - 'adversarial_batch_id': adv_id, - 'submission_id': s_id, - 'result_path': os.path.join( - self._round_name, - CLASSIFICATION_BATCHES_SUBDIR, - s_id + '_' + adv_id + '.csv') - } - # save them to datastore - client = self._datastore_client - with client.no_transact_batch() as batch: - for key, value in iteritems(self.data): - entity = client.entity(client.key(KIND_CLASSIFICATION_BATCH, key)) - entity.update(value) - batch.put(entity) - - def init_from_datastore(self): - """Initializes data by reading it from the datastore.""" - self._data = {} - client = self._datastore_client - for entity in client.query_fetch(kind=KIND_CLASSIFICATION_BATCH): - class_batch_id = entity.key.flat_path[-1] - self.data[class_batch_id] = dict(entity) - - def read_batch_from_datastore(self, class_batch_id): - """Reads and returns single batch from the datastore.""" - client = self._datastore_client - key = client.key(KIND_CLASSIFICATION_BATCH, class_batch_id) - result = client.get(key) - if result is not None: - return dict(result) - else: - raise KeyError( - 'Key {0} not found in the datastore'.format(key.flat_path)) - - def compute_classification_results(self, adv_batches, dataset_batches, - dataset_meta, defense_work=None): - """Computes classification results. - - Args: - adv_batches: instance of AversarialBatches - dataset_batches: instance of DatasetBatches - dataset_meta: instance of DatasetMetadata - defense_work: instance of DefenseWorkPieces - - Returns: - accuracy_matrix, error_matrix, hit_target_class_matrix, - processed_images_count - """ - class_batch_to_work = {} - if defense_work: - for v in itervalues(defense_work.work): - class_batch_to_work[v['output_classification_batch_id']] = v - - # accuracy_matrix[defense_id, attack_id] = num correctly classified - accuracy_matrix = ResultMatrix() - # error_matrix[defense_id, attack_id] = num misclassfied - error_matrix = ResultMatrix() - # hit_target_class_matrix[defense_id, attack_id] = num hit target class - hit_target_class_matrix = ResultMatrix() - # processed_images_count[defense_id] = num processed images by defense - processed_images_count = {} - - total_count = len(self.data) - processed_count = 0 - logging.info('Processing %d files with classification results', - len(self.data)) - for k, v in iteritems(self.data): - if processed_count % 100 == 0: - logging.info('Processed %d out of %d classification results', - processed_count, total_count) - processed_count += 1 - defense_id = v['submission_id'] - adv_batch = adv_batches.data[v['adversarial_batch_id']] - attack_id = adv_batch['submission_id'] - - work_item = class_batch_to_work.get(k) - required_work_stats = ['stat_correct', 'stat_error', 'stat_target_class', - 'stat_num_images'] - if work_item and work_item['error']: - # ignore batches with error - continue - if work_item and all(work_item.get(i) is not None - for i in required_work_stats): - count_correctly_classified = work_item['stat_correct'] - count_errors = work_item['stat_error'] - count_hit_target_class = work_item['stat_target_class'] - num_images = work_item['stat_num_images'] - else: - logging.warning('Recomputing accuracy for classification batch %s', k) - (count_correctly_classified, count_errors, count_hit_target_class, - num_images) = analyze_one_classification_result( - self._storage_client, v['result_path'], adv_batch, dataset_batches, - dataset_meta) - - # update accuracy and hit target class - accuracy_matrix[defense_id, attack_id] += count_correctly_classified - error_matrix[defense_id, attack_id] += count_errors - hit_target_class_matrix[defense_id, attack_id] += count_hit_target_class - # update number of processed images - processed_images_count[defense_id] = ( - processed_images_count.get(defense_id, 0) + num_images) - return (accuracy_matrix, error_matrix, hit_target_class_matrix, - processed_images_count) - - def __str__(self): - """Returns human readable string representation, useful for debugging.""" - buf = StringIO() - for idx, (class_batch_id, class_val) in enumerate(iteritems(self.data)): - if idx >= TO_STR_MAX_BATCHES: - buf.write(u' ...\n') - break - buf.write(u' ClassBatch "{0}"\n'.format(class_batch_id)) - buf.write(u' {0}\n'.format(str(class_val))) - return buf.getvalue() diff --git a/examples/nips17_adversarial_competition/eval_infra/code/eval_lib/cloud_client.py b/examples/nips17_adversarial_competition/eval_infra/code/eval_lib/cloud_client.py deleted file mode 100644 index d57fb615b..000000000 --- a/examples/nips17_adversarial_competition/eval_infra/code/eval_lib/cloud_client.py +++ /dev/null @@ -1,245 +0,0 @@ -"""Helper classes and wrappers to access Google Cloud. - -Google Cloud API is encapsulated with these wrappers, so it's easier to -test the code with help of fake (declared in testing/fake_cloud_client.py). -""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import logging -import math -import random -import time -import traceback as tb - -from google.cloud import datastore -from google.cloud import storage -from google.cloud.exceptions import TooManyRequests - -# To resolve InsecurePlatformWarning -try: - import urllib3.contrib.pyopenssl - urllib3.contrib.pyopenssl.inject_into_urllib3() - print('Pyopenssl fix for urllib3 succesfully injected.') -except ImportError: - print('Failed to inject pyopenssl fix for urllib3.') - -# Cloud Datastore has 500 mutations per batch limit. -MAX_MUTATIONS_IN_BATCH = 500 - - -class CompetitionStorageClient(object): - """Client wrapper to access Google Cloud Storage.""" - - def __init__(self, project_id, bucket_name): - """Initialize client with project id and name of the storage bucket.""" - self.project_id = project_id - self.bucket_name = bucket_name - self.client = storage.Client(project=project_id) - self.bucket = self.client.get_bucket(bucket_name) - - def list_blobs(self, prefix=''): - """Lists names of all blobs by their prefix.""" - return [b.name for b in self.bucket.list_blobs(prefix=prefix)] - - def get_blob(self, blob_name): - """Gets google.cloud.storage.blob.Blob object by blob name.""" - return self.bucket.get_blob(blob_name) - - def new_blob(self, blob_name): - """Creates new storage blob with provided name.""" - return storage.Blob(blob_name, self.bucket) - - -class NoTransactionBatch(object): - """No transaction batch to write large number of entities. - - Usage: - client = ... # instance of CompetitionDatastoreClient - with NoTransactionBatch(client) as batch: - batch.put(entity1) - ... - batch.put(entityN) - batch.delete(del_entity1) - ... - batch.delete(del_entityM) - - It could be also used via CompetitionDatastoreClient.no_transact_batch: - client = ... # instance of CompetitionDatastoreClient - with client.no_transact_batch() as batch: - batch.put(entity1) - ... - batch.put(entityN) - batch.delete(del_entity1) - ... - batch.delete(del_entityM) - - Most methods of this class are provided to simulate - google.cloud.datastore.batch.Batch interface, so they could be used - interchangeably. - Practically speaking, this class works by maintaining a buffer of - pending mutations and committing them as soon as the length of the buffer - reaches MAX_MUTATIONS_IN_BATCH. - """ - - def __init__(self, client): - """Init NoTransactionBatch with provided CompetitionDatastoreClient.""" - self._client = client - self._cur_batch = None - self._num_mutations = 0 - - def begin(self): - """Begins a batch.""" - if self._cur_batch: - raise ValueError('Previous batch is not committed.') - self._cur_batch = self._client.batch() - self._cur_batch.begin() - self._num_mutations = 0 - - def commit(self): - """Commits all pending mutations.""" - self._cur_batch.commit() - self._cur_batch = None - self._num_mutations = 0 - - def rollback(self): - """Rolls back pending mutations. - - Keep in mind that NoTransactionBatch splits all mutations into smaller - batches and commit them as soon as mutation buffer reaches maximum length. - That's why rollback method will only roll back pending mutations from the - buffer, but won't be able to rollback already committed mutations. - """ - try: - if self._cur_batch: - self._cur_batch.rollback() - except ValueError: - # ignore "Batch must be in progress to rollback" error - pass - self._cur_batch = None - self._num_mutations = 0 - - def put(self, entity): - """Adds mutation of the entity to the mutation buffer. - - If mutation buffer reaches its capacity then this method commit all pending - mutations from the buffer and emties it. - - Args: - entity: entity which should be put into the datastore - """ - self._cur_batch.put(entity) - self._num_mutations += 1 - if self._num_mutations >= MAX_MUTATIONS_IN_BATCH: - self.commit() - self.begin() - - def delete(self, key): - """Adds deletion of the entity with given key to the mutation buffer. - - If mutation buffer reaches its capacity then this method commit all pending - mutations from the buffer and emties it. - - Args: - key: key of the entity which should be deleted - """ - self._cur_batch.delete(key) - self._num_mutations += 1 - if self._num_mutations >= MAX_MUTATIONS_IN_BATCH: - self.commit() - self.begin() - - def __enter__(self): - self.begin() - return self - - def __exit__(self, exc_type, exc_value, traceback): - if exc_type is None: - self.commit() - else: - err = tb.format_exception(exc_type, exc_value, traceback) - logging.error('Exception occurred during write:\n%s', err) - self.rollback() - - -def iterate_with_exp_backoff(base_iter, - max_num_tries=6, - max_backoff=300.0, - start_backoff=4.0, - backoff_multiplier=2.0, - frac_random_backoff=0.25): - """Iterate with exponential backoff on failures. - - Useful to wrap results of datastore Query.fetch to avoid 429 error. - - Args: - base_iter: basic iterator of generator object - max_num_tries: maximum number of tries for each request - max_backoff: maximum backoff, in seconds - start_backoff: initial value of backoff - backoff_multiplier: backoff multiplier - frac_random_backoff: fraction of the value of random part of the backoff - - Yields: - values of yielded by base iterator - """ - try_number = 0 - if hasattr(base_iter, '__iter__'): - base_iter = iter(base_iter) - while True: - try: - yield next(base_iter) - try_number = 0 - except StopIteration: - break - except TooManyRequests as e: - logging.warning('TooManyRequests error: %s', tb.format_exc()) - if try_number >= max_num_tries: - logging.error('Number of tries exceeded, too many requests: %s', e) - raise - # compute sleep time for truncated exponential backoff - sleep_time = start_backoff * math.pow(backoff_multiplier, try_number) - sleep_time *= (1.0 + frac_random_backoff * random.random()) - sleep_time = min(sleep_time, max_backoff) - logging.warning('Too many requests error, ' - 'retrying with exponential backoff %.3f', sleep_time) - time.sleep(sleep_time) - try_number += 1 - - -class CompetitionDatastoreClient(object): - """Client wrapper to access Google Cloud Datastore.""" - - def __init__(self, project_id, namespace=None): - """Init this method with given project id and optional namespace.""" - self._client = datastore.Client(project=project_id, namespace=namespace) - - def key(self, *args, **kwargs): - """Creates datastore key.""" - return self._client.key(*args, **kwargs) - - def entity(self, key): - """Creates datastore entity.""" - return datastore.Entity(key) - - def no_transact_batch(self): - """Starts batch of mutation which is committed without transaction.""" - return NoTransactionBatch(self._client) - - def batch(self): - """Starts batch of mutations.""" - return self._client.batch() - - def transaction(self): - """Starts transaction.""" - return self._client.transaction() - - def get(self, key, transaction=None): - """Retrieves an entity given its key.""" - return self._client.get(key, transaction=transaction) - - def query_fetch(self, **kwargs): - """Queries datastore (using exponential backoff).""" - return iterate_with_exp_backoff(self._client.query(**kwargs).fetch()) diff --git a/examples/nips17_adversarial_competition/eval_infra/code/eval_lib/dataset_helper.py b/examples/nips17_adversarial_competition/eval_infra/code/eval_lib/dataset_helper.py deleted file mode 100644 index 0606748b9..000000000 --- a/examples/nips17_adversarial_competition/eval_infra/code/eval_lib/dataset_helper.py +++ /dev/null @@ -1,159 +0,0 @@ -"""Various helpers for the dataset. -""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import csv -import hashlib -import logging -import os -import shutil -import subprocess - -import numpy as np -from PIL import Image - -from six import iteritems - - -class DatasetMetadata(object): - """Helper class which loads and stores dataset metadata. - - Dataset metadata stored by this class contains true labels and target classes - for all images in the dataset. - """ - - def __init__(self, fobj): - """Initializes instance of DatasetMetadata. - - Args: - fobj: file object - """ - self._true_labels = {} - self._target_classes = {} - reader = csv.reader(fobj) - header_row = next(reader) - try: - row_idx_image_id = header_row.index('ImageId') - row_idx_true_label = header_row.index('TrueLabel') - row_idx_target_class = header_row.index('TargetClass') - except ValueError: - raise IOError('Invalid format of the dataset metadata.') - for row in reader: - if len(row) < len(header_row): - # skip partial or empty lines - continue - try: - image_id = row[row_idx_image_id] - self._true_labels[image_id] = int(row[row_idx_true_label]) - self._target_classes[image_id] = int(row[row_idx_target_class]) - except (IndexError, ValueError): - raise IOError('Invalid format of dataset metadata') - - def get_true_label(self, image_id): - """Returns true label for image with given ID.""" - return self._true_labels[image_id] - - def get_target_class(self, image_id): - """Returns target class for image with given ID.""" - return self._target_classes[image_id] - - def save_target_classes_for_batch(self, - filename, - image_batches, - batch_id): - """Saves file with target class for given dataset batch. - - Args: - filename: output filename - image_batches: instance of ImageBatchesBase with dataset batches - batch_id: dataset batch ID - """ - images = image_batches.data[batch_id]['images'] - with open(filename, 'w') as f: - for image_id, image_val in iteritems(images): - target_class = self.get_target_class(image_val['dataset_image_id']) - f.write('{0}.png,{1}\n'.format(image_id, target_class)) - - -def enforce_epsilon_and_compute_hash(dataset_batch_dir, adv_dir, output_dir, - epsilon): - """Enforces size of perturbation on images, and compute hashes for all images. - - Args: - dataset_batch_dir: directory with the images of specific dataset batch - adv_dir: directory with generated adversarial images - output_dir: directory where to copy result - epsilon: size of perturbation - - Returns: - dictionary with mapping form image ID to hash. - """ - dataset_images = [f for f in os.listdir(dataset_batch_dir) - if f.endswith('.png')] - image_hashes = {} - resize_warning = False - for img_name in dataset_images: - if not os.path.exists(os.path.join(adv_dir, img_name)): - logging.warning('Image %s not found in the output', img_name) - continue - image = np.array( - Image.open(os.path.join(dataset_batch_dir, img_name)).convert('RGB')) - image = image.astype('int32') - image_max_clip = np.clip(image + epsilon, 0, 255).astype('uint8') - image_min_clip = np.clip(image - epsilon, 0, 255).astype('uint8') - # load and resize adversarial image if needed - adv_image = Image.open(os.path.join(adv_dir, img_name)).convert('RGB') - # Image.size is reversed compared to np.array.shape - if adv_image.size[::-1] != image.shape[:2]: - resize_warning = True - adv_image = adv_image.resize((image.shape[1], image.shape[0]), - Image.BICUBIC) - adv_image = np.array(adv_image) - clipped_adv_image = np.clip(adv_image, - image_min_clip, - image_max_clip) - Image.fromarray(clipped_adv_image).save(os.path.join(output_dir, img_name)) - # compute hash - image_hashes[img_name[:-4]] = hashlib.sha1( - clipped_adv_image.view(np.uint8)).hexdigest() - if resize_warning: - logging.warning('One or more adversarial images had incorrect size') - return image_hashes - - -def download_dataset(storage_client, image_batches, target_dir, - local_dataset_copy=None): - """Downloads dataset, organize it by batches and rename images. - - Args: - storage_client: instance of the CompetitionStorageClient - image_batches: subclass of ImageBatchesBase with data about images - target_dir: target directory, should exist and be empty - local_dataset_copy: directory with local dataset copy, if local copy is - available then images will be takes from there instead of Cloud Storage - - Data in the target directory will be organized into subdirectories by batches, - thus path to each image will be "target_dir/BATCH_ID/IMAGE_ID.png" - where BATCH_ID - ID of the batch (key of image_batches.data), - IMAGE_ID - ID of the image (key of image_batches.data[batch_id]['images']) - """ - for batch_id, batch_value in iteritems(image_batches.data): - batch_dir = os.path.join(target_dir, batch_id) - os.mkdir(batch_dir) - for image_id, image_val in iteritems(batch_value['images']): - dst_filename = os.path.join(batch_dir, image_id + '.png') - # try to use local copy first - if local_dataset_copy: - local_filename = os.path.join(local_dataset_copy, - os.path.basename(image_val['image_path'])) - if os.path.exists(local_filename): - shutil.copyfile(local_filename, dst_filename) - continue - # download image from cloud - cloud_path = ('gs://' + storage_client.bucket_name - + '/' + image_val['image_path']) - if not os.path.exists(dst_filename): - subprocess.call(['gsutil', 'cp', cloud_path, dst_filename]) diff --git a/examples/nips17_adversarial_competition/eval_infra/code/eval_lib/image_batches.py b/examples/nips17_adversarial_competition/eval_infra/code/eval_lib/image_batches.py deleted file mode 100644 index 480077981..000000000 --- a/examples/nips17_adversarial_competition/eval_infra/code/eval_lib/image_batches.py +++ /dev/null @@ -1,297 +0,0 @@ -"""Module with classes to read and store image batches. -""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import copy -from io import BytesIO -from io import StringIO -import itertools -import logging -import os -import zipfile - -from six import iteritems -from six import iterkeys -from six import itervalues - - -# Cloud Datastore constants -KIND_DATASET_BATCH = u'DatasetBatch' -KIND_DATASET_IMAGE = u'DatasetImage' -KIND_ADVERSARIAL_BATCH = u'AdversarialBatch' -KIND_ADVERSARIAL_IMAGE = u'AdversarialImage' - -# Cloud Datastore ID patterns -DATASET_BATCH_ID_PATTERN = u'BATCH{:03}' -DATASET_IMAGE_ID_PATTERN = u'IMG{:06}' -ADVERSARIAL_BATCH_ID_PATTERN = u'ADVBATCH{:03}' -ADVERSARIAL_IMAGE_ID_PATTERN = u'ADV{:06}' - -# Default list of possible adversarial peturbations -DEFAULT_EPSILON = [4, 8, 12, 16] - -# Constants for __str__ -TO_STR_MAX_BATCHES = 5 -TO_STR_MAX_IMAGES_PER_BATCH = 10 - - -class ImageBatchesBase(object): - """Base class to store image batches. - - Subclasses of this class are used to store batches of images from the dataset - or batches of adversarial images. - """ - - def __init__(self, datastore_client, entity_kind_batches, entity_kind_images): - """Initialize ImageBatchesBase. - - Args: - datastore_client: instance of the CompetitionDatastoreClient - entity_kind_batches: Cloud Datastore entity kind which is used to store - batches of images. - entity_kind_images: Cloud Datastore entity kind which is used to store - individual images. - """ - self._datastore_client = datastore_client - self._entity_kind_batches = entity_kind_batches - self._entity_kind_images = entity_kind_images - # data is a dictionary with following structure: - # self._data[batch_id] = { - # 'batch_k1': batch_v1, - # ... - # 'batch_kN': batch_vN, - # 'images': { - # image_id: { 'img_k1': img_v1, ... } - # } - # } - self._data = {} - - def _write_single_batch_images_internal(self, batch_id, client_batch): - """Helper method to write images from single batch into datastore.""" - client = self._datastore_client - batch_key = client.key(self._entity_kind_batches, batch_id) - for img_id, img in iteritems(self._data[batch_id]['images']): - img_entity = client.entity( - client.key(self._entity_kind_images, img_id, parent=batch_key)) - for k, v in iteritems(img): - img_entity[k] = v - client_batch.put(img_entity) - - def write_to_datastore(self): - """Writes all image batches to the datastore.""" - client = self._datastore_client - with client.no_transact_batch() as client_batch: - for batch_id, batch_data in iteritems(self._data): - batch_key = client.key(self._entity_kind_batches, batch_id) - batch_entity = client.entity(batch_key) - for k, v in iteritems(batch_data): - if k != 'images': - batch_entity[k] = v - client_batch.put(batch_entity) - self._write_single_batch_images_internal(batch_id, client_batch) - - def write_single_batch_images_to_datastore(self, batch_id): - """Writes only images from one batch to the datastore.""" - client = self._datastore_client - with client.no_transact_batch() as client_batch: - self._write_single_batch_images_internal(batch_id, client_batch) - - def init_from_datastore(self): - """Initializes batches by reading from the datastore.""" - self._data = {} - for entity in self._datastore_client.query_fetch( - kind=self._entity_kind_batches): - batch_id = entity.key.flat_path[-1] - self._data[batch_id] = dict(entity) - self._data[batch_id]['images'] = {} - for entity in self._datastore_client.query_fetch( - kind=self._entity_kind_images): - batch_id = entity.key.flat_path[-3] - image_id = entity.key.flat_path[-1] - self._data[batch_id]['images'][image_id] = dict(entity) - - @property - def data(self): - """Dictionary with data.""" - return self._data - - def __getitem__(self, key): - """Returns specific batch by its key.""" - return self._data[key] - - def add_batch(self, batch_id, batch_properties=None): - """Adds batch with give ID and list of properties.""" - if batch_properties is None: - batch_properties = {} - if not isinstance(batch_properties, dict): - raise ValueError('batch_properties has to be dict, however it was: ' - + str(type(batch_properties))) - self._data[batch_id] = batch_properties.copy() - self._data[batch_id]['images'] = {} - - def add_image(self, batch_id, image_id, image_properties=None): - """Adds image to given batch.""" - if batch_id not in self._data: - raise KeyError('Batch with ID "{0}" does not exist'.format(batch_id)) - if image_properties is None: - image_properties = {} - if not isinstance(image_properties, dict): - raise ValueError('image_properties has to be dict, however it was: ' - + str(type(image_properties))) - self._data[batch_id]['images'][image_id] = image_properties.copy() - - def count_num_images(self): - """Counts total number of images in all batches.""" - return sum([len(v['images']) for v in itervalues(self.data)]) - - def __str__(self): - """Returns human readable representation, which is useful for debugging.""" - buf = StringIO() - for batch_idx, (batch_id, batch_val) in enumerate(iteritems(self.data)): - if batch_idx >= TO_STR_MAX_BATCHES: - buf.write(u'...\n') - break - buf.write(u'BATCH "{0}"\n'.format(batch_id)) - for k, v in iteritems(batch_val): - if k != 'images': - buf.write(u' {0}: {1}\n'.format(k, v)) - for img_idx, img_id in enumerate(iterkeys(batch_val['images'])): - if img_idx >= TO_STR_MAX_IMAGES_PER_BATCH: - buf.write(u' ...') - break - buf.write(u' IMAGE "{0}" -- {1}\n'.format(img_id, - batch_val['images'][img_id])) - buf.write(u'\n') - return buf.getvalue() - - -class DatasetBatches(ImageBatchesBase): - """Class which stores batches of images from the dataset.""" - - def __init__(self, datastore_client, storage_client, dataset_name): - """Initializes DatasetBatches. - - Args: - datastore_client: instance of CompetitionDatastoreClient - storage_client: instance of CompetitionStorageClient - dataset_name: name of the dataset ('dev' or 'final') - """ - super(DatasetBatches, self).__init__( - datastore_client=datastore_client, - entity_kind_batches=KIND_DATASET_BATCH, - entity_kind_images=KIND_DATASET_IMAGE) - self._storage_client = storage_client - self._dataset_name = dataset_name - - def _read_image_list(self, skip_image_ids=None): - """Reads list of dataset images from the datastore.""" - if skip_image_ids is None: - skip_image_ids = [] - images = self._storage_client.list_blobs( - prefix=os.path.join('dataset', self._dataset_name) + '/') - zip_files = [i for i in images if i.endswith('.zip')] - if len(zip_files) == 1: - # we have a zip archive with images - zip_name = zip_files[0] - logging.info('Reading list of images from zip file %s', zip_name) - blob = self._storage_client.get_blob(zip_name) - buf = BytesIO() - logging.info('Downloading zip') - blob.download_to_file(buf) - buf.seek(0) - logging.info('Reading content of the zip') - with zipfile.ZipFile(buf) as f: - images = [os.path.join(zip_name, os.path.basename(n)) - for n in f.namelist() if n.endswith('.png')] - buf.close() - logging.info('Found %d images', len(images)) - else: - # we have just a directory with images, filter non-PNG files - logging.info('Reading list of images from png files in storage') - images = [i for i in images if i.endswith('.png')] - logging.info('Found %d images', len(images)) - # filter images which should be skipped - images = [i for i in images - if os.path.basename(i)[:-4] not in skip_image_ids] - # assign IDs to images - images = [(DATASET_IMAGE_ID_PATTERN.format(idx), i) - for idx, i in enumerate(sorted(images))] - return images - - def init_from_storage_write_to_datastore(self, - batch_size=100, - allowed_epsilon=None, - skip_image_ids=None, - max_num_images=None): - """Initializes dataset batches from the list of images in the datastore. - - Args: - batch_size: batch size - allowed_epsilon: list of allowed epsilon or None to use default - skip_image_ids: list of image ids to skip - max_num_images: maximum number of images to read - """ - if allowed_epsilon is None: - allowed_epsilon = copy.copy(DEFAULT_EPSILON) - # init dataset batches from data in storage - self._dataset_batches = {} - # read all blob names from storage - images = self._read_image_list(skip_image_ids) - if max_num_images: - images = images[:max_num_images] - for batch_idx, batch_start in enumerate(range(0, len(images), batch_size)): - batch = images[batch_start:batch_start+batch_size] - batch_id = DATASET_BATCH_ID_PATTERN.format(batch_idx) - batch_epsilon = allowed_epsilon[batch_idx % len(allowed_epsilon)] - self.add_batch(batch_id, {'epsilon': batch_epsilon}) - for image_id, image_path in batch: - self.add_image(batch_id, image_id, - {'dataset_image_id': os.path.basename(image_path)[:-4], - 'image_path': image_path}) - # write data to datastore - self.write_to_datastore() - - -class AversarialBatches(ImageBatchesBase): - """Class which stores batches of adversarial images generated by attacks.""" - - def __init__(self, datastore_client): - """Initializes AversarialBatches. - - Args: - datastore_client: instance of CompetitionDatastoreClient - """ - super(AversarialBatches, self).__init__( - datastore_client=datastore_client, - entity_kind_batches=KIND_ADVERSARIAL_BATCH, - entity_kind_images=KIND_ADVERSARIAL_IMAGE) - - def init_from_dataset_and_submissions_write_to_datastore( - self, dataset_batches, attack_submission_ids): - """Init list of adversarial batches from dataset batches and submissions. - - Args: - dataset_batches: instances of DatasetBatches - attack_submission_ids: iterable with IDs of all (targeted and nontargeted) - attack submissions, could be obtains as - CompetitionSubmissions.get_all_attack_ids() - """ - batches_x_attacks = itertools.product(dataset_batches.data.keys(), - attack_submission_ids) - for idx, (dataset_batch_id, attack_id) in enumerate(batches_x_attacks): - adv_batch_id = ADVERSARIAL_BATCH_ID_PATTERN.format(idx) - self.add_batch(adv_batch_id, - {'dataset_batch_id': dataset_batch_id, - 'submission_id': attack_id}) - self.write_to_datastore() - - def count_generated_adv_examples(self): - """Returns total number of all generated adversarial examples.""" - result = {} - for v in itervalues(self.data): - s_id = v['submission_id'] - result[s_id] = result.get(s_id, 0) + len(v['images']) - return result diff --git a/examples/nips17_adversarial_competition/eval_infra/code/eval_lib/submissions.py b/examples/nips17_adversarial_competition/eval_infra/code/eval_lib/submissions.py deleted file mode 100644 index 0c4e92838..000000000 --- a/examples/nips17_adversarial_competition/eval_infra/code/eval_lib/submissions.py +++ /dev/null @@ -1,248 +0,0 @@ -"""Classes and functions to manage submissions. -""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -from collections import namedtuple -from io import StringIO -import os -from six import iteritems - -# Cloud Storage directories -ATTACK_SUBDIR = 'submissions/nontargeted' -TARGETED_ATTACK_SUBDIR = 'submissions/targeted' -DEFENSE_SUBDIR = 'submissions/defense' - -# Cloud Datastore entity keys -ATTACKS_ENTITY_KEY = [u'SubmissionType', u'Attacks'] -TARGET_ATTACKS_ENTITY_KEY = [u'SubmissionType', u'TargetedAttacks'] -DEFENSES_ENTITY_KEY = [u'SubmissionType', u'Defenses'] -KIND_SUBMISSION = u'Submission' - -# Cloud Datastore ID patterns -ATTACK_ID_PATTERN = u'SUBA{:03}' -TARGETED_ATTACK_ID_PATTERN = u'SUBT{:03}' -DEFENSE_ID_PATTERN = u'SUBD{:03}' - -# Constants for __str__ -TO_STR_MAX_SUBMISSIONS = 5 - -ALLOWED_EXTENSIONS = ['.zip', '.tar', '.tar.gz'] - - -def participant_from_submission_path(submission_path): - """Parses type of participant based on submission filename. - - Args: - submission_path: path to the submission in Google Cloud Storage - - Returns: - dict with one element. Element key correspond to type of participant - (team, baseline), element value is ID of the participant. - - Raises: - ValueError: is participant can't be determined based on submission path. - """ - basename = os.path.basename(submission_path) - file_ext = None - for e in ALLOWED_EXTENSIONS: - if basename.endswith(e): - file_ext = e - break - if not file_ext: - raise ValueError('Invalid submission path: ' + submission_path) - basename = basename[:-len(file_ext)] - if basename.isdigit(): - return {'team_id': int(basename)} - if basename.startswith('baseline_'): - return {'baseline_id': basename[len('baseline_'):]} - raise ValueError('Invalid submission path: ' + submission_path) - - -SubmissionDescriptor = namedtuple('SubmissionDescriptor', - ['path', 'participant_id']) - - -class CompetitionSubmissions(object): - """Class which holds information about all submissions. - - All submissions are stored in 3 dictionaries, one for targeted attacks, - one for non-targeted attacks and one for defenses. - All submissions are identified using internal competition ID, - which looks like 'SUB????'. Additionally each submission has external - identified which could be name of baseline or Kaggle ID. - External ID only used when list of submissions is formed and when - scorebored is built. Internal submission IDs are used for all actual - evaluation. Thus all identifiers are internal IDs unless otherwise noted. - """ - - def __init__(self, datastore_client, storage_client, round_name): - """Initializes CompetitionSubmissions. - - Args: - datastore_client: instance of CompetitionDatastoreClient - storage_client: instance of CompetitionStorageClient - round_name: name of the round - """ - self._datastore_client = datastore_client - self._storage_client = storage_client - self._round_name = round_name - # each of the variables is a dictionary, - # where key - submission ID - # value - SubmissionDescriptor namedtuple - self._attacks = None - self._targeted_attacks = None - self._defenses = None - - def _load_submissions_from_datastore_dir(self, dir_suffix, id_pattern): - """Loads list of submissions from the directory. - - Args: - dir_suffix: suffix of the directory where submissions are stored, - one of the folowing constants: ATTACK_SUBDIR, TARGETED_ATTACK_SUBDIR - or DEFENSE_SUBDIR. - id_pattern: pattern which is used to generate (internal) IDs - for submissins. One of the following constants: ATTACK_ID_PATTERN, - TARGETED_ATTACK_ID_PATTERN or DEFENSE_ID_PATTERN. - - Returns: - dictionary with all found submissions - """ - submissions = self._storage_client.list_blobs( - prefix=os.path.join(self._round_name, dir_suffix)) - return { - id_pattern.format(idx): SubmissionDescriptor( - path=s, participant_id=participant_from_submission_path(s)) - for idx, s in enumerate(submissions) - } - - def init_from_storage_write_to_datastore(self): - """Init list of sumibssions from Storage and saves them to Datastore. - - Should be called only once (typically by master) during evaluation of - the competition. - """ - # Load submissions - self._attacks = self._load_submissions_from_datastore_dir( - ATTACK_SUBDIR, ATTACK_ID_PATTERN) - self._targeted_attacks = self._load_submissions_from_datastore_dir( - TARGETED_ATTACK_SUBDIR, TARGETED_ATTACK_ID_PATTERN) - self._defenses = self._load_submissions_from_datastore_dir( - DEFENSE_SUBDIR, DEFENSE_ID_PATTERN) - self._write_to_datastore() - - def _write_to_datastore(self): - """Writes all submissions to datastore.""" - # Populate datastore - roots_and_submissions = zip([ATTACKS_ENTITY_KEY, - TARGET_ATTACKS_ENTITY_KEY, - DEFENSES_ENTITY_KEY], - [self._attacks, - self._targeted_attacks, - self._defenses]) - client = self._datastore_client - with client.no_transact_batch() as batch: - for root_key, submissions in roots_and_submissions: - batch.put(client.entity(client.key(*root_key))) - for k, v in iteritems(submissions): - entity = client.entity(client.key( - *(root_key + [KIND_SUBMISSION, k]))) - entity['submission_path'] = v.path - entity.update(participant_from_submission_path(v.path)) - batch.put(entity) - - def init_from_datastore(self): - """Init list of submission from Datastore. - - Should be called by each worker during initialization. - """ - self._attacks = {} - self._targeted_attacks = {} - self._defenses = {} - for entity in self._datastore_client.query_fetch(kind=KIND_SUBMISSION): - submission_id = entity.key.flat_path[-1] - submission_path = entity['submission_path'] - participant_id = {k: entity[k] - for k in ['team_id', 'baseline_id'] - if k in entity} - submission_descr = SubmissionDescriptor(path=submission_path, - participant_id=participant_id) - if list(entity.key.flat_path[0:2]) == ATTACKS_ENTITY_KEY: - self._attacks[submission_id] = submission_descr - elif list(entity.key.flat_path[0:2]) == TARGET_ATTACKS_ENTITY_KEY: - self._targeted_attacks[submission_id] = submission_descr - elif list(entity.key.flat_path[0:2]) == DEFENSES_ENTITY_KEY: - self._defenses[submission_id] = submission_descr - - @property - def attacks(self): - """Dictionary with all non-targeted attacks.""" - return self._attacks - - @property - def targeted_attacks(self): - """Dictionary with all targeted attacks.""" - return self._targeted_attacks - - @property - def defenses(self): - """Dictionary with all defenses.""" - return self._defenses - - def get_all_attack_ids(self): - """Returns IDs of all attacks (targeted and non-targeted).""" - return list(self.attacks.keys()) + list(self.targeted_attacks.keys()) - - def find_by_id(self, submission_id): - """Finds submission by ID. - - Args: - submission_id: ID of the submission - - Returns: - SubmissionDescriptor with information about submission or None if - submission is not found. - """ - return self._attacks.get( - submission_id, - self._defenses.get( - submission_id, - self._targeted_attacks.get(submission_id, None))) - - def get_external_id(self, submission_id): - """Returns human readable submission external ID. - - Args: - submission_id: internal submission ID. - - Returns: - human readable ID. - """ - submission = self.find_by_id(submission_id) - if not submission: - return None - if 'team_id' in submission.participant_id: - return submission.participant_id['team_id'] - elif 'baseline_id' in submission.participant_id: - return 'baseline_' + submission.participant_id['baseline_id'] - else: - return '' - - def __str__(self): - """Returns human readable representation, useful for debugging purposes.""" - buf = StringIO() - title_values = zip([u'Attacks', u'Targeted Attacks', u'Defenses'], - [self._attacks, self._targeted_attacks, self._defenses]) - for idx, (title, values) in enumerate(title_values): - if idx >= TO_STR_MAX_SUBMISSIONS: - buf.write('...\n') - break - buf.write(title) - buf.write(u':\n') - for k, v in iteritems(values): - buf.write(u'{0} -- {1} {2}\n'.format(k, v.path, - str(v.participant_id))) - buf.write(u'\n') - return buf.getvalue() diff --git a/examples/nips17_adversarial_competition/eval_infra/code/eval_lib/tests/classification_results_test.py b/examples/nips17_adversarial_competition/eval_infra/code/eval_lib/tests/classification_results_test.py deleted file mode 100644 index da205de16..000000000 --- a/examples/nips17_adversarial_competition/eval_infra/code/eval_lib/tests/classification_results_test.py +++ /dev/null @@ -1,242 +0,0 @@ -"""Tests for eval_lib.classification_results.""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import unittest - -from six import assertCountEqual - -from eval_lib import classification_results -from eval_lib import image_batches -from eval_lib import submissions -from eval_lib import work_data -from eval_lib.tests import fake_cloud_client - - -ROUND_NAME = 'round-name' - - -class FakeDatasetMeta(object): - """Fake for DatasetMetadata which alwasy returns constants.""" - - def get_true_label(self, _): - return 1 - - def get_target_class(self, _): - return 2 - - -class ClassificationResultsTest(unittest.TestCase): - - def setUp(self): - self.storage_client = fake_cloud_client.FakeStorageClient() - self.datastore_client = fake_cloud_client.FakeDatastoreClient() - self.submissions = submissions.CompetitionSubmissions( - datastore_client=self.datastore_client, - storage_client=self.storage_client, - round_name=ROUND_NAME) - # we only need list of submissin ids in CompetitionSubmissions for this test - self.submissions._defenses = { - 'SUBD000': {}, - 'SUBD001': {}, - } - self.adv_batches = image_batches.AversarialBatches( - datastore_client=self.datastore_client) - self.adv_batches._data = { - 'ADVBATCH000': {'dataset_batch_id': 'BATCH000', - 'images': {}, - 'submission_id': 'SUBA000'}, - 'ADVBATCH001': {'dataset_batch_id': 'BATCH000', - 'images': {}, - 'submission_id': 'SUBA001'}, - 'ADVBATCH002': {'dataset_batch_id': 'BATCH000', - 'images': {}, - 'submission_id': 'SUBT000'}, - } - - def verify_classification_batches(self, class_batches): - assertCountEqual( - self, - [ - {'adversarial_batch_id': 'ADVBATCH000', 'submission_id': 'SUBD000', - 'result_path': - (ROUND_NAME + '/classification_batches/SUBD000_ADVBATCH000.csv')}, - {'adversarial_batch_id': 'ADVBATCH000', 'submission_id': 'SUBD001', - 'result_path': - (ROUND_NAME + '/classification_batches/SUBD001_ADVBATCH000.csv')}, - {'adversarial_batch_id': 'ADVBATCH001', 'submission_id': 'SUBD000', - 'result_path': - (ROUND_NAME + '/classification_batches/SUBD000_ADVBATCH001.csv')}, - {'adversarial_batch_id': 'ADVBATCH001', 'submission_id': 'SUBD001', - 'result_path': - (ROUND_NAME + '/classification_batches/SUBD001_ADVBATCH001.csv')}, - {'adversarial_batch_id': 'ADVBATCH002', 'submission_id': 'SUBD000', - 'result_path': - (ROUND_NAME + '/classification_batches/SUBD000_ADVBATCH002.csv')}, - {'adversarial_batch_id': 'ADVBATCH002', 'submission_id': 'SUBD001', - 'result_path': - (ROUND_NAME + '/classification_batches/SUBD001_ADVBATCH002.csv')}, - ], class_batches.data.values()) - - def test_init_from_adv_batches_and_submissions(self): - class_batches = classification_results.ClassificationBatches( - self.datastore_client, self.storage_client, ROUND_NAME) - class_batches.init_from_adversarial_batches_write_to_datastore( - self.submissions, self.adv_batches) - self.verify_classification_batches(class_batches) - class_batches = classification_results.ClassificationBatches( - self.datastore_client, self.storage_client, ROUND_NAME) - class_batches.init_from_datastore() - self.verify_classification_batches(class_batches) - - def test_read_batch_from_datastore(self): - class_batches = classification_results.ClassificationBatches( - self.datastore_client, self.storage_client, ROUND_NAME) - class_batches.init_from_adversarial_batches_write_to_datastore( - self.submissions, self.adv_batches) - class_batches = classification_results.ClassificationBatches( - self.datastore_client, self.storage_client, ROUND_NAME) - # read first batch from datastore and verify that only one batch was read - batch = class_batches.read_batch_from_datastore('CBATCH000000') - self.assertEqual(0, len(class_batches.data)) - assertCountEqual(self, ['result_path', 'adversarial_batch_id', - 'submission_id'], batch.keys()) - - def test_compute_classification_results_from_defense_work(self): - # Test computation of the results for the following case: - # - one dataset batch BATCH000 with 5 images - # - two defenses: SUBD000, SUBD001 - # - three attacks with corresponding adversarial batches: - # SUBA000 - ADVBATCH000 - # SUBA001 - ADVBATCH001 - # SUBT000 - ADVBATCH002 - # - # Results are following (correct/incorrect/hit tc/total adv img): - # | SUBD000 | SUBD001 | - # ----------+------------------+------------------+ - # SUBA000 | defense error | 3 / 1 / 0 / 4 | - # | WORK000 | WORK001 | - # ----------+------------------+------------------+ - # SUBA001 | 2 / 2 / 1 / 5 | 4 / 1 / 0 / 5 | - # | WORK002 | WORK003 | - # ----------+------------------+------------------+ - # SUBT000 | 1 / 4 / 4 / 5 | 3 / 2 / 1 / 5 | - # | WORK004 | WORK005 | - - class_batches = classification_results.ClassificationBatches( - self.datastore_client, self.storage_client, ROUND_NAME) - result_path_prefix = ROUND_NAME + '/classification_batches/' - class_batches._data = { - 'CBATCH000000': { - 'adversarial_batch_id': 'ADVBATCH000', 'submission_id': 'SUBD000', - 'result_path': result_path_prefix + 'SUBD000_ADVBATCH000.csv'}, - 'CBATCH000001': { - 'adversarial_batch_id': 'ADVBATCH000', 'submission_id': 'SUBD001', - 'result_path': result_path_prefix + 'SUBD001_ADVBATCH000.csv'}, - 'CBATCH000002': { - 'adversarial_batch_id': 'ADVBATCH001', 'submission_id': 'SUBD000', - 'result_path': result_path_prefix + 'SUBD000_ADVBATCH001.csv'}, - 'CBATCH000003': { - 'adversarial_batch_id': 'ADVBATCH001', 'submission_id': 'SUBD001', - 'result_path': result_path_prefix + 'SUBD001_ADVBATCH001.csv'}, - 'CBATCH000004': { - 'adversarial_batch_id': 'ADVBATCH002', 'submission_id': 'SUBD000', - 'result_path': result_path_prefix + 'SUBD000_ADVBATCH002.csv'}, - 'CBATCH000005': { - 'adversarial_batch_id': 'ADVBATCH002', 'submission_id': 'SUBD001', - 'result_path': result_path_prefix + 'SUBD001_ADVBATCH002.csv'}, - } - defense_work = work_data.DefenseWorkPieces(self.datastore_client) - defense_work._work = { - 'WORK000': {'output_classification_batch_id': 'CBATCH000000', - 'error': 'error'}, - 'WORK001': {'output_classification_batch_id': 'CBATCH000001', - 'stat_correct': 3, 'stat_error': 1, 'stat_target_class': 0, - 'stat_num_images': 4, 'error': None}, - 'WORK002': {'output_classification_batch_id': 'CBATCH000002', - 'stat_correct': 2, 'stat_error': 2, 'stat_target_class': 1, - 'stat_num_images': 5, 'error': None}, - 'WORK003': {'output_classification_batch_id': 'CBATCH000003', - 'stat_correct': 4, 'stat_error': 1, 'stat_target_class': 0, - 'stat_num_images': 5, 'error': None}, - 'WORK004': {'output_classification_batch_id': 'CBATCH000004', - 'stat_correct': 1, 'stat_error': 4, 'stat_target_class': 4, - 'stat_num_images': 5, 'error': None}, - 'WORK005': {'output_classification_batch_id': 'CBATCH000005', - 'stat_correct': 3, 'stat_error': 2, 'stat_target_class': 1, - 'stat_num_images': 5, 'error': None}, - } - # Compute and verify results - (accuracy_matrix, error_matrix, hit_target_class_matrix, - processed_images_count) = class_batches.compute_classification_results( - self.adv_batches, dataset_batches=None, dataset_meta=None, - defense_work=defense_work) - self.assertDictEqual( - { - ('SUBD001', 'SUBA000'): 3, - ('SUBD000', 'SUBA001'): 2, - ('SUBD001', 'SUBA001'): 4, - ('SUBD000', 'SUBT000'): 1, - ('SUBD001', 'SUBT000'): 3, - }, - accuracy_matrix._items) - self.assertDictEqual( - { - ('SUBD001', 'SUBA000'): 1, - ('SUBD000', 'SUBA001'): 2, - ('SUBD001', 'SUBA001'): 1, - ('SUBD000', 'SUBT000'): 4, - ('SUBD001', 'SUBT000'): 2, - }, - error_matrix._items) - self.assertDictEqual( - { - ('SUBD001', 'SUBA000'): 0, - ('SUBD000', 'SUBA001'): 1, - ('SUBD001', 'SUBA001'): 0, - ('SUBD000', 'SUBT000'): 4, - ('SUBD001', 'SUBT000'): 1, - }, - hit_target_class_matrix._items) - self.assertDictEqual({'SUBD000': 10, 'SUBD001': 14}, - processed_images_count) - - def test_read_classification_results(self): - self.storage_client = fake_cloud_client.FakeStorageClient( - {'filename': 'img1.png,123\nimg2.jpg,456'}) - results = classification_results.read_classification_results( - self.storage_client, 'filename') - self.assertDictEqual({'img1': 123, 'img2': 456}, results) - - def test_analyze_one_classification_result(self): - self.storage_client = fake_cloud_client.FakeStorageClient( - {'filename': - 'a1.png,1\na2.png,4\na3.png,1\na4.png,1\na5.png,2\na6.png,9'}) - adv_batch = { - 'dataset_batch_id': 'BATCH000', - 'images': {'a' + str(i): - {'clean_image_id': 'c' + str(i)} for i in range(1, 6)} - } - dataset_batches = image_batches.DatasetBatches( - datastore_client=self.datastore_client, - storage_client=self.storage_client, - dataset_name='final') - dataset_batches._data = { - 'BATCH000': {'images': {'c' + str(i): {'dataset_image_id': str(i)} - for i in range(1, 6)}}, - } - (count_correctly_classified, count_errors, - count_hit_target_class, num_images) = ( - classification_results.analyze_one_classification_result( - self.storage_client, 'filename', adv_batch, dataset_batches, - FakeDatasetMeta())) - self.assertEqual(3, count_correctly_classified) - self.assertEqual(2, count_errors) - self.assertEqual(1, count_hit_target_class) - self.assertEqual(5, num_images) - - -if __name__ == '__main__': - unittest.main() diff --git a/examples/nips17_adversarial_competition/eval_infra/code/eval_lib/tests/fake_cloud_client.py b/examples/nips17_adversarial_competition/eval_infra/code/eval_lib/tests/fake_cloud_client.py deleted file mode 100644 index a2bfd3e1e..000000000 --- a/examples/nips17_adversarial_competition/eval_infra/code/eval_lib/tests/fake_cloud_client.py +++ /dev/null @@ -1,390 +0,0 @@ -"""Library with fake Google Cloud client, used for testing of eval_lib. -""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import copy -from io import StringIO -import six - - -class FakeBlob(object): - """Fake for google.cloud.storage.blob.Blob to be used in tests.""" - - def __init__(self, content): - """Initializes FakeBlob with given content.""" - if six.PY3 and isinstance(content, str): - self._content = content.encode() - else: - self._content = content - self.size = len(content) - - def download_to_file(self, fobj): - """Writes content of this blob into given file object.""" - fobj.write(self._content) - - -class FakeStorageClient(object): - """Fake for CompetitionStorageClient to be used in tests.""" - - def __init__(self, blobs=None): - """Inits FakeStorageClient with given blobs. - - Args: - blobs: either list of blob names or dict with mapping from blob names to - their content - - Raises: - TypeError: if blobs argument has invalid type - """ - if blobs is not None: - if isinstance(blobs, dict): - self._blobs = copy.deepcopy(blobs) - elif isinstance(blobs, list): - self._blobs = {k: '' for k in blobs} - else: - raise TypeError('Invalid type of blobs argument') - else: - self._blobs = {} - - def list_blobs(self, prefix=''): - """Lists names of all blobs by their prefix.""" - return [b for b in self._blobs.keys() if b.startswith(prefix)] - - def get_blob(self, blob_name): - """Gets google.cloud.storage.blob.Blob object by blob name.""" - if blob_name in self._blobs: - return FakeBlob(self._blobs[blob_name]) - else: - return None - - def new_blob(self, blob_name): - """Creates new storage blob with provided name.""" - del blob_name - raise NotImplementedError('new_blob is not implemented in fake client.') - - -class FakeDatastoreKey(object): - """Fake datastore key. - - Fake datastore key is represented as a list with flat path. - """ - - def __init__(self, *args, **kwargs): - if 'parent' not in kwargs: - self._flat_path = args - else: - parent = kwargs['parent'] - if not isinstance(parent, FakeDatastoreKey): - raise ValueError('Invalid type of parent: ' + str(type(parent))) - self._flat_path = parent.flat_path + args - - @property - def flat_path(self): - return self._flat_path - - def __hash__(self): - return hash(self._flat_path) - - def __eq__(self, other): - return (isinstance(other, FakeDatastoreKey) - and (self.flat_path == other.flat_path)) - - def __ne__(self, other): - return not self.__eq__(other) - - def __str__(self): - return self.__repr__() - - def __repr__(self): - return ''.format(self._flat_path) - - -class FakeDatastoreEntity(dict): - """Fake Datstore Entity. - - Fake datastore entity is just a dict, which additionally has key property. - """ - - def __init__(self, key): - super(FakeDatastoreEntity, self).__init__() - if not isinstance(key, FakeDatastoreKey): - raise TypeError('Wrong type of key: ' + str(type(key))) - self._key = key - - @property - def key(self): - return self._key - - def __eq__(self, other): - if not isinstance(other, FakeDatastoreEntity): - return False - return other.key == self.key and (set(self.items()) == set(other.items())) - - def __ne__(self, other): - return not self.__eq__(other) - - def __str__(self): - return self.__repr__() - - def __repr__(self): - return ''.format( - self.key, super(FakeDatastoreEntity, self).__repr__()) - - -def make_entity(key): - """Helper method to make FakeDatastoreEntity. - - This method allows to path either tuple or FakeDatastoreKey as a key. - - Args: - key: entity key, either tuple or FakeDatastoreKey - - Returns: - Instance of FakeDatastoreEntity - """ - if isinstance(key, tuple): - key = FakeDatastoreKey(*key) - return FakeDatastoreEntity(key) - - -class FakeDatastoreClientBatch(object): - """Fake for NoTransactionBatch.""" - - def __init__(self, fake_datastore_client): - """Init FakeDatastoreClientBatch.""" - self._fake_datastore_client = fake_datastore_client - self._mutations = [] - - def __enter__(self): - return self - - def __exit__(self, exc_type, exc_value, traceback): - if exc_type is None: - for m in self._mutations: - self._fake_datastore_client.put(m) - - def put(self, entity): - """Adds entity mutation to the batch.""" - assert isinstance(entity, FakeDatastoreEntity) - self._mutations.append(copy.deepcopy(entity)) - - -class FakeDatastoreClientTransaction(object): - """Fake for datastore transaction. - - See https://cloud.google.com/datastore/docs/concepts/transactions - for details of how transactions work in Cloud Datastore. - """ - - def __init__(self, fake_datastore_client): - """Init FakeDatastoreClientTransaction.""" - self._client = fake_datastore_client - # snapshot of the data in the fake datastore - self._data_snapshot = copy.deepcopy(fake_datastore_client.entities) - # transaction stated: 'init', 'started', 'committed', 'rolledback' - self._state = 'init' - # list of mutations in this transactions in sequential order - # each mutation is instance of FakeDatastoreEntity - self._mutations = [] - # set of keys read in this transaction - self._read_keys = set() - - def _check_transaction_started(self): - """Helper method to check that transaction has been started.""" - if self._state != 'started': - raise ValueError(("Invalid state of transaction, " - "expected started, was %s") % self._state) - - def _check_update_state(self, old_state, new_state): - """Checks old state and updates it to new state.""" - if self._state != old_state: - raise ValueError('Invalid state of transaction, expected %s, was %s' % - (old_state, self._state)) - self._state = new_state - - def begin(self): - """Begins transaction.""" - self._check_update_state('init', 'started') - - def commit(self): - """Commits transaction.""" - self._check_transaction_started() - # before committing transaction verity that all entities which - # were read or updated in the transaction were not modified outside - # of transaction - touched_keys = self._read_keys | set([e.key for e in self._mutations]) - for k in touched_keys: - old_value = self._data_snapshot.get(k) - cur_value = self._client.entities.get(k) - if old_value != cur_value: - self.rollback() - raise Exception('Transaction can not be committed due to ' - 'conflicted updates in datastore.') - # commit all changes - self._state = 'committed' - for m in self._mutations: - self._client.put(m) - - def rollback(self): - """Rolls back current transaction.""" - self._check_update_state('started', 'rolledback') - self._mutations = [] - - def put(self, entity): - """Puts entity to datastore.""" - assert isinstance(entity, FakeDatastoreEntity) - self._check_transaction_started() - self._mutations.append(copy.deepcopy(entity)) - - def get(self, key): - """Gets entity from the datastore.""" - assert isinstance(key, FakeDatastoreKey) - self._check_transaction_started() - self._read_keys.add(key) - return copy.deepcopy(self._data_snapshot.get(key)) - - def __enter__(self): - self.begin() - return self - - def __exit__(self, exc_type, exc_value, traceback): - if exc_type is None: - self.commit() - else: - self.rollback() - - -_QUERY_FILTER_OPERATOR = { - '<': lambda x, y: x < y, - '<=': lambda x, y: x <= y, - '=': lambda x, y: x == y, - '>': lambda x, y: x > y, - '>=': lambda x, y: x >= y, -} - - -class FakeDatastoreClient(object): - """Fake for CompetitionDatastoreClient.""" - - def __init__(self, entities=None): - """Init FakeDatastoreClient with specified entities.""" - self._transaction_hook = None - if isinstance(entities, list): - self._entities = {e.key: e for e in entities} - elif isinstance(entities, dict): - self._entities = entities - elif entities is None: - self._entities = {} - else: - raise ValueError('Invalid type of entities: ' + str(type(entities))) - assert all([isinstance(k, FakeDatastoreKey) - for k in self._entities.keys()]) - - @property - def entities(self): - """List of stored entities.""" - return self._entities - - def key(self, *args, **kwargs): - """Creates datastore key.""" - return FakeDatastoreKey(*args, **kwargs) - - def entity(self, key): - """Creates datastore entity.""" - assert isinstance(key, FakeDatastoreKey) - return FakeDatastoreEntity(key) - - def no_transact_batch(self): - """Starts batch of mutation which is committed without transaction.""" - return FakeDatastoreClientBatch(self) - - def transaction(self): - """Starts datastore transaction.""" - result = FakeDatastoreClientTransaction(self) - if self._transaction_hook: - self._transaction_hook(self) - self._transaction_hook = None - return result - - def get(self, key, transaction=None): - """Gets an entity with given key.""" - assert isinstance(key, FakeDatastoreKey) - if transaction: - return transaction.get(key) - return copy.deepcopy(self._entities.get(key)) - - def put(self, entity): - """Updates entity in the datastore.""" - assert isinstance(entity, FakeDatastoreEntity) - entity = copy.deepcopy(entity) - if entity.key in self.entities: - self.entities[entity.key].update(entity) - else: - self.entities[entity.key] = entity - - def batch(self): - """Starts batch of mutations.""" - raise NotImplementedError('FakeDatastoreClient.batch not implemented') - - def query_fetch(self, **kwargs): - """Queries datastore.""" - kind = kwargs.get('kind', None) - ancestor = kwargs.get('ancestor', None) - filters = kwargs.get('filters', []) - if ancestor and not isinstance(ancestor, FakeDatastoreKey): - raise ValueError('Invalid ancestor type: ' + str(type(ancestor))) - if (('projection' in kwargs) or ('order' in kwargs) or - ('distinct_on' in kwargs)): - raise ValueError('Unsupported clause in arguments: ' + str(kwargs)) - for f in filters: - if not isinstance(f, tuple) or len(f) != 3: - raise ValueError('Invalid filter: ' + str(filters)) - if f[1] not in _QUERY_FILTER_OPERATOR.keys(): - raise ValueError('Unsupported operator in filters: ' + str(filters)) - for e in self._entities.values(): - key_tuple = e.key.flat_path - if (kind is not None) and (key_tuple[-2] != kind): - continue - if (ancestor is not None) and (key_tuple[:-2] != ancestor.flat_path): - continue - all_filters_true = True - for f in filters: - if f[0] not in e: - all_filters_true = False - break - if not _QUERY_FILTER_OPERATOR[f[1]](e[f[0]], f[2]): - all_filters_true = False - break - if not all_filters_true: - continue - yield e - - def set_transaction_hook(self, hook): - """Sets transaction hook. - - This hook will be executed right after next transaction created. - It helps to model a situation when data are modified outside of transaction. - To be used in tests to test how your code handles edits concurrent with - transaction. - - Args: - hook: transaction hook, should be a function which takes exactly - one argument - instance of this class. - - Raises: - ValueError: if transaction hook was already set - """ - if self._transaction_hook is not None: - raise ValueError('Attempt to set transaction hook twice') - self._transaction_hook = hook - - def __str__(self): - """Returns string representation of all stored entities.""" - buf = StringIO() - for entity in self.entities.values(): - buf.write(u'Entity {0}:\n'.format(entity.key.flat_path)) - buf.write(u' {0}\n'.format(dict(entity))) - return buf.getvalue() diff --git a/examples/nips17_adversarial_competition/eval_infra/code/eval_lib/tests/fake_cloud_client_test.py b/examples/nips17_adversarial_competition/eval_infra/code/eval_lib/tests/fake_cloud_client_test.py deleted file mode 100644 index 209181b95..000000000 --- a/examples/nips17_adversarial_competition/eval_infra/code/eval_lib/tests/fake_cloud_client_test.py +++ /dev/null @@ -1,452 +0,0 @@ -"""Tests for eval_lib.testing.fake_cloud_client.""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import copy -from io import BytesIO -import unittest -from eval_lib.tests import fake_cloud_client -from six import assertCountEqual -from six import b as six_b - - -class FakeStorageClientTest(unittest.TestCase): - - def test_list_blobs(self): - all_blobs = [ - 'some_blob', - 'dataset/dev_dataset.csv', - 'dataset/dev/img1.png', - 'dataset/dev/img2.png' - ] - client = fake_cloud_client.FakeStorageClient(all_blobs) - assertCountEqual(self, all_blobs, client.list_blobs()) - assertCountEqual(self, [ - 'dataset/dev_dataset.csv', - 'dataset/dev/img1.png', - 'dataset/dev/img2.png' - ], client.list_blobs('dataset/dev')) - assertCountEqual(self, [ - 'dataset/dev/img1.png', - 'dataset/dev/img2.png' - ], client.list_blobs('dataset/dev/')) - - def test_get_blob(self): - client = fake_cloud_client.FakeStorageClient({'some_blob': 'some_content', - 'blob2': 'another_content'}) - self.assertIsNone(client.get_blob('blob3')) - buf = BytesIO() - client.get_blob('some_blob').download_to_file(buf) - self.assertEqual(six_b('some_content'), buf.getvalue()) - - -class FakeDatastoreKeyTest(unittest.TestCase): - - def test_flat_path(self): - key1 = fake_cloud_client.FakeDatastoreKey('abc', '1') - self.assertTupleEqual(('abc', '1'), key1.flat_path) - key2 = fake_cloud_client.FakeDatastoreKey('def', 'xyz', parent=key1) - self.assertTupleEqual(('abc', '1', 'def', 'xyz'), key2.flat_path) - - def test_equality(self): - key1a = fake_cloud_client.FakeDatastoreKey('abc', '1') - key1b = fake_cloud_client.FakeDatastoreKey('abc', '1') - key2a = fake_cloud_client.FakeDatastoreKey('def', 'xyz', parent=key1a) - key2b = fake_cloud_client.FakeDatastoreKey('def', 'xyz', parent=key1a) - # key equal to self - self.assertTrue(key1a == key1a) - self.assertFalse(key1a != key1a) - # key equal to the same key - self.assertTrue(key1a == key1b) - self.assertFalse(key1a != key1b) - self.assertTrue(key2a == key2b) - self.assertFalse(key2a != key2b) - # key different from other key - self.assertFalse(key1a == key2a) - self.assertTrue(key1a != key2a) - # key not equal to tuple - self.assertTrue(key1a != key1a.flat_path) - self.assertFalse(key1a == key1a.flat_path) - - -class FakeDatastoreEntityTest(unittest.TestCase): - - def test_key(self): - entity = fake_cloud_client.make_entity(('abc', '1')) - self.assertEqual(entity.key, - fake_cloud_client.FakeDatastoreKey('abc', '1')) - - def test_equality_keys(self): - entity1a = fake_cloud_client.make_entity(('abc', '1')) - entity1b = fake_cloud_client.make_entity(('abc', '1')) - entity2 = fake_cloud_client.make_entity(('abc', '2')) - self.assertFalse(entity1a == entity2) - self.assertTrue(entity1a != entity2) - self.assertTrue(entity1a == entity1b) - self.assertFalse(entity1b != entity1b) - - def test_equality_dict(self): - entity1 = fake_cloud_client.make_entity(('abc', '1')) - entity1['k1'] = 'v1' - entity2 = fake_cloud_client.make_entity(('abc', '1')) - entity2['k1'] = 'v2' - entity3 = fake_cloud_client.make_entity(('abc', '1')) - entity1['k1'] = 'v1' - entity1['k2'] = 'v2' - # compare to self - self.assertTrue(entity1 == entity1) - self.assertFalse(entity1 != entity1) - self.assertTrue(entity2 == entity2) - self.assertFalse(entity2 != entity2) - self.assertTrue(entity3 == entity3) - self.assertFalse(entity3 != entity3) - # compare to others - self.assertFalse(entity1 == entity2) - self.assertTrue(entity1 != entity2) - self.assertFalse(entity1 == entity3) - self.assertTrue(entity1 != entity3) - self.assertFalse(entity2 == entity3) - self.assertTrue(entity2 != entity3) - - def test_copy(self): - entity1 = fake_cloud_client.make_entity(('abc', '1')) - entity1['k1'] = ['v1'] - self.assertEqual(entity1.key, - fake_cloud_client.FakeDatastoreKey('abc', '1')) - self.assertEqual(dict(entity1), - {'k1': ['v1']}) - entity2 = copy.copy(entity1) - entity2['k1'].append('v2') - entity2['k3'] = 'v3' - self.assertIsInstance(entity2, fake_cloud_client.FakeDatastoreEntity) - self.assertEqual(entity1.key, - fake_cloud_client.FakeDatastoreKey('abc', '1')) - self.assertEqual(dict(entity1), - {'k1': ['v1', 'v2']}) - self.assertEqual(entity2.key, - fake_cloud_client.FakeDatastoreKey('abc', '1')) - self.assertEqual(dict(entity2), - {'k1': ['v1', 'v2'], 'k3': 'v3'}) - - def test_deep_copy(self): - entity1 = fake_cloud_client.make_entity(('abc', '1')) - entity1['k1'] = ['v1'] - self.assertEqual(entity1.key, - fake_cloud_client.FakeDatastoreKey('abc', '1')) - self.assertEqual(dict(entity1), - {'k1': ['v1']}) - entity2 = copy.deepcopy(entity1) - entity2['k1'].append('v2') - entity2['k3'] = 'v3' - self.assertIsInstance(entity2, fake_cloud_client.FakeDatastoreEntity) - self.assertEqual(entity1.key, - fake_cloud_client.FakeDatastoreKey('abc', '1')) - self.assertEqual(dict(entity1), - {'k1': ['v1']}) - self.assertEqual(entity2.key, - fake_cloud_client.FakeDatastoreKey('abc', '1')) - self.assertEqual(dict(entity2), - {'k1': ['v1', 'v2'], 'k3': 'v3'}) - - -class FakeDatastoreClientTest(unittest.TestCase): - - def setUp(self): - self._client = fake_cloud_client.FakeDatastoreClient() - self._key1 = self._client.key('abc', 'def') - self._key2 = self._client.key('qwe', 'rty', parent=self._key1) - self._entity1 = self._client.entity(self._key1) - self._entity1['k1'] = 'v1' - self._entity2 = self._client.entity(self._key2) - self._entity2['k2'] = 'v2' - self._entity2['k3'] = 'v3' - - def test_make_key(self): - self.assertTupleEqual(('abc', 'def'), self._key1.flat_path) - self.assertTupleEqual(('abc', 'def', 'qwe', 'rty'), self._key2.flat_path) - - def test_make_entity(self): - self.assertTupleEqual(('abc', 'def'), self._entity1.key.flat_path) - - def test_put_entity(self): - self.assertDictEqual({}, self._client.entities) - self._client.put(self._entity1) - self.assertDictEqual({self._key1: self._entity1}, self._client.entities) - self._client.put(self._entity2) - self.assertDictEqual({self._key1: self._entity1, self._key2: self._entity2}, - self._client.entities) - - def test_get_entity(self): - self._client.put(self._entity1) - self._client.put(self._entity2) - self.assertEqual(self._entity1, self._client.get(self._key1)) - self.assertEqual(self._entity2, self._client.get(self._key2)) - - def test_write_batch(self): - with self._client.no_transact_batch() as batch: - batch.put(self._entity1) - batch.put(self._entity2) - assertCountEqual(self, [self._key1, self._key2], - self._client.entities.keys()) - self.assertEqual(self._key1, self._client.entities[self._key1].key) - self.assertDictEqual({'k1': 'v1'}, dict(self._client.entities[self._key1])) - self.assertEqual(self._key2, self._client.entities[self._key2].key) - self.assertDictEqual({'k2': 'v2', 'k3': 'v3'}, - dict(self._client.entities[self._key2])) - - def test_overwrite_values(self): - client = fake_cloud_client.FakeDatastoreClient() - key1 = client.key('abc', 'def') - entity1 = client.entity(key1) - entity1['k1'] = 'v1' - entity2 = client.entity(key1) - entity2['k1'] = 'v2' - entity2['k2'] = 'v3' - with client.no_transact_batch() as batch: - batch.put(entity1) - assertCountEqual(self, [key1], client.entities.keys()) - self.assertEqual(key1, client.entities[key1].key) - self.assertDictEqual({'k1': 'v1'}, dict(client.entities[key1])) - with client.no_transact_batch() as batch: - batch.put(entity2) - assertCountEqual(self, [key1], client.entities.keys()) - self.assertEqual(key1, client.entities[key1].key) - self.assertDictEqual({'k1': 'v2', 'k2': 'v3'}, dict(client.entities[key1])) - - def test_query_fetch_all(self): - entity1 = fake_cloud_client.make_entity(('abc', '1')) - entity1['k1'] = 'v1' - entity2 = fake_cloud_client.make_entity(('abc', '1', 'def', '2')) - entity2['k2'] = 'v2' - client = fake_cloud_client.FakeDatastoreClient([entity1, entity2]) - assertCountEqual(self, [entity1, entity2], client.query_fetch()) - - def test_query_fetch_kind_filter(self): - entity1 = fake_cloud_client.make_entity(('abc', '1')) - entity1['k1'] = 'v1' - entity2 = fake_cloud_client.make_entity(('abc', '1', 'def', '2')) - entity2['k2'] = 'v2' - client = fake_cloud_client.FakeDatastoreClient([entity1, entity2]) - assertCountEqual(self, [entity1], client.query_fetch(kind='abc')) - assertCountEqual(self, [entity2], client.query_fetch(kind='def')) - - def test_query_fetch_ancestor_filter(self): - entity1 = fake_cloud_client.make_entity(('abc', '1', 'def', '2')) - entity1['k1'] = 'v1' - entity2 = fake_cloud_client.make_entity(('xyz', '3', 'qwe', '4')) - entity2['k2'] = 'v2' - client = fake_cloud_client.FakeDatastoreClient([entity1, entity2]) - assertCountEqual(self, [entity1], - client.query_fetch(ancestor=client.key('abc', '1'))) - assertCountEqual(self, [entity2], - client.query_fetch(ancestor=client.key('xyz', '3'))) - - def test_query_fetch_ancestor_and_kind_filter(self): - entity1 = fake_cloud_client.make_entity(('abc', '1', 'def', '2')) - entity1['k1'] = 'v1' - entity2 = fake_cloud_client.make_entity(('abc', '1', 'xyz', '3')) - entity2['k2'] = 'v2' - entity3 = fake_cloud_client.make_entity(('def', '4')) - entity3['k2'] = 'v2' - client = fake_cloud_client.FakeDatastoreClient([entity1, entity2, entity3]) - assertCountEqual(self, [entity1], - client.query_fetch(kind='def', - ancestor=client.key('abc', '1'))) - - def test_query_fetch_data_filter(self): - entity1 = fake_cloud_client.make_entity(('abc', '1')) - entity1['k1'] = 'v1' - entity2 = fake_cloud_client.make_entity(('abc', '2')) - entity2['k1'] = 'v2' - entity2['k2'] = 'v2' - entity3 = fake_cloud_client.make_entity(('abc', '3')) - entity3['k2'] = 'v3' - client = fake_cloud_client.FakeDatastoreClient([entity1, entity2, entity3]) - assertCountEqual(self, [entity1], - client.query_fetch(filters=[('k1', '=', 'v1')])) - assertCountEqual(self, [entity2], - client.query_fetch(filters=[('k1', '>', 'v1')])) - assertCountEqual(self, [entity1, entity2], - client.query_fetch(filters=[('k1', '>=', 'v1')])) - assertCountEqual(self, [entity2], - client.query_fetch(filters=[('k2', '<', 'v3')])) - assertCountEqual(self, [entity2, entity3], - client.query_fetch(filters=[('k2', '<=', 'v3')])) - assertCountEqual(self, [entity2], - client.query_fetch(filters=[('k1', '>=', 'v1'), - ('k2', '<=', 'v3')])) - - -class FakeDatastoreClientTransactionTest(unittest.TestCase): - - def setUp(self): - self._client = fake_cloud_client.FakeDatastoreClient() - self._key1 = self._client.key('abc', 'def') - self._key2 = self._client.key('qwe', 'rty', parent=self._key1) - self._key3 = self._client.key('123', '456') - self._entity1 = self._client.entity(self._key1) - self._entity1['k1'] = 'v1' - self._entity2 = self._client.entity(self._key2) - self._entity2['k2'] = 'v2' - self._entity2['k3'] = 'v3' - self._entity3 = self._client.entity(self._key3) - self._entity3['k4'] = 'v4' - self._entity3['k5'] = 'v5' - self._entity3['k6'] = 'v6' - self._client.put(self._entity1) - self._client.put(self._entity2) - self._client.put(self._entity3) - # verify datastore content - assertCountEqual(self, [self._key1, self._key2, self._key3], - self._client.entities.keys()) - self.assertDictEqual({'k1': 'v1'}, dict(self._client.entities[self._key1])) - self.assertDictEqual({'k2': 'v2', 'k3': 'v3'}, - dict(self._client.entities[self._key2])) - self.assertDictEqual({'k4': 'v4', 'k5': 'v5', 'k6': 'v6'}, - dict(self._client.entities[self._key3])) - - def test_transaction_write_only_no_concurrent(self): - key4 = self._client.key('zxc', 'vbn') - entity4 = self._client.entity(key4) - entity4['k7'] = 'v7' - entity3_upd = self._client.entity(self._key3) - entity3_upd['k4'] = 'upd_v4' - with self._client.transaction() as transaction: - # first write in transaction - transaction.put(entity4) - # second write in transaction - transaction.put(entity3_upd) - # verify datastore content - assertCountEqual(self, [self._key1, self._key2, self._key3, key4], - self._client.entities.keys()) - self.assertDictEqual({'k1': 'v1'}, dict(self._client.entities[self._key1])) - self.assertDictEqual({'k2': 'v2', 'k3': 'v3'}, - dict(self._client.entities[self._key2])) - self.assertDictEqual({'k4': 'upd_v4', 'k5': 'v5', 'k6': 'v6'}, - dict(self._client.entities[self._key3])) - self.assertDictEqual({'k7': 'v7'}, dict(self._client.entities[key4])) - - def test_transaction_read_write_no_concurrent(self): - key4 = self._client.key('zxc', 'vbn') - entity4 = self._client.entity(key4) - entity4['k7'] = 'v7' - entity3_upd = self._client.entity(self._key3) - entity3_upd['k4'] = 'upd_v4' - with self._client.transaction() as transaction: - # reading in transaction always returns data snapshot before transaction - read_entity = self._client.get(self._key3, transaction=transaction) - self.assertDictEqual({'k4': 'v4', 'k5': 'v5', 'k6': 'v6'}, - dict(read_entity)) - # first write in transaction - transaction.put(entity3_upd) - # second write in transaction - transaction.put(entity4) - # reading in transaction always returns data snapshot before transaction - read_entity = self._client.get(self._key3, transaction=transaction) - self.assertDictEqual({'k4': 'v4', 'k5': 'v5', 'k6': 'v6'}, - dict(read_entity)) - # verify datastore content - assertCountEqual(self, [self._key1, self._key2, self._key3, key4], - self._client.entities.keys()) - self.assertDictEqual({'k1': 'v1'}, dict(self._client.entities[self._key1])) - self.assertDictEqual({'k2': 'v2', 'k3': 'v3'}, - dict(self._client.entities[self._key2])) - self.assertDictEqual({'k4': 'upd_v4', 'k5': 'v5', 'k6': 'v6'}, - dict(self._client.entities[self._key3])) - self.assertDictEqual({'k7': 'v7'}, dict(self._client.entities[key4])) - - def test_transaction_read_write_concurrent_not_intersecting(self): - key4 = self._client.key('zxc', 'vbn') - entity4 = self._client.entity(key4) - entity4['k7'] = 'v7' - entity3_upd = self._client.entity(self._key3) - entity3_upd['k4'] = 'upd_v4' - entity1_upd = self._client.entity(self._key1) - entity1_upd['k1'] = 'upd_v1' - with self._client.transaction() as transaction: - # reading in transaction always returns data snapshot before transaction - read_entity = self._client.get(self._key3, transaction=transaction) - self.assertDictEqual({'k4': 'v4', 'k5': 'v5', 'k6': 'v6'}, - dict(read_entity)) - # first write in transaction - transaction.put(entity3_upd) - # modify some data which are not references in the transaction - self._client.put(entity1_upd) - # second write in transaction - transaction.put(entity4) - # reading in transaction always returns data snapshot before transaction - read_entity = self._client.get(self._key3, transaction=transaction) - self.assertDictEqual({'k4': 'v4', 'k5': 'v5', 'k6': 'v6'}, - dict(read_entity)) - # verify datastore content - assertCountEqual(self, [self._key1, self._key2, self._key3, key4], - self._client.entities.keys()) - self.assertDictEqual({'k1': 'upd_v1'}, - dict(self._client.entities[self._key1])) - self.assertDictEqual({'k2': 'v2', 'k3': 'v3'}, - dict(self._client.entities[self._key2])) - self.assertDictEqual({'k4': 'upd_v4', 'k5': 'v5', 'k6': 'v6'}, - dict(self._client.entities[self._key3])) - self.assertDictEqual({'k7': 'v7'}, dict(self._client.entities[key4])) - - def test_transaction_write_concurrent(self): - key4 = self._client.key('zxc', 'vbn') - entity4 = self._client.entity(key4) - entity4['k7'] = 'v7' - entity3_upd = self._client.entity(self._key3) - entity3_upd['k4'] = 'upd_v4' - entity3_upd_no_transact = self._client.entity(self._key3) - entity3_upd_no_transact['k4'] = 'another_v4' - reached_end_of_transaction = False - with self.assertRaises(Exception): - with self._client.transaction() as transaction: - # first write in transaction - transaction.put(entity3_upd) - # modify some data which are not references in the transaction - self._client.put(entity3_upd_no_transact) - # second write in transaction - transaction.put(entity4) - reached_end_of_transaction = True - self.assertTrue(reached_end_of_transaction) - # verify datastore content - assertCountEqual(self, [self._key1, self._key2, self._key3], - self._client.entities.keys()) - self.assertDictEqual({'k1': 'v1'}, dict(self._client.entities[self._key1])) - self.assertDictEqual({'k2': 'v2', 'k3': 'v3'}, - dict(self._client.entities[self._key2])) - self.assertDictEqual({'k4': 'another_v4', 'k5': 'v5', 'k6': 'v6'}, - dict(self._client.entities[self._key3])) - - def test_transaction_read_concurrent(self): - key4 = self._client.key('zxc', 'vbn') - entity4 = self._client.entity(key4) - entity4['k7'] = 'v7' - entity3_upd_no_transact = self._client.entity(self._key3) - entity3_upd_no_transact['k4'] = 'another_v4' - reached_end_of_transaction = False - with self.assertRaises(Exception): - with self._client.transaction() as transaction: - # write in transaction - transaction.put(entity4) - # read in transaction - read_entity = self._client.get(self._key3, transaction=transaction) - self.assertDictEqual({'k4': 'v4', 'k5': 'v5', 'k6': 'v6'}, - dict(read_entity)) - # modify some data which are not references in the transaction - self._client.put(entity3_upd_no_transact) - reached_end_of_transaction = True - self.assertTrue(reached_end_of_transaction) - # verify datastore content - assertCountEqual(self, [self._key1, self._key2, self._key3], - self._client.entities.keys()) - self.assertDictEqual({'k1': 'v1'}, dict(self._client.entities[self._key1])) - self.assertDictEqual({'k2': 'v2', 'k3': 'v3'}, - dict(self._client.entities[self._key2])) - self.assertDictEqual({'k4': 'another_v4', 'k5': 'v5', 'k6': 'v6'}, - dict(self._client.entities[self._key3])) - - -if __name__ == '__main__': - unittest.main() diff --git a/examples/nips17_adversarial_competition/eval_infra/code/eval_lib/tests/image_batches_test.py b/examples/nips17_adversarial_competition/eval_infra/code/eval_lib/tests/image_batches_test.py deleted file mode 100644 index 8e031b4a7..000000000 --- a/examples/nips17_adversarial_competition/eval_infra/code/eval_lib/tests/image_batches_test.py +++ /dev/null @@ -1,267 +0,0 @@ -"""Tests for eval_lib.image_batches.""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import itertools -import unittest - -from six import assertCountEqual - -from eval_lib import image_batches -from eval_lib import submissions -from eval_lib.tests import fake_cloud_client - - -ROUND_NAME = 'round-name' - - -class ImageBatchesBaseTest(unittest.TestCase): - - def setUp(self): - self.datastore_client = fake_cloud_client.FakeDatastoreClient() - self.image_batches = image_batches.ImageBatchesBase( - datastore_client=self.datastore_client, - entity_kind_batches='Batch', - entity_kind_images='Image') - - def test_add_batch(self): - self.assertEqual(0, len(self.image_batches.data)) - self.image_batches.add_batch('batch1', - batch_properties={'k1': 'v1', 'k2': 'v2'}) - self.assertEqual(1, len(self.image_batches.data)) - self.assertDictEqual({'k1': 'v1', 'k2': 'v2', 'images': {}}, - self.image_batches['batch1']) - self.image_batches.add_batch('batch2', batch_properties={'k3': 'v3'}) - self.assertEqual(2, len(self.image_batches.data)) - self.assertDictEqual({'k3': 'v3', 'images': {}}, - self.image_batches['batch2']) - - def test_add_image(self): - self.assertEqual(0, len(self.image_batches.data)) - self.image_batches.add_batch('batch1', - batch_properties={'k1': 'v1', 'k2': 'v2'}) - self.image_batches.add_image('batch1', 'img1', - image_properties={'k4': 'v4'}) - self.assertEqual(1, len(self.image_batches.data)) - self.assertDictEqual({'k1': 'v1', 'k2': 'v2', - 'images': {'img1': {'k4': 'v4'}}}, - self.image_batches['batch1']) - self.image_batches.add_image('batch1', 'img2', - image_properties={'k5': 'v5'}) - self.assertEqual(1, len(self.image_batches.data)) - self.assertDictEqual({'k1': 'v1', 'k2': 'v2', - 'images': {'img1': {'k4': 'v4'}, - 'img2': {'k5': 'v5'}}}, - self.image_batches['batch1']) - - def test_write_to_datastore(self): - # add 2 batches and 3 images, write everything to datastore - self.image_batches.add_batch('batch1', - batch_properties={'k1': 'v1', 'k2': 'v2'}) - self.image_batches.add_batch('batch2', batch_properties={'k3': 'v3'}) - self.image_batches.add_image('batch1', 'img1', - image_properties={'k4': 'v4'}) - self.image_batches.add_image('batch1', 'img2', - image_properties={'k5': 'v5'}) - self.image_batches.add_image('batch2', 'img3', - image_properties={'k6': 'v6'}) - self.image_batches.write_to_datastore() - # verify batches - batch_entity1 = self.datastore_client.entity( - fake_cloud_client.FakeDatastoreKey('Batch', 'batch1')) - batch_entity1.update({'k1': 'v1', 'k2': 'v2'}) - batch_entity2 = self.datastore_client.entity( - fake_cloud_client.FakeDatastoreKey('Batch', 'batch2')) - batch_entity2.update({'k3': 'v3'}) - assertCountEqual(self, [batch_entity1, batch_entity2], - self.datastore_client.query_fetch(kind='Batch')) - # verify images - img_entity1 = self.datastore_client.entity( - fake_cloud_client.FakeDatastoreKey('Batch', 'batch2', 'Image', 'img1')) - img_entity1.update({'k4': 'v4'}) - img_entity2 = self.datastore_client.entity( - fake_cloud_client.FakeDatastoreKey('Batch', 'batch2', 'Image', 'img2')) - img_entity2.update({'k5': 'v5'}) - img_entity3 = self.datastore_client.entity( - fake_cloud_client.FakeDatastoreKey('Batch', 'batch2', 'Image', 'img3')) - img_entity3.update({'k6': 'v6'}) - - def test_write_single_batch_images_to_datastore(self): - # add 2 batches and 3 images, write only one batch to datastore - self.image_batches.add_batch('batch1', - batch_properties={'k1': 'v1', 'k2': 'v2'}) - self.image_batches.add_batch('batch2', batch_properties={'k3': 'v3'}) - self.image_batches.add_image('batch1', 'img1', - image_properties={'k4': 'v4'}) - self.image_batches.add_image('batch1', 'img2', - image_properties={'k5': 'v5'}) - self.image_batches.add_image('batch2', 'img3', - image_properties={'k6': 'v6'}) - self.image_batches.write_single_batch_images_to_datastore('batch2') - # verify batches - # write_single_batch_images_to_datastore writes only images, so no batches - assertCountEqual(self, [], self.datastore_client.query_fetch(kind='Batch')) - # verify images - img_entity3 = self.datastore_client.entity( - fake_cloud_client.FakeDatastoreKey('Batch', 'batch2', 'Image', 'img3')) - img_entity3.update({'k6': 'v6'}) - assertCountEqual(self, [img_entity3], - self.datastore_client.query_fetch(kind='Image')) - - -class DatasetBatchesTest(unittest.TestCase): - - def setUp(self): - storage_blobs = [ - 'dataset/dev/img1.png', - 'dataset/dev/img2.png', - 'dataset/dev/img3.png', - 'dataset/dev/img4.png', - 'dataset/dev/img5.png', - 'dataset/dev_dataset.csv', - ] - self.storage_client = fake_cloud_client.FakeStorageClient(storage_blobs) - self.datastore_client = fake_cloud_client.FakeDatastoreClient() - self.dataset_batches = image_batches.DatasetBatches( - datastore_client=self.datastore_client, - storage_client=self.storage_client, - dataset_name='dev') - - def verify_dataset_batches(self): - self.assertEqual(2, len(self.dataset_batches.data)) - all_images = {} - for batch in self.dataset_batches.data.values(): - self.assertIn(batch['epsilon'], [4, 8, 12, 16]) - self.assertGreaterEqual(3, len(batch['images'])) - self.assertTrue( - set(all_images.keys()).isdisjoint(batch['images'].keys()), - msg=('all_images and batch[\'images\'] contains common keys %s' - % set(all_images.keys()).intersection(batch['images'].keys())) - ) - all_images.update(batch['images']) - assertCountEqual(self, [ - {'dataset_image_id': 'img1', 'image_path': 'dataset/dev/img1.png'}, - {'dataset_image_id': 'img2', 'image_path': 'dataset/dev/img2.png'}, - {'dataset_image_id': 'img3', 'image_path': 'dataset/dev/img3.png'}, - {'dataset_image_id': 'img4', 'image_path': 'dataset/dev/img4.png'}, - {'dataset_image_id': 'img5', 'image_path': 'dataset/dev/img5.png'}, - ], all_images.values()) - - def verify_datastore_entities(self): - # Verify 'DatasetBatch' entities - expected_batch_entities = [] - for batch_id, batch in self.dataset_batches.data.items(): - entity = self.datastore_client.entity( - fake_cloud_client.FakeDatastoreKey('DatasetBatch', batch_id)) - entity['epsilon'] = batch['epsilon'] - expected_batch_entities.append(entity) - assertCountEqual(self, expected_batch_entities, - self.datastore_client.query_fetch(kind='DatasetBatch')) - # Verify 'DatasetImage' entities - expected_image_entities = [] - for batch_id, batch in self.dataset_batches.data.items(): - for image_id, image in batch['images'].items(): - entity = self.datastore_client.entity( - fake_cloud_client.FakeDatastoreKey('DatasetBatch', batch_id, - 'DatasetImage', image_id)) - entity.update(image) - expected_image_entities.append(entity) - assertCountEqual(self, expected_image_entities, - self.datastore_client.query_fetch(kind='DatasetImage')) - - def test_init_from_storage(self): - self.dataset_batches.init_from_storage_write_to_datastore(batch_size=3) - self.verify_dataset_batches() - self.verify_datastore_entities() - - def test_init_from_datastore(self): - self.dataset_batches.init_from_storage_write_to_datastore(batch_size=3) - self.dataset_batches = image_batches.DatasetBatches( - datastore_client=self.datastore_client, - storage_client=self.storage_client, - dataset_name='dev') - self.dataset_batches.init_from_datastore() - self.verify_dataset_batches() - - def test_count_num_images(self): - self.dataset_batches.init_from_storage_write_to_datastore(batch_size=3) - self.assertEqual(5, self.dataset_batches.count_num_images()) - - -class AdversarialBatchesTest(unittest.TestCase): - - def setUp(self): - # prepare dataset batches and submissions - storage_blobs = [ - 'dataset/dev/img1.png', - 'dataset/dev/img2.png', - 'dataset/dev/img3.png', - 'dataset/dev/img4.png', - 'dataset/dev/img5.png', - 'dataset/dev_dataset.csv', - ROUND_NAME + '/submissions/nontargeted/1.zip', - ROUND_NAME + '/submissions/nontargeted/baseline_nt.zip', - ROUND_NAME + '/submissions/targeted/1.zip', - ROUND_NAME + '/submissions/targeted/2.zip', - ROUND_NAME + '/submissions/defense/3.zip', - ROUND_NAME + '/submissions/defense/baseline_adv_train.zip', - ] - self.storage_client = fake_cloud_client.FakeStorageClient(storage_blobs) - self.datastore_client = fake_cloud_client.FakeDatastoreClient() - self.dataset_batches = image_batches.DatasetBatches( - datastore_client=self.datastore_client, - storage_client=self.storage_client, - dataset_name='dev') - self.dataset_batches.init_from_storage_write_to_datastore(batch_size=3) - self.submissions = submissions.CompetitionSubmissions( - datastore_client=self.datastore_client, - storage_client=self.storage_client, - round_name=ROUND_NAME) - self.submissions.init_from_storage_write_to_datastore() - - def verify_adversarial_batches_without_images(self, adv_batches): - attack_ids = (list(self.submissions.attacks.keys()) - + list(self.submissions.targeted_attacks.keys())) - dataset_batch_ids = self.dataset_batches.data.keys() - expected_batches = [ - {'dataset_batch_id': b_id, 'submission_id': a_id, 'images': {}} - for (b_id, a_id) in itertools.product(dataset_batch_ids, attack_ids) - ] - assertCountEqual(self, expected_batches, adv_batches.data.values()) - - def test_init_from_dataset_and_submissions(self): - adv_batches = image_batches.AversarialBatches( - datastore_client=self.datastore_client) - adv_batches.init_from_dataset_and_submissions_write_to_datastore( - dataset_batches=self.dataset_batches, - attack_submission_ids=self.submissions.get_all_attack_ids()) - self.verify_adversarial_batches_without_images(adv_batches) - - def test_init_from_datastore(self): - # populate datastore - adv_batches = image_batches.AversarialBatches( - datastore_client=self.datastore_client) - adv_batches.init_from_dataset_and_submissions_write_to_datastore( - dataset_batches=self.dataset_batches, - attack_submission_ids=self.submissions.get_all_attack_ids()) - # init AversarialBatches from datastore - adv_batches = image_batches.AversarialBatches( - datastore_client=self.datastore_client) - adv_batches.init_from_datastore() - self.verify_adversarial_batches_without_images(adv_batches) - - def test_count_generated_adv_examples(self): - adv_batches = image_batches.AversarialBatches( - datastore_client=self.datastore_client) - adv_batches.init_from_dataset_and_submissions_write_to_datastore( - dataset_batches=self.dataset_batches, - attack_submission_ids=self.submissions.get_all_attack_ids()) - self.assertDictEqual( - {'SUBA000': 0, 'SUBA001': 0, 'SUBT000': 0, 'SUBT001': 0}, - adv_batches.count_generated_adv_examples()) - - -if __name__ == '__main__': - unittest.main() diff --git a/examples/nips17_adversarial_competition/eval_infra/code/eval_lib/tests/submissions_test.py b/examples/nips17_adversarial_competition/eval_infra/code/eval_lib/tests/submissions_test.py deleted file mode 100644 index db4afc71c..000000000 --- a/examples/nips17_adversarial_competition/eval_infra/code/eval_lib/tests/submissions_test.py +++ /dev/null @@ -1,172 +0,0 @@ -"""Tests for eval_lib.submissions.""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import unittest - -from six import assertCountEqual - -from eval_lib import submissions -from eval_lib.tests import fake_cloud_client - - -ROUND_NAME = 'round-name' - - -class ParticipantFromSubmissionPathTest(unittest.TestCase): - - def test_team_id(self): - self.assertDictEqual( - {'team_id': 42}, - submissions.participant_from_submission_path('path/42.zip') - ) - - def test_baseline_id(self): - self.assertDictEqual( - {'baseline_id': 'a_1'}, - submissions.participant_from_submission_path('path/baseline_a_1.zip') - ) - - def test_tar_extension(self): - self.assertDictEqual( - {'team_id': 42}, - submissions.participant_from_submission_path('path/42.tar') - ) - - def test_tar_gz_extension(self): - self.assertDictEqual( - {'team_id': 42}, - submissions.participant_from_submission_path('path/42.tar.gz') - ) - - -class SubmissionsTest(unittest.TestCase): - - def setUp(self): - storage_blobs = [ - ROUND_NAME + '/submissions/nontargeted/1.zip', - ROUND_NAME + '/submissions/nontargeted/baseline_nt.zip', - ROUND_NAME + '/submissions/targeted/1.zip', - ROUND_NAME + '/submissions/targeted/2.zip', - ROUND_NAME + '/submissions/defense/3.zip', - ROUND_NAME + '/submissions/defense/baseline_adv_train.zip', - ] - self.storage_client = fake_cloud_client.FakeStorageClient(storage_blobs) - self.datastore_client = fake_cloud_client.FakeDatastoreClient() - self.submissions = submissions.CompetitionSubmissions( - datastore_client=self.datastore_client, - storage_client=self.storage_client, - round_name=ROUND_NAME) - - def verify_submissions(self): - assertCountEqual(self, [ - submissions.SubmissionDescriptor( - path=(ROUND_NAME + '/submissions/nontargeted/1.zip'), - participant_id={'team_id': 1}), - submissions.SubmissionDescriptor( - path=(ROUND_NAME + '/submissions/nontargeted/baseline_nt.zip'), - participant_id={'baseline_id': 'nt'}) - ], self.submissions.attacks.values()) - assertCountEqual(self, [ - submissions.SubmissionDescriptor( - path=(ROUND_NAME + '/submissions/targeted/1.zip'), - participant_id={'team_id': 1}), - submissions.SubmissionDescriptor( - path=(ROUND_NAME + '/submissions/targeted/2.zip'), - participant_id={'team_id': 2}) - ], self.submissions.targeted_attacks.values()) - assertCountEqual(self, [ - submissions.SubmissionDescriptor( - path=(ROUND_NAME + '/submissions/defense/3.zip'), - participant_id={'team_id': 3}), - submissions.SubmissionDescriptor( - path=(ROUND_NAME + '/submissions/defense/baseline_adv_train.zip'), - participant_id={'baseline_id': 'adv_train'}) - ], self.submissions.defenses.values()) - self.assertEqual(len(self.submissions.attacks) - + len(self.submissions.targeted_attacks) - + len(self.submissions.defenses), - len(set(self.submissions.attacks.keys()) - | set(self.submissions.targeted_attacks.keys()) - | set(self.submissions.defenses.keys()))) - - def verify_datastore_entities(self): - # Verify 'SubmissionType' entities - assertCountEqual(self, [ - self.datastore_client.entity( - fake_cloud_client.FakeDatastoreKey('SubmissionType', 'Attacks')), - self.datastore_client.entity( - fake_cloud_client.FakeDatastoreKey('SubmissionType', - 'TargetedAttacks')), - self.datastore_client.entity( - fake_cloud_client.FakeDatastoreKey('SubmissionType', 'Defenses')), - ], self.datastore_client.query_fetch(kind='SubmissionType')) - # Verify 'Submission' entities - expected_submission_entities = [] - for key_prefix, submission_entries in [ - (('SubmissionType', 'Attacks'), self.submissions.attacks), - (('SubmissionType', 'TargetedAttacks'), - self.submissions.targeted_attacks), - (('SubmissionType', 'Defenses'), self.submissions.defenses)]: - for k, v in submission_entries.items(): - entity = self.datastore_client.entity( - fake_cloud_client.FakeDatastoreKey( - *(key_prefix + ('Submission', k)))) - entity['submission_path'] = v.path - entity.update(v.participant_id) - expected_submission_entities.append(entity) - assertCountEqual(self, expected_submission_entities, - self.datastore_client.query_fetch(kind='Submission')) - - def test_init_from_storage(self): - self.submissions.init_from_storage_write_to_datastore() - self.verify_submissions() - self.verify_datastore_entities() - - def test_init_from_datastore(self): - # first we need to populate datastore - self.submissions.init_from_storage_write_to_datastore() - # now reset submission class and load data from datastore - self.submissions = submissions.CompetitionSubmissions( - datastore_client=self.datastore_client, - storage_client=self.storage_client, - round_name=ROUND_NAME) - self.assertFalse(self.submissions.attacks) - self.assertFalse(self.submissions.targeted_attacks) - self.assertFalse(self.submissions.defenses) - self.submissions.init_from_datastore() - self.verify_submissions() - - def test_get_all_attacks_ids(self): - self.submissions.init_from_storage_write_to_datastore() - # total will be two targeted and two not-targeted attacks, - # their IDs are generated sequentially - assertCountEqual(self, ['SUBA000', 'SUBA001', 'SUBT000', 'SUBT001'], - self.submissions.get_all_attack_ids()) - - def test_find_by_id(self): - self.submissions.init_from_storage_write_to_datastore() - self.assertEqual( - self.submissions.attacks['SUBA000'], - self.submissions.find_by_id('SUBA000')) - self.assertEqual( - self.submissions.targeted_attacks['SUBT001'], - self.submissions.find_by_id('SUBT001')) - self.assertEqual( - self.submissions.defenses['SUBD001'], - self.submissions.find_by_id('SUBD001')) - - def test_get_external_id(self): - self.submissions.init_from_storage_write_to_datastore() - assertCountEqual(self, [3, 'baseline_adv_train'], - [self.submissions.get_external_id('SUBD000'), - self.submissions.get_external_id('SUBD001')]) - assertCountEqual(self, [1, 'baseline_nt'], - [self.submissions.get_external_id('SUBA000'), - self.submissions.get_external_id('SUBA001')]) - - -if __name__ == '__main__': - unittest.main() diff --git a/examples/nips17_adversarial_competition/eval_infra/code/eval_lib/tests/work_data_test.py b/examples/nips17_adversarial_competition/eval_infra/code/eval_lib/tests/work_data_test.py deleted file mode 100644 index 36676ff86..000000000 --- a/examples/nips17_adversarial_competition/eval_infra/code/eval_lib/tests/work_data_test.py +++ /dev/null @@ -1,408 +0,0 @@ -"""Tests for eval_lib.work_data.""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import copy -import unittest - -from six import assertCountEqual -from six import itervalues - -from eval_lib import work_data -from eval_lib.tests import fake_cloud_client - - -TEST_WORK_TYPE_ENTITY_ID = 'AllWork' - - -class WorkPiecesBaseTest(unittest.TestCase): - - def setUp(self): - self.datastore_client = fake_cloud_client.FakeDatastoreClient() - self.work1 = {'submission_id': 's1', - 'output_adversarial_batch_id': 'o1', - 'claimed_worker_id': 'worker9999', - 'claimed_worker_start_time': -1, - 'is_completed': True} - self.work2 = {'submission_id': 's2', - 'output_adversarial_batch_id': 'o2', - 'claimed_worker_id': None, - 'claimed_worker_start_time': None, - 'is_completed': False} - - def reset_work_pieces(self): - self.work_pieces = work_data.WorkPiecesBase(self.datastore_client, - TEST_WORK_TYPE_ENTITY_ID) - - def test_is_unclaimed(self): - # completed work considered claimed - self.assertFalse(work_data.is_unclaimed(self.work1)) - # not completed, not claimed work - self.assertTrue(work_data.is_unclaimed(self.work2)) - # claimed but not completed work - self.work2['claimed_worker_id'] = 'some_worker' - self.work2['claimed_worker_start_time'] = work_data.get_integer_time() - self.assertFalse(work_data.is_unclaimed(self.work2)) - # work claimed too long ago considered unclaimed now - self.work2['claimed_worker_start_time'] = ( - work_data.get_integer_time() - work_data.MAX_PROCESSING_TIME - 1) - self.assertTrue(work_data.is_unclaimed(self.work2)) - - def test_write_to_datastore(self): - self.reset_work_pieces() - self.work_pieces.work['w1'] = self.work1 - self.work_pieces.work['w2'] = self.work2 - self.work_pieces.write_all_to_datastore() - # verify content of the datastore - parent_key = fake_cloud_client.FakeDatastoreKey(work_data.KIND_WORK_TYPE, - TEST_WORK_TYPE_ENTITY_ID) - assertCountEqual( - self, [fake_cloud_client.make_entity(parent_key)], - self.datastore_client.query_fetch(kind=work_data.KIND_WORK_TYPE)) - entity1 = fake_cloud_client.make_entity( - fake_cloud_client.FakeDatastoreKey( - work_data.KIND_WORK, 'w1', parent=parent_key)) - entity1.update(self.work1) - entity2 = fake_cloud_client.make_entity( - fake_cloud_client.FakeDatastoreKey( - work_data.KIND_WORK, 'w2', parent=parent_key)) - entity2.update(self.work2) - assertCountEqual( - self, [entity1, entity2], - self.datastore_client.query_fetch(kind=work_data.KIND_WORK)) - - def test_read_from_datastore(self): - self.reset_work_pieces() - self.work_pieces.work['w10'] = self.work1 - self.work_pieces.work['w20'] = self.work2 - self.work_pieces.write_all_to_datastore() - self.reset_work_pieces() - self.work_pieces.read_all_from_datastore() - # verify data - self.assertDictEqual({'w10': self.work1, 'w20': self.work2}, - self.work_pieces.work) - - def test_is_all_work_completed(self): - self.reset_work_pieces() - # empty set of work is considered completed - self.assertTrue(self.work_pieces.is_all_work_competed()) - # one completed piece of work - all work completed - self.work_pieces.work['w11'] = copy.deepcopy(self.work1) - self.assertTrue(self.work_pieces.is_all_work_competed()) - # two completed pieces of work - all work completed - self.work_pieces.work['w12'] = copy.deepcopy(self.work1) - self.assertTrue(self.work_pieces.is_all_work_competed()) - # two completed and one incomplete pieces of work - work not completed - self.work_pieces.work['w2'] = copy.deepcopy(self.work2) - self.assertFalse(self.work_pieces.is_all_work_competed()) - - def test_read_undone_from_datastore(self): - self.reset_work_pieces() - self.work_pieces.work['w10'] = self.work1 - self.work_pieces.work['w20'] = self.work2 - self.work_pieces.write_all_to_datastore() - self.reset_work_pieces() - # return value is None because sharding is not used - self.assertIsNone(self.work_pieces.read_undone_from_datastore()) - # Only work with ID 'w20' is undone - self.assertDictEqual({'w20': self.work2}, self.work_pieces.work) - - def test_read_undone_from_datastore_same_shards(self): - self.reset_work_pieces() - self.work1['shard_id'] = 1 - self.work_pieces.work['w10'] = self.work1 - self.work2['shard_id'] = 2 - self.work_pieces.work['w20'] = self.work2 - self.work_pieces.write_all_to_datastore() - self.reset_work_pieces() - # return value is ID of the shard with undone work - self.assertEqual(2, self.work_pieces.read_undone_from_datastore( - shard_id=2, num_shards=3)) - # Only work with ID 'w20' is undone - self.assertDictEqual({'w20': self.work2}, self.work_pieces.work) - - def test_read_undone_from_datastore_different_shards(self): - self.reset_work_pieces() - self.work1['shard_id'] = 1 - self.work_pieces.work['w10'] = self.work1 - self.work2['shard_id'] = 2 - self.work_pieces.work['w20'] = self.work2 - self.work_pieces.write_all_to_datastore() - self.reset_work_pieces() - # return value is ID of the shard with undone work - self.assertEqual(2, self.work_pieces.read_undone_from_datastore( - shard_id=1, num_shards=3)) - # Only work with ID 'w20' is undone - self.assertDictEqual({'w20': self.work2}, self.work_pieces.work) - - def test_try_pick_piece_of_work_simple(self): - self.reset_work_pieces() - self.work_pieces.work['w10'] = self.work1 - self.work_pieces.work['w20'] = self.work2 - self.work_pieces.write_all_to_datastore() - work_id = self.work_pieces.try_pick_piece_of_work('worker0') - self.assertEqual('w20', work_id) - self.reset_work_pieces() - self.work_pieces.read_all_from_datastore() - self.assertEqual('worker0', - self.work_pieces.work['w20']['claimed_worker_id']) - - def test_try_pick_piece_of_work_all_completed(self): - self.reset_work_pieces() - self.work_pieces.work['w10'] = self.work1 - self.work_pieces.work['w20'] = self.work2 - self.work_pieces.work['w20']['is_completed'] = True - self.work_pieces.write_all_to_datastore() - work_id = self.work_pieces.try_pick_piece_of_work('worker0') - self.assertIsNone(work_id) - - def test_try_pick_piece_of_work_already_claimed(self): - self.reset_work_pieces() - self.work_pieces.work['w10'] = self.work1 - self.work2['claimed_worker_id'] = 'other_worker' - self.work2['claimed_worker_start_time'] = work_data.get_integer_time() - self.work_pieces.work['w20'] = self.work2 - self.work_pieces.write_all_to_datastore() - work_id = self.work_pieces.try_pick_piece_of_work('worker0') - # if work is claimed by another worker then it won't be picked - self.assertIsNone(work_id) - - def test_try_pick_piece_of_work_claimed_long_ago(self): - self.reset_work_pieces() - self.work_pieces.work['w10'] = self.work1 - self.work2['claimed_worker_id'] = 'other_worker' - self.work2['claimed_worker_start_time'] = ( - work_data.get_integer_time() - work_data.MAX_PROCESSING_TIME * 2) - self.work_pieces.work['w20'] = self.work2 - self.work_pieces.write_all_to_datastore() - work_id = self.work_pieces.try_pick_piece_of_work('worker0') - # if work is claimed by another worker, but it happened some time ago - # then work will be claimed - self.assertEqual('w20', work_id) - - def test_try_pick_piece_of_work_concurrent_update(self): - self.reset_work_pieces() - self.work_pieces.work['w10'] = self.work1 - self.work_pieces.work['w20'] = self.work2 - self.work_pieces.write_all_to_datastore() - # any concurrent change in the entity will cause transaction to fail - - def transaction_hook(client): - key = client.key('WorkType', TEST_WORK_TYPE_ENTITY_ID, 'Work', 'w20') - client.entities[key]['output_adversarial_batch_id'] = 'o3' - self.datastore_client.set_transaction_hook(transaction_hook) - work_id = self.work_pieces.try_pick_piece_of_work('worker0') - self.assertIsNone(work_id) - - def test_try_pick_piece_of_work_concurrent_update_of_other(self): - self.reset_work_pieces() - self.work_pieces.work['w10'] = self.work1 - self.work_pieces.work['w20'] = self.work2 - self.work_pieces.write_all_to_datastore() - # concurrent change in entity which is not touched by the transaction - # won't prevent transaction from completing - - def transaction_hook(client): - key = client.key('WorkType', TEST_WORK_TYPE_ENTITY_ID, 'Work', 'w10') - client.entities[key]['output_adversarial_batch_id'] = 'o3' - self.datastore_client.set_transaction_hook(transaction_hook) - work_id = self.work_pieces.try_pick_piece_of_work('worker0') - self.assertEqual('w20', work_id) - - def test_update_work_as_completed(self): - self.reset_work_pieces() - self.work_pieces.work['w10'] = self.work1 - self.work_pieces.work['w20'] = self.work2 - self.work2['claimed_worker_id'] = 'this_worker' - self.work2['claimed_worker_start_time'] = work_data.get_integer_time() - self.work_pieces.write_all_to_datastore() - self.assertTrue( - self.work_pieces.update_work_as_completed('this_worker', 'w20')) - self.reset_work_pieces() - self.work_pieces.read_all_from_datastore() - self.assertTrue(self.work_pieces.work['w20']['is_completed']) - self.assertNotIn('error', self.work_pieces.work['w20']) - - def test_update_work_as_completed_other_values(self): - self.reset_work_pieces() - self.work_pieces.work['w10'] = self.work1 - self.work_pieces.work['w20'] = self.work2 - self.work2['claimed_worker_id'] = 'this_worker' - self.work2['claimed_worker_start_time'] = work_data.get_integer_time() - self.work_pieces.write_all_to_datastore() - self.assertTrue( - self.work_pieces.update_work_as_completed( - 'this_worker', 'w20', other_values={'a': 123, 'b': 456})) - self.reset_work_pieces() - self.work_pieces.read_all_from_datastore() - self.assertTrue(self.work_pieces.work['w20']['is_completed']) - self.assertNotIn('error', self.work_pieces.work['w20']) - self.assertEqual(123, self.work_pieces.work['w20']['a']) - self.assertEqual(456, self.work_pieces.work['w20']['b']) - - def test_update_work_as_completed_with_error(self): - self.reset_work_pieces() - self.work_pieces.work['w10'] = self.work1 - self.work_pieces.work['w20'] = self.work2 - self.work2['claimed_worker_id'] = 'this_worker' - self.work2['claimed_worker_start_time'] = work_data.get_integer_time() - self.work_pieces.write_all_to_datastore() - self.assertTrue( - self.work_pieces.update_work_as_completed( - 'this_worker', 'w20', error='err')) - self.reset_work_pieces() - self.work_pieces.read_all_from_datastore() - self.assertTrue(self.work_pieces.work['w20']['is_completed']) - self.assertEqual('err', self.work_pieces.work['w20']['error']) - - def test_update_work_as_completed_wrong_claimed_worker(self): - self.reset_work_pieces() - self.work_pieces.work['w10'] = self.work1 - self.work_pieces.work['w20'] = self.work2 - self.work2['claimed_worker_id'] = 'other_worker' - self.work2['claimed_worker_start_time'] = work_data.get_integer_time() - self.work_pieces.write_all_to_datastore() - self.assertFalse( - self.work_pieces.update_work_as_completed('this_worker', 'w20')) - self.reset_work_pieces() - self.work_pieces.read_all_from_datastore() - self.assertFalse(self.work_pieces.work['w20']['is_completed']) - - def test_compute_work_stats(self): - self.reset_work_pieces() - self.work_pieces.work['w11'] = { - 'submission_id': 's1', - 'output_adversarial_batch_id': 'o1', - 'claimed_worker_id': 'worker1', - 'claimed_worker_start_time': -1, - 'is_completed': True, - 'elapsed_time': 1, - } - self.work_pieces.work['w12'] = { - 'submission_id': 's1', - 'output_adversarial_batch_id': 'o2', - 'claimed_worker_id': 'worker2', - 'claimed_worker_start_time': -1, - 'is_completed': False, - } - self.work_pieces.work['w21'] = { - 'submission_id': 's2', - 'output_adversarial_batch_id': 'o1', - 'claimed_worker_id': 'worker1', - 'claimed_worker_start_time': -1, - 'is_completed': True, - 'elapsed_time': 5, - } - self.work_pieces.work['w22'] = { - 'submission_id': 's2', - 'output_adversarial_batch_id': 'o2', - 'claimed_worker_id': 'worker2', - 'claimed_worker_start_time': -1, - 'is_completed': True, - 'elapsed_time': 10, - 'error': 'err', - } - self.work_pieces.work['w23'] = { - 'submission_id': 's2', - 'output_adversarial_batch_id': 'o1', - 'claimed_worker_id': 'worker1', - 'claimed_worker_start_time': -1, - 'is_completed': True, - 'elapsed_time': 7, - } - stats = self.work_pieces.compute_work_statistics() - for v in itervalues(stats): - v['eval_times'] = sorted(v['eval_times']) - self.assertDictEqual( - { - 's1': {'completed': 1, - 'num_errors': 0, - 'error_messages': set(), - 'eval_times': [1.0], - 'min_eval_time': 1.0, - 'max_eval_time': 1.0, - 'mean_eval_time': 1.0, - 'median_eval_time': 1.0}, - 's2': {'completed': 3, - 'num_errors': 1, - 'error_messages': set(['err']), - 'eval_times': [5.0, 7.0], - 'min_eval_time': 5.0, - 'max_eval_time': 7.0, - 'mean_eval_time': 6.0, - 'median_eval_time': 6.0}, - }, stats) - - -class AttackWorkPiecesTest(unittest.TestCase): - - def setUp(self): - self.datastore_client = fake_cloud_client.FakeDatastoreClient() - - def test_init_from_adversarial_batches(self): - adv_batches = { - 'ADVBATCH000': {'submission_id': 's1'}, - 'ADVBATCH001': {'submission_id': 's2'}, - 'ADVBATCH002': {'submission_id': 's3'}, - } - expected_values = [ - {'claimed_worker_id': None, 'claimed_worker_start_time': None, - 'is_completed': False, 'error': None, 'elapsed_time': None, - 'submission_id': 's1', 'shard_id': None, - 'output_adversarial_batch_id': 'ADVBATCH000'}, - {'claimed_worker_id': None, 'claimed_worker_start_time': None, - 'is_completed': False, 'error': None, 'elapsed_time': None, - 'submission_id': 's2', 'shard_id': None, - 'output_adversarial_batch_id': 'ADVBATCH001'}, - {'claimed_worker_id': None, 'claimed_worker_start_time': None, - 'is_completed': False, 'error': None, 'elapsed_time': None, - 'submission_id': 's3', 'shard_id': None, - 'output_adversarial_batch_id': 'ADVBATCH002'} - ] - attack_work = work_data.AttackWorkPieces(self.datastore_client) - attack_work.init_from_adversarial_batches(adv_batches) - assertCountEqual(self, expected_values, attack_work.work.values()) - attack_work.write_all_to_datastore() - attack_work = work_data.AttackWorkPieces(self.datastore_client) - attack_work.read_all_from_datastore() - assertCountEqual(self, expected_values, attack_work.work.values()) - - -class DefenseWorkPiecesTest(unittest.TestCase): - - def setUp(self): - self.datastore_client = fake_cloud_client.FakeDatastoreClient() - - def test_init_from_classification_batches(self): - class_batches = { - 'CBATCH000000': {'submission_id': 's1'}, - 'CBATCH000001': {'submission_id': 's2'}, - 'CBATCH000002': {'submission_id': 's3'}, - } - expected_values = [ - {'claimed_worker_id': None, 'claimed_worker_start_time': None, - 'is_completed': False, 'error': None, 'elapsed_time': None, - 'submission_id': 's1', 'shard_id': None, - 'output_classification_batch_id': 'CBATCH000000'}, - {'claimed_worker_id': None, 'claimed_worker_start_time': None, - 'is_completed': False, 'error': None, 'elapsed_time': None, - 'submission_id': 's2', 'shard_id': None, - 'output_classification_batch_id': 'CBATCH000001'}, - {'claimed_worker_id': None, 'claimed_worker_start_time': None, - 'is_completed': False, 'error': None, 'elapsed_time': None, - 'submission_id': 's3', 'shard_id': None, - 'output_classification_batch_id': 'CBATCH000002'} - ] - defense_work = work_data.DefenseWorkPieces(self.datastore_client) - defense_work.init_from_class_batches(class_batches) - assertCountEqual(self, expected_values, defense_work.work.values()) - defense_work.write_all_to_datastore() - defense_work = work_data.DefenseWorkPieces(self.datastore_client) - defense_work.read_all_from_datastore() - assertCountEqual(self, expected_values, defense_work.work.values()) - - -if __name__ == '__main__': - unittest.main() diff --git a/examples/nips17_adversarial_competition/eval_infra/code/eval_lib/work_data.py b/examples/nips17_adversarial_competition/eval_infra/code/eval_lib/work_data.py deleted file mode 100644 index 894317ad6..000000000 --- a/examples/nips17_adversarial_competition/eval_infra/code/eval_lib/work_data.py +++ /dev/null @@ -1,411 +0,0 @@ -"""Module with classes to read and store data about work entities. -""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from builtins import int # long in python 2 - -from io import StringIO -import pickle -import random -import time - -import numpy as np - -from six import iteritems -from six import itervalues -from six import text_type - - -# Cloud Datastore constants -KIND_WORK_TYPE = u'WorkType' -KIND_WORK = u'Work' -ID_ATTACKS_WORK_ENTITY = u'AllAttacks' -ID_DEFENSES_WORK_ENTITY = u'AllDefenses' - -ATTACK_WORK_ID_PATTERN = u'WORKA{:03}' -DEFENSE_WORK_ID_PATTERN = u'WORKD{:05}' - -# Constants for __str__ -TO_STR_MAX_WORK = 20 - -# How long worker is allowed to process one piece of work, -# before considered failed -MAX_PROCESSING_TIME = 600 - -# Number of work records to read at once -MAX_WORK_RECORDS_READ = 1000 - - -def get_integer_time(): - """Returns current time in long integer format.""" - return int(time.time()) - - -def is_unclaimed(work): - """Returns True if work piece is unclaimed.""" - if work['is_completed']: - return False - cutoff_time = time.time() - MAX_PROCESSING_TIME - if (work['claimed_worker_id'] and - work['claimed_worker_start_time'] is not None - and work['claimed_worker_start_time'] >= cutoff_time): - return False - return True - - -class WorkPiecesBase(object): - """Base class to store one piece of work. - - In adversarial competition, all work consists of the following: - - evaluation of all attacks on all images from dataset which results in - generation of adversarial images; - - evaluation of all defenses on all adversarial images which results in - storing classification labels. - - One piece of work is either evaluation of one attack on a subset of images or - evaluation of one defense on a subset of adversarial images. - This way all work is split into work pieces which could be computed - independently in parallel by different workers. - - Each work piece is identified by unique ID and has one of the following - statuses: - - Unclaimed. This means that no worker has started working on the work piece. - - Claimed by worker NN. This means that worker NN is working on this work - piece. After workpiece being claimed for too long (more than - MAX_PROCESSING_TIME seconds) it automatically considered unclaimed. This - is needed in case worker failed while processing the work piece. - - Completed. This means that computation of work piece is done. - - Additionally each work piece may be assigned to a shard. In such case - workers are also grouped into shards. Each time worker looking for a work - piece it first tries to find undone work from the shard worker is assigned to. - Only after all work from this shard is done, worker will try to claim - work pieces from other shards. - - The purpose of sharding is to reduce load on Google Cloud Datastore. - """ - - def __init__(self, - datastore_client, - work_type_entity_id): - """Initializes WorkPiecesBase class. - - Args: - datastore_client: instance of CompetitionDatastoreClient. - work_type_entity_id: ID of the WorkType parent entity - """ - self._datastore_client = datastore_client - self._work_type_entity_id = work_type_entity_id - # Dictionary: work_id -> dict with properties of the piece of work - # - # Common properties are following: - # - claimed_worker_id - worker id which claimed the work - # - claimed_worker_start_time - when work was claimed - # - is_completed - whether work is completed or not - # - error - if not None then work was completed with error - # - elapsed_time - time took to complete the work - # - shard_id - ID of the shard which run the work - # - submission_id - ID of the submission which should be executed - # - # Additionally piece of work will have property specific to work type: - # output_adversarial_batch_id for attack and output_classification_batch_id - # for defense. Also upon completion of the work, worker may write - # additional statistics field to the work. - self._work = {} - - def serialize(self, fobj): - """Serialize work pieces into file object.""" - pickle.dump(self._work, fobj) - - def deserialize(self, fobj): - """Deserialize work pieces from file object.""" - self._work = pickle.load(fobj) - - @property - def work(self): - """Dictionary with all work pieces.""" - return self._work - - def replace_work(self, value): - """Replaces work with provided value. - - Generally this method should be called only by master, that's why it - separated from the property self.work. - - Args: - value: dictionary with new work pieces - """ - assert isinstance(value, dict) - self._work = value - - def __len__(self): - return len(self._work) - - def is_all_work_competed(self): - """Returns whether all work pieces are completed or not.""" - return all([w['is_completed'] for w in itervalues(self.work)]) - - def write_all_to_datastore(self): - """Writes all work pieces into datastore. - - Each work piece is identified by ID. This method writes/updates only those - work pieces which IDs are stored in this class. For examples, if this class - has only work pieces with IDs '1' ... '100' and datastore already contains - work pieces with IDs '50' ... '200' then this method will create new - work pieces with IDs '1' ... '49', update work pieces with IDs - '50' ... '100' and keep unchanged work pieces with IDs '101' ... '200'. - """ - client = self._datastore_client - with client.no_transact_batch() as batch: - parent_key = client.key(KIND_WORK_TYPE, self._work_type_entity_id) - batch.put(client.entity(parent_key)) - for work_id, work_val in iteritems(self._work): - entity = client.entity(client.key(KIND_WORK, work_id, - parent=parent_key)) - entity.update(work_val) - batch.put(entity) - - def read_all_from_datastore(self): - """Reads all work pieces from the datastore.""" - self._work = {} - client = self._datastore_client - parent_key = client.key(KIND_WORK_TYPE, self._work_type_entity_id) - for entity in client.query_fetch(kind=KIND_WORK, ancestor=parent_key): - work_id = entity.key.flat_path[-1] - self.work[work_id] = dict(entity) - - def _read_undone_shard_from_datastore(self, shard_id=None): - """Reads undone worke pieces which are assigned to shard with given id.""" - self._work = {} - client = self._datastore_client - parent_key = client.key(KIND_WORK_TYPE, self._work_type_entity_id) - filters = [('is_completed', '=', False)] - if shard_id is not None: - filters.append(('shard_id', '=', shard_id)) - for entity in client.query_fetch(kind=KIND_WORK, ancestor=parent_key, - filters=filters): - work_id = entity.key.flat_path[-1] - self.work[work_id] = dict(entity) - if len(self._work) >= MAX_WORK_RECORDS_READ: - break - - def read_undone_from_datastore(self, shard_id=None, num_shards=None): - """Reads undone work from the datastore. - - If shard_id and num_shards are specified then this method will attempt - to read undone work for shard with id shard_id. If no undone work was found - then it will try to read shard (shard_id+1) and so on until either found - shard with undone work or all shards are read. - - Args: - shard_id: Id of the start shard - num_shards: total number of shards - - Returns: - id of the shard with undone work which was read. None means that work - from all datastore was read. - """ - if shard_id is not None: - shards_list = [(i + shard_id) % num_shards for i in range(num_shards)] - else: - shards_list = [] - shards_list.append(None) - for shard in shards_list: - self._read_undone_shard_from_datastore(shard) - if self._work: - return shard - return None - - def try_pick_piece_of_work(self, worker_id, submission_id=None): - """Tries pick next unclaimed piece of work to do. - - Attempt to claim work piece is done using Cloud Datastore transaction, so - only one worker can claim any work piece at a time. - - Args: - worker_id: ID of current worker - submission_id: if not None then this method will try to pick - piece of work for this submission - - Returns: - ID of the claimed work piece - """ - client = self._datastore_client - unclaimed_work_ids = None - if submission_id: - unclaimed_work_ids = [ - k for k, v in iteritems(self.work) - if is_unclaimed(v) and (v['submission_id'] == submission_id) - ] - if not unclaimed_work_ids: - unclaimed_work_ids = [k for k, v in iteritems(self.work) - if is_unclaimed(v)] - if unclaimed_work_ids: - next_work_id = random.choice(unclaimed_work_ids) - else: - return None - try: - with client.transaction() as transaction: - work_key = client.key(KIND_WORK_TYPE, self._work_type_entity_id, - KIND_WORK, next_work_id) - work_entity = client.get(work_key, transaction=transaction) - if not is_unclaimed(work_entity): - return None - work_entity['claimed_worker_id'] = worker_id - work_entity['claimed_worker_start_time'] = get_integer_time() - transaction.put(work_entity) - except Exception: - return None - return next_work_id - - def update_work_as_completed(self, worker_id, work_id, other_values=None, - error=None): - """Updates work piece in datastore as completed. - - Args: - worker_id: ID of the worker which did the work - work_id: ID of the work which was done - other_values: dictionary with additonal values which should be saved - with the work piece - error: if not None then error occurred during computation of the work - piece. In such case work will be marked as completed with error. - - Returns: - whether work was successfully updated - """ - client = self._datastore_client - try: - with client.transaction() as transaction: - work_key = client.key(KIND_WORK_TYPE, self._work_type_entity_id, - KIND_WORK, work_id) - work_entity = client.get(work_key, transaction=transaction) - if work_entity['claimed_worker_id'] != worker_id: - return False - work_entity['is_completed'] = True - if other_values: - work_entity.update(other_values) - if error: - work_entity['error'] = text_type(error) - transaction.put(work_entity) - except Exception: - return False - return True - - def compute_work_statistics(self): - """Computes statistics from all work pieces stored in this class.""" - result = {} - for v in itervalues(self.work): - submission_id = v['submission_id'] - if submission_id not in result: - result[submission_id] = { - 'completed': 0, - 'num_errors': 0, - 'error_messages': set(), - 'eval_times': [], - 'min_eval_time': None, - 'max_eval_time': None, - 'mean_eval_time': None, - 'median_eval_time': None, - } - if not v['is_completed']: - continue - result[submission_id]['completed'] += 1 - if 'error' in v and v['error']: - result[submission_id]['num_errors'] += 1 - result[submission_id]['error_messages'].add(v['error']) - else: - result[submission_id]['eval_times'].append(float(v['elapsed_time'])) - for v in itervalues(result): - if v['eval_times']: - v['min_eval_time'] = np.min(v['eval_times']) - v['max_eval_time'] = np.max(v['eval_times']) - v['mean_eval_time'] = np.mean(v['eval_times']) - v['median_eval_time'] = np.median(v['eval_times']) - return result - - def __str__(self): - buf = StringIO() - buf.write(u'WorkType "{0}"\n'.format(self._work_type_entity_id)) - for idx, (work_id, work_val) in enumerate(iteritems(self.work)): - if idx >= TO_STR_MAX_WORK: - buf.write(u' ...\n') - break - buf.write(u' Work "{0}"\n'.format(work_id)) - buf.write(u' {0}\n'.format(str(work_val))) - return buf.getvalue() - - -class AttackWorkPieces(WorkPiecesBase): - """Subclass which represents work pieces for adversarial attacks.""" - - def __init__(self, datastore_client): - """Initializes AttackWorkPieces.""" - super(AttackWorkPieces, self).__init__( - datastore_client=datastore_client, - work_type_entity_id=ID_ATTACKS_WORK_ENTITY) - - def init_from_adversarial_batches(self, adv_batches): - """Initializes work pieces from adversarial batches. - - Args: - adv_batches: dict with adversarial batches, - could be obtained as AversarialBatches.data - """ - for idx, (adv_batch_id, adv_batch_val) in enumerate(iteritems(adv_batches)): - work_id = ATTACK_WORK_ID_PATTERN.format(idx) - self.work[work_id] = { - 'claimed_worker_id': None, - 'claimed_worker_start_time': None, - 'is_completed': False, - 'error': None, - 'elapsed_time': None, - 'submission_id': adv_batch_val['submission_id'], - 'shard_id': None, - 'output_adversarial_batch_id': adv_batch_id, - } - - -class DefenseWorkPieces(WorkPiecesBase): - """Subclass which represents work pieces for adversarial defenses.""" - - def __init__(self, datastore_client): - """Initializes DefenseWorkPieces.""" - super(DefenseWorkPieces, self).__init__( - datastore_client=datastore_client, - work_type_entity_id=ID_DEFENSES_WORK_ENTITY) - - def init_from_class_batches(self, class_batches, num_shards=None): - """Initializes work pieces from classification batches. - - Args: - class_batches: dict with classification batches, could be obtained - as ClassificationBatches.data - num_shards: number of shards to split data into, - if None then no sharding is done. - """ - shards_for_submissions = {} - shard_idx = 0 - for idx, (batch_id, batch_val) in enumerate(iteritems(class_batches)): - work_id = DEFENSE_WORK_ID_PATTERN.format(idx) - submission_id = batch_val['submission_id'] - shard_id = None - if num_shards: - shard_id = shards_for_submissions.get(submission_id) - if shard_id is None: - shard_id = shard_idx % num_shards - shards_for_submissions[submission_id] = shard_id - shard_idx += 1 - # Note: defense also might have following fields populated by worker: - # stat_correct, stat_error, stat_target_class, stat_num_images - self.work[work_id] = { - 'claimed_worker_id': None, - 'claimed_worker_start_time': None, - 'is_completed': False, - 'error': None, - 'elapsed_time': None, - 'submission_id': submission_id, - 'shard_id': shard_id, - 'output_classification_batch_id': batch_id, - } diff --git a/examples/nips17_adversarial_competition/eval_infra/code/master.py b/examples/nips17_adversarial_competition/eval_infra/code/master.py deleted file mode 100644 index d4d8c0d21..000000000 --- a/examples/nips17_adversarial_competition/eval_infra/code/master.py +++ /dev/null @@ -1,793 +0,0 @@ -"""Master which prepares work for all workers. - -Evaluation of competition is split into work pieces. One work piece is a -either evaluation of an attack on a batch of images or evaluation of a -defense on a batch of adversarial images. -Work pieces are run by workers. Master prepares work pieces for workers and -writes them to the datastore. -""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import argparse -import collections -from collections import defaultdict -import csv -from io import BytesIO -import logging -import os -import pickle -import random -import time - -from six import iteritems -from six import iterkeys -from six import itervalues -from six.moves import input as input_str - -import eval_lib - - -# List of allowed sizes of adversarial perturbation -ALLOWED_EPS = [4, 8, 12, 16] - -# Batch size -DEFAULT_BATCH_SIZE = 100 - - -def print_header(text): - """Prints header with given text and frame composed of '#' characters.""" - print() - print('#'*(len(text)+4)) - print('# ' + text + ' #') - print('#'*(len(text)+4)) - print() - - -def save_dict_to_file(filename, dictionary): - """Saves dictionary as CSV file.""" - with open(filename, 'w') as f: - writer = csv.writer(f) - for k, v in iteritems(dictionary): - writer.writerow([str(k), str(v)]) - - -class EvaluationMaster(object): - """Class which encapsulates logit of the master.""" - - def __init__(self, storage_client, datastore_client, round_name, dataset_name, - blacklisted_submissions='', results_dir='', - num_defense_shards=None, verbose=False, - batch_size=DEFAULT_BATCH_SIZE, max_dataset_num_images=None): - """Initializes EvaluationMaster. - - Args: - storage_client: instance of eval_lib.CompetitionStorageClient - datastore_client: instance of eval_lib.CompetitionDatastoreClient - round_name: name of the current round - dataset_name: name of the dataset, 'dev' or 'final' - blacklisted_submissions: optional list of blacklisted submissions which - should not be evaluated - results_dir: local directory where results and logs should be written - num_defense_shards: optional number of defense shards - verbose: whether output should be verbose on not. If True, then methods - of this class will print some additional information which is useful - for debugging. - batch_size: batch size to use - max_dataset_num_images: maximum number of images from the dataset to use - or None if entire dataset should be used. - """ - self.storage_client = storage_client - self.datastore_client = datastore_client - self.round_name = round_name - self.dataset_name = dataset_name - self.results_dir = results_dir - if num_defense_shards: - self.num_defense_shards = int(num_defense_shards) - else: - self.num_defense_shards = None - self.verbose = verbose - self.blacklisted_submissions = [s.strip() - for s in blacklisted_submissions.split(',')] - self.batch_size = batch_size - self.max_dataset_num_images = max_dataset_num_images - # init client classes - self.submissions = eval_lib.CompetitionSubmissions( - datastore_client=self.datastore_client, - storage_client=self.storage_client, - round_name=self.round_name) - self.dataset_batches = eval_lib.DatasetBatches( - datastore_client=self.datastore_client, - storage_client=self.storage_client, - dataset_name=self.dataset_name) - self.adv_batches = eval_lib.AversarialBatches( - datastore_client=self.datastore_client) - self.class_batches = eval_lib.ClassificationBatches( - datastore_client=self.datastore_client, - storage_client=self.storage_client, - round_name=self.round_name) - self.attack_work = eval_lib.AttackWorkPieces( - datastore_client=self.datastore_client) - self.defense_work = eval_lib.DefenseWorkPieces( - datastore_client=self.datastore_client) - - def ask_when_work_is_populated(self, work): - """When work is already populated asks whether we should continue. - - This method prints warning message that work is populated and asks - whether user wants to continue or not. - - Args: - work: instance of WorkPiecesBase - - Returns: - True if we should continue and populate datastore, False if we should stop - """ - work.read_all_from_datastore() - if work.work: - print('Work is already written to datastore.\n' - 'If you continue these data will be overwritten and ' - 'possible corrupted.') - inp = input_str('Do you want to continue? ' - '(type "yes" without quotes to confirm): ') - return inp == 'yes' - else: - return True - - def prepare_attacks(self): - """Prepares all data needed for evaluation of attacks.""" - print_header('PREPARING ATTACKS DATA') - # verify that attacks data not written yet - if not self.ask_when_work_is_populated(self.attack_work): - return - self.attack_work = eval_lib.AttackWorkPieces( - datastore_client=self.datastore_client) - # prepare submissions - print_header('Initializing submissions') - self.submissions.init_from_storage_write_to_datastore() - if self.verbose: - print(self.submissions) - # prepare dataset batches - print_header('Initializing dataset batches') - self.dataset_batches.init_from_storage_write_to_datastore( - batch_size=self.batch_size, - allowed_epsilon=ALLOWED_EPS, - skip_image_ids=[], - max_num_images=self.max_dataset_num_images) - if self.verbose: - print(self.dataset_batches) - # prepare adversarial batches - print_header('Initializing adversarial batches') - self.adv_batches.init_from_dataset_and_submissions_write_to_datastore( - dataset_batches=self.dataset_batches, - attack_submission_ids=self.submissions.get_all_attack_ids()) - if self.verbose: - print(self.adv_batches) - # prepare work pieces - print_header('Preparing attack work pieces') - self.attack_work.init_from_adversarial_batches(self.adv_batches.data) - self.attack_work.write_all_to_datastore() - if self.verbose: - print(self.attack_work) - - def prepare_defenses(self): - """Prepares all data needed for evaluation of defenses.""" - print_header('PREPARING DEFENSE DATA') - # verify that defense data not written yet - if not self.ask_when_work_is_populated(self.defense_work): - return - self.defense_work = eval_lib.DefenseWorkPieces( - datastore_client=self.datastore_client) - # load results of attacks - self.submissions.init_from_datastore() - self.dataset_batches.init_from_datastore() - self.adv_batches.init_from_datastore() - self.attack_work.read_all_from_datastore() - # populate classification results - print_header('Initializing classification batches') - self.class_batches.init_from_adversarial_batches_write_to_datastore( - self.submissions, self.adv_batches) - if self.verbose: - print(self.class_batches) - # populate work pieces - print_header('Preparing defense work pieces') - self.defense_work.init_from_class_batches( - self.class_batches.data, num_shards=self.num_defense_shards) - self.defense_work.write_all_to_datastore() - if self.verbose: - print(self.defense_work) - - def _save_work_results(self, run_stats, scores, num_processed_images, - filename): - """Saves statistics about each submission. - - Saved statistics include score; number of completed and failed batches; - min, max, average and median time needed to run one batch. - - Args: - run_stats: dictionary with runtime statistics for submissions, - can be generated by WorkPiecesBase.compute_work_statistics - scores: dictionary mapping submission ids to scores - num_processed_images: dictionary with number of successfully processed - images by each submission, one of the outputs of - ClassificationBatches.compute_classification_results - filename: output filename - """ - with open(filename, 'w') as f: - writer = csv.writer(f) - writer.writerow( - ['SubmissionID', 'ExternalSubmissionId', 'Score', - 'CompletedBatches', 'BatchesWithError', 'ProcessedImages', - 'MinEvalTime', 'MaxEvalTime', - 'MedianEvalTime', 'MeanEvalTime', - 'ErrorMsg']) - for submission_id in sorted(iterkeys(run_stats)): - stat = run_stats.get( - submission_id, - collections.defaultdict(lambda: float('NaN'))) - external_id = self.submissions.get_external_id(submission_id) - error_msg = '' - while not error_msg and stat['error_messages']: - error_msg = stat['error_messages'].pop() - if error_msg.startswith('Cant copy adversarial batch locally'): - error_msg = '' - writer.writerow([ - submission_id, external_id, scores.get(submission_id, None), - stat['completed'], stat['num_errors'], - num_processed_images.get(submission_id, None), - stat['min_eval_time'], stat['max_eval_time'], - stat['median_eval_time'], stat['mean_eval_time'], - error_msg - ]) - - def _save_sorted_results(self, run_stats, scores, image_count, filename): - """Saves sorted (by score) results of the evaluation. - - Args: - run_stats: dictionary with runtime statistics for submissions, - can be generated by WorkPiecesBase.compute_work_statistics - scores: dictionary mapping submission ids to scores - image_count: dictionary with number of images processed by submission - filename: output filename - """ - with open(filename, 'w') as f: - writer = csv.writer(f) - writer.writerow(['SubmissionID', 'ExternalTeamId', 'Score', - 'MedianTime', 'ImageCount']) - - def get_second(x): - """Returns second entry of a list/tuple""" - return x[1] - for s_id, score in sorted(iteritems(scores), - key=get_second, reverse=True): - external_id = self.submissions.get_external_id(s_id) - stat = run_stats.get( - s_id, collections.defaultdict(lambda: float('NaN'))) - writer.writerow([s_id, external_id, score, - stat['median_eval_time'], - image_count[s_id]]) - - def _read_dataset_metadata(self): - """Reads dataset metadata. - - Returns: - instance of DatasetMetadata - """ - blob = self.storage_client.get_blob( - 'dataset/' + self.dataset_name + '_dataset.csv') - buf = BytesIO() - blob.download_to_file(buf) - buf.seek(0) - return eval_lib.DatasetMetadata(buf) - - def compute_results(self): - """Computes results (scores, stats, etc...) of competition evaluation. - - Results are saved into output directory (self.results_dir). - Also this method saves all intermediate data into output directory as well, - so it can resume computation if it was interrupted for some reason. - This is useful because computatin of resuls could take many minutes. - """ - # read all data - logging.info('Reading data from datastore') - dataset_meta = self._read_dataset_metadata() - self.submissions.init_from_datastore() - self.dataset_batches.init_from_datastore() - self.adv_batches.init_from_datastore() - self.attack_work.read_all_from_datastore() - - if os.path.exists(os.path.join(self.results_dir, 'defense_work.dump')): - with open(os.path.join(self.results_dir, 'defense_work.dump')) as f: - self.defense_work.deserialize(f) - else: - self.defense_work.read_all_from_datastore() - with open(os.path.join(self.results_dir, 'defense_work.dump'), 'w') as f: - self.defense_work.serialize(f) - - if os.path.exists(os.path.join(self.results_dir, 'class_batches.dump')): - with open(os.path.join(self.results_dir, 'class_batches.dump')) as f: - self.class_batches.deserialize(f) - else: - self.class_batches.init_from_datastore() - with open(os.path.join(self.results_dir, 'class_batches.dump'), 'w') as f: - self.class_batches.serialize(f) - - # process data - logging.info('Processing classification results') - count_adv_images = self.adv_batches.count_generated_adv_examples() - intermediate_files = ['acc_matrix.dump', 'error_matrix.dump', - 'hit_tc_matrix.dump', 'classified_images_count.dump'] - if all([os.path.exists(os.path.join(self.results_dir, fname)) - for fname in intermediate_files]): - with open(os.path.join(self.results_dir, 'acc_matrix.dump')) as f: - acc_matrix = pickle.load(f) - with open(os.path.join(self.results_dir, 'error_matrix.dump')) as f: - error_matrix = pickle.load(f) - with open(os.path.join(self.results_dir, 'hit_tc_matrix.dump')) as f: - hit_tc_matrix = pickle.load(f) - with open(os.path.join(self.results_dir, - 'classified_images_count.dump')) as f: - classified_images_count = pickle.load(f) - else: - acc_matrix, error_matrix, hit_tc_matrix, classified_images_count = ( - self.class_batches.compute_classification_results( - self.adv_batches, - self.dataset_batches, - dataset_meta, - self.defense_work)) - with open(os.path.join(self.results_dir, 'acc_matrix.dump'), 'w') as f: - pickle.dump(acc_matrix, f) - with open(os.path.join(self.results_dir, 'error_matrix.dump'), 'w') as f: - pickle.dump(error_matrix, f) - with open(os.path.join(self.results_dir, 'hit_tc_matrix.dump'), 'w') as f: - pickle.dump(hit_tc_matrix, f) - with open(os.path.join(self.results_dir, - 'classified_images_count.dump'), 'w') as f: - pickle.dump(classified_images_count, f) - - # compute attacks and defenses which will be used for scoring - logging.info('Computing attacks and defenses which are used for scoring') - expected_num_adv_images = self.dataset_batches.count_num_images() - attacks_to_use = [k for k, v in iteritems(count_adv_images) - if ((v == expected_num_adv_images) - and (k not in self.blacklisted_submissions))] - - total_num_adversarial = sum(itervalues(count_adv_images)) - defenses_to_use = [k for k, v in iteritems(classified_images_count) - if ((v == total_num_adversarial) - and (k not in self.blacklisted_submissions))] - - logging.info('Expected number of adversarial images: %d', - expected_num_adv_images) - logging.info('Number of attacks to use to score defenses: %d', - len(attacks_to_use)) - logging.info('Expected number of classification predictions: %d', - total_num_adversarial) - logging.info('Number of defenses to use to score attacks: %d', - len(defenses_to_use)) - - save_dict_to_file(os.path.join(self.results_dir, 'count_adv_images.csv'), - count_adv_images) - save_dict_to_file(os.path.join(self.results_dir, - 'classified_images_count.csv'), - classified_images_count) - - # compute scores - logging.info('Computing scores') - attack_scores = defaultdict(lambda: 0) - targeted_attack_scores = defaultdict(lambda: 0) - defense_scores = defaultdict(lambda: 0) - for defense_id in acc_matrix.dim0: - for attack_id in acc_matrix.dim1: - if attack_id in attacks_to_use: - defense_scores[defense_id] += acc_matrix[defense_id, attack_id] - if defense_id in defenses_to_use: - if attack_id in self.submissions.targeted_attacks: - targeted_attack_scores[attack_id] += ( - hit_tc_matrix[defense_id, attack_id]) - else: - attack_scores[attack_id] += error_matrix[defense_id, attack_id] - # negate results of blacklisted submissions - for s_id in self.blacklisted_submissions: - if s_id in defense_scores: - defense_scores[s_id] = -defense_scores[s_id] - if s_id in attack_scores: - attack_scores[s_id] = -attack_scores[s_id] - if s_id in targeted_attack_scores: - targeted_attack_scores[s_id] = -targeted_attack_scores[s_id] - # save results - logging.info('Saving results') - all_attack_stats = self.attack_work.compute_work_statistics() - nontargeted_attack_stats = {k: v for k, v in iteritems(all_attack_stats) - if k in self.submissions.attacks} - targeted_attack_stats = {k: v for k, v in iteritems(all_attack_stats) - if k in self.submissions.targeted_attacks} - defense_stats = self.defense_work.compute_work_statistics() - self._save_work_results( - nontargeted_attack_stats, attack_scores, count_adv_images, - os.path.join(self.results_dir, 'attack_results.csv')) - self._save_work_results( - targeted_attack_stats, targeted_attack_scores, count_adv_images, - os.path.join(self.results_dir, 'targeted_attack_results.csv')) - self._save_work_results( - defense_stats, defense_scores, - classified_images_count, - os.path.join(self.results_dir, 'defense_results.csv')) - - self._save_sorted_results( - nontargeted_attack_stats, attack_scores, count_adv_images, - os.path.join(self.results_dir, 'sorted_attack_results.csv')) - self._save_sorted_results( - targeted_attack_stats, targeted_attack_scores, count_adv_images, - os.path.join(self.results_dir, 'sorted_target_attack_results.csv')) - self._save_sorted_results( - defense_stats, defense_scores, classified_images_count, - os.path.join(self.results_dir, 'sorted_defense_results.csv')) - - defense_id_to_name = {k: self.submissions.get_external_id(k) - for k in iterkeys(self.submissions.defenses)} - attack_id_to_name = {k: self.submissions.get_external_id(k) - for k in self.submissions.get_all_attack_ids()} - acc_matrix.save_to_file( - os.path.join(self.results_dir, 'accuracy_matrix.csv'), - remap_dim0=defense_id_to_name, remap_dim1=attack_id_to_name) - error_matrix.save_to_file( - os.path.join(self.results_dir, 'error_matrix.csv'), - remap_dim0=defense_id_to_name, remap_dim1=attack_id_to_name) - hit_tc_matrix.save_to_file( - os.path.join(self.results_dir, 'hit_target_class_matrix.csv'), - remap_dim0=defense_id_to_name, remap_dim1=attack_id_to_name) - - save_dict_to_file(os.path.join(self.results_dir, 'defense_id_to_name.csv'), - defense_id_to_name) - save_dict_to_file(os.path.join(self.results_dir, 'attack_id_to_name.csv'), - attack_id_to_name) - - def _show_status_for_work(self, work): - """Shows status for given work pieces. - - Args: - work: instance of either AttackWorkPieces or DefenseWorkPieces - """ - work_count = len(work.work) - work_completed = {} - work_completed_count = 0 - for v in itervalues(work.work): - if v['is_completed']: - work_completed_count += 1 - worker_id = v['claimed_worker_id'] - if worker_id not in work_completed: - work_completed[worker_id] = { - 'completed_count': 0, - 'last_update': 0.0, - } - work_completed[worker_id]['completed_count'] += 1 - work_completed[worker_id]['last_update'] = max( - work_completed[worker_id]['last_update'], - v['claimed_worker_start_time']) - print('Completed {0}/{1} work'.format(work_completed_count, - work_count)) - for k in sorted(iterkeys(work_completed)): - last_update_time = time.strftime( - '%Y-%m-%d %H:%M:%S', - time.localtime(work_completed[k]['last_update'])) - print('Worker {0}: completed {1} last claimed work at {2}'.format( - k, work_completed[k]['completed_count'], last_update_time)) - - def _export_work_errors(self, work, output_file): - """Saves errors for given work pieces into file. - - Args: - work: instance of either AttackWorkPieces or DefenseWorkPieces - output_file: name of the output file - """ - errors = set() - for v in itervalues(work.work): - if v['is_completed'] and v['error'] is not None: - errors.add(v['error']) - with open(output_file, 'w') as f: - for e in sorted(errors): - f.write(e) - f.write('\n') - - def show_status(self): - """Shows current status of competition evaluation. - - Also this method saves error messages generated by attacks and defenses - into attack_errors.txt and defense_errors.txt. - """ - print_header('Attack work statistics') - self.attack_work.read_all_from_datastore() - self._show_status_for_work(self.attack_work) - self._export_work_errors( - self.attack_work, - os.path.join(self.results_dir, 'attack_errors.txt')) - print_header('Defense work statistics') - self.defense_work.read_all_from_datastore() - self._show_status_for_work(self.defense_work) - self._export_work_errors( - self.defense_work, - os.path.join(self.results_dir, 'defense_errors.txt')) - - def cleanup_failed_attacks(self): - """Cleans up data of failed attacks.""" - print_header('Cleaning up failed attacks') - attacks_to_replace = {} - self.attack_work.read_all_from_datastore() - failed_submissions = set() - error_msg = set() - for k, v in iteritems(self.attack_work.work): - if v['error'] is not None: - attacks_to_replace[k] = dict(v) - failed_submissions.add(v['submission_id']) - error_msg.add(v['error']) - attacks_to_replace[k].update( - { - 'claimed_worker_id': None, - 'claimed_worker_start_time': None, - 'is_completed': False, - 'error': None, - 'elapsed_time': None, - }) - self.attack_work.replace_work(attacks_to_replace) - print('Affected submissions:') - print(' '.join(sorted(failed_submissions))) - print('Error messages:') - print(' '.join(sorted(error_msg))) - print('') - inp = input_str('Are you sure? (type "yes" without quotes to confirm): ') - if inp != 'yes': - return - self.attack_work.write_all_to_datastore() - print('Work cleaned up') - - def cleanup_attacks_with_zero_images(self): - """Cleans up data about attacks which generated zero images.""" - print_header('Cleaning up attacks which generated 0 images.') - # find out attack work to cleanup - self.adv_batches.init_from_datastore() - self.attack_work.read_all_from_datastore() - new_attack_work = {} - affected_adversarial_batches = set() - for work_id, work in iteritems(self.attack_work.work): - adv_batch_id = work['output_adversarial_batch_id'] - img_count_adv_batch = len(self.adv_batches.data[adv_batch_id]['images']) - if (img_count_adv_batch < 100) and (work['elapsed_time'] < 500): - affected_adversarial_batches.add(adv_batch_id) - new_attack_work[work_id] = dict(work) - new_attack_work[work_id].update( - { - 'claimed_worker_id': None, - 'claimed_worker_start_time': None, - 'is_completed': False, - 'error': None, - 'elapsed_time': None, - }) - self.attack_work.replace_work(new_attack_work) - print_header('Changes in attack works:') - print(self.attack_work) - # build list of classification batches - self.class_batches.init_from_datastore() - affected_class_batches = set() - for k, v in iteritems(self.class_batches.data): - if v['adversarial_batch_id'] in affected_adversarial_batches: - affected_class_batches.add(k) - # cleanup defense work on affected batches - self.defense_work.read_all_from_datastore() - new_defense_work = {} - for k, v in iteritems(self.defense_work.work): - if v['output_classification_batch_id'] in affected_class_batches: - new_defense_work[k] = dict(v) - new_defense_work[k].update( - { - 'claimed_worker_id': None, - 'claimed_worker_start_time': None, - 'is_completed': False, - 'error': None, - 'elapsed_time': None, - 'stat_correct': None, - 'stat_error': None, - 'stat_target_class': None, - 'stat_num_images': None, - }) - self.defense_work.replace_work(new_defense_work) - print_header('Changes in defense works:') - print(self.defense_work) - print('') - print('Total number of affected attack work: ', len(self.attack_work)) - print('Total number of affected defense work: ', len(self.defense_work)) - inp = input_str('Are you sure? (type "yes" without quotes to confirm): ') - if inp != 'yes': - return - print('Writing attacks work') - self.attack_work.write_all_to_datastore() - print('Writing defenses work') - self.defense_work.write_all_to_datastore() - print('Done!') - - def _cleanup_keys_with_confirmation(self, keys_to_delete): - """Asks confirmation and then deletes entries with keys. - - Args: - keys_to_delete: list of datastore keys for which entries should be deleted - """ - print('Round name: ', self.round_name) - print('Number of entities to be deleted: ', len(keys_to_delete)) - if not keys_to_delete: - return - if self.verbose: - print('Entities to delete:') - idx = 0 - prev_key_prefix = None - dots_printed_after_same_prefix = False - for k in keys_to_delete: - if idx >= 20: - print(' ...') - print(' ...') - break - key_prefix = (k.flat_path[0:1] - if k.flat_path[0] in [u'SubmissionType', u'WorkType'] - else k.flat_path[0]) - if prev_key_prefix == key_prefix: - if not dots_printed_after_same_prefix: - print(' ...') - dots_printed_after_same_prefix = True - else: - print(' ', k) - dots_printed_after_same_prefix = False - idx += 1 - prev_key_prefix = key_prefix - print() - inp = input_str('Are you sure? (type "yes" without quotes to confirm): ') - if inp != 'yes': - return - with self.datastore_client.no_transact_batch() as batch: - for k in keys_to_delete: - batch.delete(k) - print('Data deleted') - - def cleanup_defenses(self): - """Cleans up all data about defense work in current round.""" - print_header('CLEANING UP DEFENSES DATA') - work_ancestor_key = self.datastore_client.key('WorkType', 'AllDefenses') - keys_to_delete = [ - e.key - for e in self.datastore_client.query_fetch(kind=u'ClassificationBatch') - ] + [ - e.key - for e in self.datastore_client.query_fetch(kind=u'Work', - ancestor=work_ancestor_key) - ] - self._cleanup_keys_with_confirmation(keys_to_delete) - - def cleanup_datastore(self): - """Cleans up datastore and deletes all information about current round.""" - print_header('CLEANING UP ENTIRE DATASTORE') - kinds_to_delete = [u'Submission', u'SubmissionType', - u'DatasetImage', u'DatasetBatch', - u'AdversarialImage', u'AdversarialBatch', - u'Work', u'WorkType', - u'ClassificationBatch'] - keys_to_delete = [e.key for k in kinds_to_delete - for e in self.datastore_client.query_fetch(kind=k)] - self._cleanup_keys_with_confirmation(keys_to_delete) - - -USAGE = """Use one of the following commands to run master: - run_master.sh attack - run_master.sh defense - run_master.sh cleanup_defenses - run_master.sh results - run_master.sh status - run_master.sh cleanup_datastore -""" - - -def main(args): - """Main function which runs master.""" - if args.blacklisted_submissions: - logging.warning('BLACKLISTED SUBMISSIONS: %s', - args.blacklisted_submissions) - if args.limited_dataset: - logging.info('Using limited dataset: 3 batches * 10 images') - max_dataset_num_images = 30 - batch_size = 10 - else: - logging.info('Using full dataset. Batch size: %d', DEFAULT_BATCH_SIZE) - max_dataset_num_images = None - batch_size = DEFAULT_BATCH_SIZE - random.seed() - print('\nRound: {0}\n'.format(args.round_name)) - eval_master = EvaluationMaster( - storage_client=eval_lib.CompetitionStorageClient( - args.project_id, args.storage_bucket), - datastore_client=eval_lib.CompetitionDatastoreClient( - args.project_id, args.round_name), - round_name=args.round_name, - dataset_name=args.dataset_name, - blacklisted_submissions=args.blacklisted_submissions, - results_dir=args.results_dir, - num_defense_shards=args.num_defense_shards, - verbose=args.verbose, - batch_size=batch_size, - max_dataset_num_images=max_dataset_num_images) - if args.command == 'attack': - eval_master.prepare_attacks() - elif args.command == 'defense': - eval_master.prepare_defenses() - elif args.command == 'cleanup_defenses': - eval_master.cleanup_defenses() - elif args.command == 'results': - eval_master.compute_results() - elif args.command == 'status': - eval_master.show_status() - elif args.command == 'cleanup_datastore': - eval_master.cleanup_datastore() - elif args.command == 'cleanup_failed_attacks': - eval_master.cleanup_failed_attacks() - elif args.command == 'cleanup_attacks_with_zero_images': - eval_master.cleanup_attacks_with_zero_images() - else: - print('Invalid command: ', args.command) - print('') - print(USAGE) - - -if __name__ == '__main__': - parser = argparse.ArgumentParser( - description='Master which coordinates all workers.') - parser.add_argument('command', - help='Command to run. Possible commands include ' - '"attack", "defense", "scores", "status".') - parser.add_argument('--project_id', - required=True, - help='Your Google Cloud project ID.') - parser.add_argument('--storage_bucket', - required=True, - help='Cloud Storage bucket to store competition data.') - parser.add_argument('--round_name', - default='testing-round', - required=False, - help='Name of the round.') - parser.add_argument('--dataset_name', - default='dev', - required=False, - help='Which dataset to use, either dev or final.') - parser.add_argument('--blacklisted_submissions', - default='', - required=False, - help='Comma separated list of blacklisted submission ' - 'IDs.') - parser.add_argument('--results_dir', - required=True, - help='Directory where to save results.') - parser.add_argument('--num_defense_shards', - default=10, - required=False, - help='Number of defense shards') - parser.add_argument('--limited_dataset', dest='limited_dataset', - action='store_true') - parser.add_argument('--nolimited_dataset', dest='limited_dataset', - action='store_false') - parser.set_defaults(limited_dataset=False) - parser.add_argument('--verbose', dest='verbose', action='store_true') - parser.add_argument('--noverbose', dest='verbose', action='store_false') - parser.set_defaults(verbose=False) - parser.add_argument('--log_file', - default='', - required=False, - help='Location of the logfile.') - master_args = parser.parse_args() - logging_args = { - 'format': - '%(asctime)s - %(filename)s:%(lineno)d - %(levelname)s -- %(message)s', - 'level': logging.INFO, - 'datefmt': '%Y-%m-%d %H:%M:%S', - } - if master_args.log_file: - logging_args['filename'] = master_args.log_file - logging_args['filemode'] = 'w' - logging.basicConfig(**logging_args) - main(master_args) diff --git a/examples/nips17_adversarial_competition/eval_infra/code/worker.py b/examples/nips17_adversarial_competition/eval_infra/code/worker.py deleted file mode 100644 index 23bc1a1a3..000000000 --- a/examples/nips17_adversarial_competition/eval_infra/code/worker.py +++ /dev/null @@ -1,977 +0,0 @@ -"""Worker which runs all computations on Cloud VMs. - -Evaluation of competition is split into work pieces. One work piece is a -either evaluation of an attack on a batch of images or evaluation of a -defense on a batch of adversarial images. -All pieces of attack work are independent from each other and could be run -in parallel. Same for pieces of defense work - they are independent from each -other and could be run in parallel. But defense work could be run only after -all attack work is completed. - -Worker first runs all attack pieces, by querying next piece of undone work -and running it. After all attack pieces are done, worker runs all defense pieces -in a similar way. - -Before workers could be started, datastore has to be populated by master -with description of work to be done. See master.py for details. - -NOTE: Worker is designed to run on linux machine with NVidia docker -installed. Worker generally needs administrative privilege to run properly. -Also worker relies on very specific directory structure created in home -directory. That's why it's highly recommended to run worker only in VM. -""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from builtins import int # long in python 2 - -import argparse -import json -import logging -import os -import random -import shutil -import subprocess -import time -import uuid - -from six import iteritems - -import eval_lib - -from cleverhans.utils import shell_call - - -# Sleep time while waiting for next available piece of work -SLEEP_TIME = 30 -SLEEP_TIME_SHORT = 10 - -# Time limit to run one pice of work -SUBMISSION_TIME_LIMIT = 500 - -# Set of local temporary directories and files -LOCAL_EVAL_ROOT_DIR = os.path.expanduser('~/competition_eval') -LOCAL_DATASET_DIR = os.path.expanduser('~/competition_eval/dataset_images') -LOCAL_SUBMISSIONS_DIR = os.path.expanduser('~/competition_eval/submissions') -LOCAL_INPUT_DIR = os.path.expanduser('~/competition_eval/input') -LOCAL_OUTPUT_DIR = os.path.expanduser('~/competition_eval/output') -LOCAL_PROCESSED_OUTPUT_DIR = os.path.expanduser( - '~/competition_eval/processed_output') -LOCAL_ZIPPED_OUTPUT_DIR = os.path.expanduser( - '~/competition_eval/zipped_output') -LOCAL_DATASET_METADATA_FILE = os.path.expanduser( - '~/competition_eval/dataset_meta.csv') -LOCAL_DATASET_COPY = os.path.expanduser('~/competition_data/dataset') - -# Types of submissions -TYPE_TARGETED = 'targeted' -TYPE_NONTARGETED = 'nontargeted' -TYPE_DEFENSE = 'defense' - -# Extraction commands for various types of archive -EXTRACT_COMMAND = { - '.zip': ['unzip', '${src}', '-d', '${dst}'], - '.tar': ['tar', 'xvf', '${src}', '-C', '${dst}'], - '.tar.gz': ['tar', 'xvzf', '${src}', '-C', '${dst}'], -} - -# Docker binary to use -DOCKER_BINARY = 'docker' -DOCKER_NVIDIA_RUNTIME = '--runtime=nvidia' - -# Names of relevant fields in submission metadata file -METADATA_CONTAINER = 'container_gpu' -METADATA_ENTRY_POINT = 'entry_point' -METADATA_TYPE = 'type' - -# Mapping from submission type in metadata to submission type used in worker -METADATA_JSON_TYPE_TO_TYPE = { - 'attack': TYPE_NONTARGETED, - 'targeted_attack': TYPE_TARGETED, - 'defense': TYPE_DEFENSE, -} - - -def make_directory_writable(dirname): - """Makes directory readable and writable by everybody. - - If you run something inside Docker container and it writes files, then - these files will be written as root user with restricted permissions. - So to be able to read/modify these files outside of Docker you have to change - permissions to be world readable and writable. - - Args: - dirname: name of the directory - - Returns: - True if operation was successfull - """ - shell_call(['docker', 'run', '-v', - '{0}:/output_dir'.format(dirname), - 'busybox:1.27.2', - 'chmod', '-R', 'a+rwx', '/output_dir']) - - -def sudo_remove_dirtree(dir_name): - """Removes directory tree as a superuser. - - Args: - dir_name: name of the directory to remove. - - This function is necessary to cleanup directories created from inside a - Docker, since they usually written as a root, thus have to be removed as a - root. - """ - try: - subprocess.check_output(['sudo', 'rm', '-rf', dir_name]) - except subprocess.CalledProcessError as e: - raise WorkerError('Can''t remove directory {0}'.format(dir_name), e) - - -class WorkerError(Exception): - """Error which happen during evaluation of submission. - - To simplify error handling, worker only raises this type of exception. - Exceptions of different types raised by other modules encapsulated - into WorkerError by the worker. - """ - - def __init__(self, message, exc=None): - """Initializes WorkerError. - - Args: - message: error message - exc: optional underlying exception. - """ - super(WorkerError, self).__init__() - self.msg = message - self.exc = exc - - def __str__(self): - """Returns human readable string representation of the exception.""" - if self.exc: - return '{0}\nUnderlying exception:\n{1}'.format(self.msg, self.exc) - else: - return self.msg - - -def get_id_of_running_docker(container_name): - """Returns ID of running docker container.""" - return shell_call([DOCKER_BINARY, - 'ps', - '-q', - '--filter=name={}'.format(container_name)]).strip() - - -def is_docker_still_running(container_name): - """Returns whether given Docker container is still running.""" - return bool(get_id_of_running_docker(container_name)) - - -def kill_docker_container(container_name): - """Kills given docker container.""" - docker_id = get_id_of_running_docker(container_name) - shell_call([DOCKER_BINARY, 'stop', docker_id]) - - -class ExecutableSubmission(object): - """Base class which is used to run submissions.""" - - def __init__(self, submission_id, submissions, storage_bucket): - """Initializes ExecutableSubmission. - - Args: - submission_id: ID of the submissions - submissions: instance of CompetitionSubmissions with all submissions - storage_bucket: storage bucket where all submissions are stored - - Raises: - WorkerError: if submission was not found - """ - self.submission_id = submission_id - self.storage_bucket = storage_bucket - self.type = None - self.submission = None - if submission_id in submissions.attacks: - self.type = TYPE_NONTARGETED - self.submission = submissions.attacks[submission_id] - elif submission_id in submissions.targeted_attacks: - self.type = TYPE_TARGETED - self.submission = submissions.targeted_attacks[submission_id] - elif submission_id in submissions.defenses: - self.type = TYPE_DEFENSE - self.submission = submissions.defenses[submission_id] - else: - raise WorkerError( - 'Submission with ID "{0}" not found'.format(submission_id)) - self.submission_dir = None - self.extracted_submission_dir = None - - def download(self): - """Method which downloads submission to local directory.""" - # Structure of the download directory: - # submission_dir=LOCAL_SUBMISSIONS_DIR/submission_id - # submission_dir/s.ext <-- archived submission - # submission_dir/extracted <-- extracted submission - - # Check whether submission is already there - if self.extracted_submission_dir: - return - self.submission_dir = os.path.join(LOCAL_SUBMISSIONS_DIR, - self.submission_id) - if (os.path.isdir(self.submission_dir) - and os.path.isdir(os.path.join(self.submission_dir, 'extracted'))): - # submission already there, just re-read metadata - self.extracted_submission_dir = os.path.join(self.submission_dir, - 'extracted') - with open(os.path.join(self.extracted_submission_dir, 'metadata.json'), - 'r') as f: - meta_json = json.load(f) - self.container_name = str(meta_json[METADATA_CONTAINER]) - self.entry_point = str(meta_json[METADATA_ENTRY_POINT]) - return - # figure out submission location in the Cloud and determine extractor - submission_cloud_path = os.path.join('gs://', self.storage_bucket, - self.submission.path) - extract_command_tmpl = None - extension = None - for k, v in iteritems(EXTRACT_COMMAND): - if submission_cloud_path.endswith(k): - extension = k - extract_command_tmpl = v - break - if not extract_command_tmpl: - raise WorkerError('Unsupported submission extension') - # download archive - try: - os.makedirs(self.submission_dir) - tmp_extract_dir = os.path.join(self.submission_dir, 'tmp') - os.makedirs(tmp_extract_dir) - download_path = os.path.join(self.submission_dir, 's' + extension) - try: - logging.info('Downloading submission from %s to %s', - submission_cloud_path, download_path) - shell_call(['gsutil', 'cp', submission_cloud_path, download_path]) - except subprocess.CalledProcessError as e: - raise WorkerError('Can''t copy submission locally', e) - # extract archive - try: - shell_call(extract_command_tmpl, - src=download_path, dst=tmp_extract_dir) - except subprocess.CalledProcessError as e: - # proceed even if extraction returned non zero error code, - # sometimes it's just warning - logging.warning('Submission extraction returned non-zero error code. ' - 'It may be just a warning, continuing execution. ' - 'Error: %s', e) - try: - make_directory_writable(tmp_extract_dir) - except subprocess.CalledProcessError as e: - raise WorkerError('Can''t make submission directory writable', e) - # determine root of the submission - tmp_root_dir = tmp_extract_dir - root_dir_content = [d for d in os.listdir(tmp_root_dir) - if d != '__MACOSX'] - if (len(root_dir_content) == 1 - and os.path.isdir(os.path.join(tmp_root_dir, root_dir_content[0]))): - tmp_root_dir = os.path.join(tmp_root_dir, root_dir_content[0]) - # move files to extract subdirectory - self.extracted_submission_dir = os.path.join(self.submission_dir, - 'extracted') - try: - shell_call(['mv', os.path.join(tmp_root_dir), - self.extracted_submission_dir]) - except subprocess.CalledProcessError as e: - raise WorkerError('Can''t move submission files', e) - # read metadata file - try: - with open(os.path.join(self.extracted_submission_dir, 'metadata.json'), - 'r') as f: - meta_json = json.load(f) - except IOError as e: - raise WorkerError( - 'Can''t read metadata.json for submission "{0}"'.format( - self.submission_id), - e) - try: - self.container_name = str(meta_json[METADATA_CONTAINER]) - self.entry_point = str(meta_json[METADATA_ENTRY_POINT]) - type_from_meta = METADATA_JSON_TYPE_TO_TYPE[meta_json[METADATA_TYPE]] - except KeyError as e: - raise WorkerError('Invalid metadata.json file', e) - if type_from_meta != self.type: - raise WorkerError('Inconsistent submission type in metadata: ' - + type_from_meta + ' vs ' + self.type) - except WorkerError as e: - self.extracted_submission_dir = None - sudo_remove_dirtree(self.submission_dir) - raise - - def temp_copy_extracted_submission(self): - """Creates a temporary copy of extracted submission. - - When executed, submission is allowed to modify it's own directory. So - to ensure that submission does not pass any data between runs, new - copy of the submission is made before each run. After a run temporary copy - of submission is deleted. - - Returns: - directory where temporary copy is located - """ - tmp_copy_dir = os.path.join(self.submission_dir, 'tmp_copy') - shell_call(['cp', '-R', os.path.join(self.extracted_submission_dir), - tmp_copy_dir]) - return tmp_copy_dir - - def run_without_time_limit(self, cmd): - """Runs docker command without time limit. - - Args: - cmd: list with the command line arguments which are passed to docker - binary - - Returns: - how long it took to run submission in seconds - - Raises: - WorkerError: if error occurred during execution of the submission - """ - cmd = [DOCKER_BINARY, 'run', DOCKER_NVIDIA_RUNTIME] + cmd - logging.info('Docker command: %s', ' '.join(cmd)) - start_time = time.time() - retval = subprocess.call(cmd) - elapsed_time_sec = int(time.time() - start_time) - logging.info('Elapsed time of attack: %d', elapsed_time_sec) - logging.info('Docker retval: %d', retval) - if retval != 0: - logging.warning('Docker returned non-zero retval: %d', retval) - raise WorkerError('Docker returned non-zero retval ' + str(retval)) - return elapsed_time_sec - - def run_with_time_limit(self, cmd, time_limit=SUBMISSION_TIME_LIMIT): - """Runs docker command and enforces time limit. - - Args: - cmd: list with the command line arguments which are passed to docker - binary after run - time_limit: time limit, in seconds. Negative value means no limit. - - Returns: - how long it took to run submission in seconds - - Raises: - WorkerError: if error occurred during execution of the submission - """ - if time_limit < 0: - return self.run_without_time_limit(cmd) - container_name = str(uuid.uuid4()) - cmd = [DOCKER_BINARY, 'run', DOCKER_NVIDIA_RUNTIME, - '--detach', '--name', container_name] + cmd - logging.info('Docker command: %s', ' '.join(cmd)) - logging.info('Time limit %d seconds', time_limit) - retval = subprocess.call(cmd) - start_time = time.time() - elapsed_time_sec = 0 - while is_docker_still_running(container_name): - elapsed_time_sec = int(time.time() - start_time) - if elapsed_time_sec < time_limit: - time.sleep(1) - else: - kill_docker_container(container_name) - logging.warning('Submission was killed because run out of time') - logging.info('Elapsed time of submission: %d', elapsed_time_sec) - logging.info('Docker retval: %d', retval) - if retval != 0: - logging.warning('Docker returned non-zero retval: %d', retval) - raise WorkerError('Docker returned non-zero retval ' + str(retval)) - return elapsed_time_sec - - -class AttackSubmission(ExecutableSubmission): - """Class to run attack submissions.""" - - def __init__(self, submission_id, submissions, storage_bucket): - """Initializes AttackSubmission. - - Args: - submission_id: ID of the submission - submissions: instance of CompetitionSubmissions with all submissions - storage_bucket: storage bucket where all submissions are stored - - Raises: - WorkerError: if submission has incorrect type - """ - super(AttackSubmission, self).__init__(submission_id, submissions, - storage_bucket) - if (self.type != TYPE_TARGETED) and (self.type != TYPE_NONTARGETED): - raise WorkerError('Incorrect attack type for submission "{0}"'.format( - submission_id)) - - def run(self, input_dir, output_dir, epsilon): - """Runs attack inside Docker. - - Args: - input_dir: directory with input (dataset). - output_dir: directory where output (adversarial images) should be written. - epsilon: maximum allowed size of adversarial perturbation, - should be in range [0, 255]. - - Returns: - how long it took to run submission in seconds - """ - logging.info('Running attack %s', self.submission_id) - tmp_run_dir = self.temp_copy_extracted_submission() - cmd = ['--network=none', - '-m=24g', - '--cpus=3.75', - '-v', '{0}:/input_images:ro'.format(input_dir), - '-v', '{0}:/output_images'.format(output_dir), - '-v', '{0}:/code'.format(tmp_run_dir), - '-w', '/code', - self.container_name, - './' + self.entry_point, - '/input_images', - '/output_images', - str(epsilon)] - elapsed_time_sec = self.run_with_time_limit(cmd) - sudo_remove_dirtree(tmp_run_dir) - return elapsed_time_sec - - -class DefenseSubmission(ExecutableSubmission): - """Helper class to run one defense submission.""" - - def __init__(self, submission_id, submissions, storage_bucket): - """Initializes DefenseSubmission. - - Args: - submission_id: ID of the submission - submissions: instance of CompetitionSubmissions with all submissions - storage_bucket: storage bucket where all submissions are stored - - Raises: - WorkerError: if submission has incorrect type - """ - super(DefenseSubmission, self).__init__(submission_id, submissions, - storage_bucket) - if self.type != TYPE_DEFENSE: - raise WorkerError('Incorrect defense type for submission "{0}"'.format( - submission_id)) - - def run(self, input_dir, output_file_path): - """Runs defense inside Docker. - - Args: - input_dir: directory with input (adversarial images). - output_file_path: path of the output file. - - Returns: - how long it took to run submission in seconds - """ - logging.info('Running defense %s', self.submission_id) - tmp_run_dir = self.temp_copy_extracted_submission() - output_dir = os.path.dirname(output_file_path) - output_filename = os.path.basename(output_file_path) - cmd = ['--network=none', - '-m=24g', - '--cpus=3.75', - '-v', '{0}:/input_images:ro'.format(input_dir), - '-v', '{0}:/output_data'.format(output_dir), - '-v', '{0}:/code'.format(tmp_run_dir), - '-w', '/code', - self.container_name, - './' + self.entry_point, - '/input_images', - '/output_data/' + output_filename] - elapsed_time_sec = self.run_with_time_limit(cmd) - sudo_remove_dirtree(tmp_run_dir) - return elapsed_time_sec - - -class EvaluationWorker(object): - """Class which encapsulate logit of the worker. - - Main entry point of this class is EvaluationWorker.run_work method which - performs cleanup of temporary directories, then runs - EvaluationWorker.run_attacks and EvaluationWorker.run_defenses - """ - - def __init__(self, worker_id, storage_client, datastore_client, - storage_bucket, round_name, dataset_name, - blacklisted_submissions='', num_defense_shards=None): - """Initializes EvaluationWorker. - - Args: - worker_id: ID of the worker - storage_client: instance of eval_lib.CompetitionStorageClient - datastore_client: instance of eval_lib.CompetitionDatastoreClient - storage_bucket: name of the Google Cloud Storage bucket where all - competition data is stored - round_name: name of the competition round - dataset_name: name of the dataset to use, typically 'dev' of 'final' - blacklisted_submissions: optional list of blacklisted submissions which - are excluded from evaluation - num_defense_shards: optional number of shards to use for evaluation of - defenses - """ - self.worker_id = int(worker_id) - self.storage_client = storage_client - self.datastore_client = datastore_client - self.storage_bucket = storage_bucket - self.round_name = round_name - self.dataset_name = dataset_name - self.blacklisted_submissions = [s.strip() - for s in blacklisted_submissions.split(',')] - if num_defense_shards: - self.num_defense_shards = int(num_defense_shards) - else: - self.num_defense_shards = None - logging.info('Number of defense shards: %s', str(self.num_defense_shards)) - # init client classes - self.submissions = eval_lib.CompetitionSubmissions( - datastore_client=self.datastore_client, - storage_client=self.storage_client, - round_name=self.round_name) - self.dataset_batches = eval_lib.DatasetBatches( - datastore_client=self.datastore_client, - storage_client=self.storage_client, - dataset_name=self.dataset_name) - self.adv_batches = eval_lib.AversarialBatches( - datastore_client=self.datastore_client) - self.attack_work = eval_lib.AttackWorkPieces( - datastore_client=self.datastore_client) - self.defense_work = eval_lib.DefenseWorkPieces( - datastore_client=self.datastore_client) - self.class_batches = eval_lib.ClassificationBatches( - datastore_client=self.datastore_client, - storage_client=self.storage_client, - round_name=self.round_name) - # whether data was initialized - self.attacks_data_initialized = False - self.defenses_data_initialized = False - # dataset metadata - self.dataset_meta = None - - def read_dataset_metadata(self): - """Read `dataset_meta` field from bucket""" - if self.dataset_meta: - return - shell_call(['gsutil', 'cp', - 'gs://' + self.storage_client.bucket_name + '/' - + 'dataset/' + self.dataset_name + '_dataset.csv', - LOCAL_DATASET_METADATA_FILE]) - with open(LOCAL_DATASET_METADATA_FILE, 'r') as f: - self.dataset_meta = eval_lib.DatasetMetadata(f) - - def fetch_attacks_data(self): - """Initializes data necessary to execute attacks. - - This method could be called multiple times, only first call does - initialization, subsequent calls are noop. - """ - if self.attacks_data_initialized: - return - # init data from datastore - self.submissions.init_from_datastore() - self.dataset_batches.init_from_datastore() - self.adv_batches.init_from_datastore() - # copy dataset locally - if not os.path.exists(LOCAL_DATASET_DIR): - os.makedirs(LOCAL_DATASET_DIR) - eval_lib.download_dataset(self.storage_client, self.dataset_batches, - LOCAL_DATASET_DIR, - os.path.join(LOCAL_DATASET_COPY, - self.dataset_name, 'images')) - # download dataset metadata - self.read_dataset_metadata() - # mark as initialized - self.attacks_data_initialized = True - - def run_attack_work(self, work_id): - """Runs one attack work. - - Args: - work_id: ID of the piece of work to run - - Returns: - elapsed_time_sec, submission_id - elapsed time and id of the submission - - Raises: - WorkerError: if error occurred during execution. - """ - adv_batch_id = ( - self.attack_work.work[work_id]['output_adversarial_batch_id']) - adv_batch = self.adv_batches[adv_batch_id] - dataset_batch_id = adv_batch['dataset_batch_id'] - submission_id = adv_batch['submission_id'] - epsilon = self.dataset_batches[dataset_batch_id]['epsilon'] - logging.info('Attack work piece: ' - 'dataset_batch_id="%s" submission_id="%s" ' - 'epsilon=%d', dataset_batch_id, submission_id, epsilon) - if submission_id in self.blacklisted_submissions: - raise WorkerError('Blacklisted submission') - # get attack - attack = AttackSubmission(submission_id, self.submissions, - self.storage_bucket) - attack.download() - # prepare input - input_dir = os.path.join(LOCAL_DATASET_DIR, dataset_batch_id) - if attack.type == TYPE_TARGETED: - # prepare file with target classes - target_class_filename = os.path.join(input_dir, 'target_class.csv') - self.dataset_meta.save_target_classes_for_batch(target_class_filename, - self.dataset_batches, - dataset_batch_id) - # prepare output directory - if os.path.exists(LOCAL_OUTPUT_DIR): - sudo_remove_dirtree(LOCAL_OUTPUT_DIR) - os.mkdir(LOCAL_OUTPUT_DIR) - if os.path.exists(LOCAL_PROCESSED_OUTPUT_DIR): - shutil.rmtree(LOCAL_PROCESSED_OUTPUT_DIR) - os.mkdir(LOCAL_PROCESSED_OUTPUT_DIR) - if os.path.exists(LOCAL_ZIPPED_OUTPUT_DIR): - shutil.rmtree(LOCAL_ZIPPED_OUTPUT_DIR) - os.mkdir(LOCAL_ZIPPED_OUTPUT_DIR) - # run attack - elapsed_time_sec = attack.run(input_dir, LOCAL_OUTPUT_DIR, epsilon) - if attack.type == TYPE_TARGETED: - # remove target class file - os.remove(target_class_filename) - # enforce epsilon and compute hashes - image_hashes = eval_lib.enforce_epsilon_and_compute_hash( - input_dir, LOCAL_OUTPUT_DIR, LOCAL_PROCESSED_OUTPUT_DIR, epsilon) - if not image_hashes: - logging.warning('No images saved by the attack.') - return elapsed_time_sec, submission_id - # write images back to datastore - # rename images and add information to adversarial batch - for clean_image_id, hash_val in iteritems(image_hashes): - # we will use concatenation of batch_id and image_id - # as adversarial image id and as a filename of adversarial images - adv_img_id = adv_batch_id + '_' + clean_image_id - # rename the image - os.rename( - os.path.join(LOCAL_PROCESSED_OUTPUT_DIR, clean_image_id + '.png'), - os.path.join(LOCAL_PROCESSED_OUTPUT_DIR, adv_img_id + '.png')) - # populate values which will be written to datastore - image_path = '{0}/adversarial_images/{1}/{1}.zip/{2}.png'.format( - self.round_name, adv_batch_id, adv_img_id) - # u'' + foo is a a python 2/3 compatible way of casting foo to unicode - adv_batch['images'][adv_img_id] = { - 'clean_image_id': u'' + str(clean_image_id), - 'image_path': u'' + str(image_path), - 'image_hash': u'' + str(hash_val), - } - # archive all images and copy to storage - zipped_images_filename = os.path.join(LOCAL_ZIPPED_OUTPUT_DIR, - adv_batch_id + '.zip') - try: - logging.debug('Compressing adversarial images to %s', - zipped_images_filename) - shell_call([ - 'zip', '-j', '-r', zipped_images_filename, - LOCAL_PROCESSED_OUTPUT_DIR]) - except subprocess.CalledProcessError as e: - raise WorkerError('Can''t make archive from adversarial iamges', e) - # upload archive to storage - dst_filename = '{0}/adversarial_images/{1}/{1}.zip'.format( - self.round_name, adv_batch_id) - logging.debug( - 'Copying archive with adversarial images to %s', dst_filename) - self.storage_client.new_blob(dst_filename).upload_from_filename( - zipped_images_filename) - # writing adv batch to datastore - logging.debug('Writing adversarial batch to datastore') - self.adv_batches.write_single_batch_images_to_datastore(adv_batch_id) - return elapsed_time_sec, submission_id - - def run_attacks(self): - """Method which evaluates all attack work. - - In a loop this method queries not completed attack work, picks one - attack work and runs it. - """ - logging.info('******** Start evaluation of attacks ********') - prev_submission_id = None - while True: - # wait until work is available - self.attack_work.read_all_from_datastore() - if not self.attack_work.work: - logging.info('Work is not populated, waiting...') - time.sleep(SLEEP_TIME) - continue - if self.attack_work.is_all_work_competed(): - logging.info('All attack work completed.') - break - # download all attacks data and dataset - self.fetch_attacks_data() - # pick piece of work - work_id = self.attack_work.try_pick_piece_of_work( - self.worker_id, submission_id=prev_submission_id) - if not work_id: - logging.info('Failed to pick work, waiting...') - time.sleep(SLEEP_TIME_SHORT) - continue - logging.info('Selected work_id: %s', work_id) - # execute work - try: - elapsed_time_sec, prev_submission_id = self.run_attack_work(work_id) - logging.info('Work %s is done', work_id) - # indicate that work is completed - is_work_update = self.attack_work.update_work_as_completed( - self.worker_id, work_id, - other_values={'elapsed_time': elapsed_time_sec}) - except WorkerError as e: - logging.info('Failed to run work:\n%s', str(e)) - is_work_update = self.attack_work.update_work_as_completed( - self.worker_id, work_id, error=str(e)) - if not is_work_update: - logging.warning('Can''t update work "%s" as completed by worker %d', - work_id, self.worker_id) - logging.info('******** Finished evaluation of attacks ********') - - def fetch_defense_data(self): - """Lazy initialization of data necessary to execute defenses.""" - if self.defenses_data_initialized: - return - logging.info('Fetching defense data from datastore') - # init data from datastore - self.submissions.init_from_datastore() - self.dataset_batches.init_from_datastore() - self.adv_batches.init_from_datastore() - # read dataset metadata - self.read_dataset_metadata() - # mark as initialized - self.defenses_data_initialized = True - - def run_defense_work(self, work_id): - """Runs one defense work. - - Args: - work_id: ID of the piece of work to run - - Returns: - elapsed_time_sec, submission_id - elapsed time and id of the submission - - Raises: - WorkerError: if error occurred during execution. - """ - class_batch_id = ( - self.defense_work.work[work_id]['output_classification_batch_id']) - class_batch = self.class_batches.read_batch_from_datastore(class_batch_id) - adversarial_batch_id = class_batch['adversarial_batch_id'] - submission_id = class_batch['submission_id'] - cloud_result_path = class_batch['result_path'] - logging.info('Defense work piece: ' - 'adversarial_batch_id="%s" submission_id="%s"', - adversarial_batch_id, submission_id) - if submission_id in self.blacklisted_submissions: - raise WorkerError('Blacklisted submission') - # get defense - defense = DefenseSubmission(submission_id, self.submissions, - self.storage_bucket) - defense.download() - # prepare input - copy adversarial batch locally - input_dir = os.path.join(LOCAL_INPUT_DIR, adversarial_batch_id) - if os.path.exists(input_dir): - sudo_remove_dirtree(input_dir) - os.makedirs(input_dir) - try: - shell_call([ - 'gsutil', '-m', 'cp', - # typical location of adv batch: - # testing-round/adversarial_images/ADVBATCH000/ - os.path.join('gs://', self.storage_bucket, self.round_name, - 'adversarial_images', adversarial_batch_id, '*'), - input_dir - ]) - adv_images_files = os.listdir(input_dir) - if (len(adv_images_files) == 1) and adv_images_files[0].endswith('.zip'): - logging.info('Adversarial batch is in zip archive %s', - adv_images_files[0]) - shell_call([ - 'unzip', os.path.join(input_dir, adv_images_files[0]), - '-d', input_dir - ]) - os.remove(os.path.join(input_dir, adv_images_files[0])) - adv_images_files = os.listdir(input_dir) - logging.info('%d adversarial images copied', len(adv_images_files)) - except (subprocess.CalledProcessError, IOError) as e: - raise WorkerError('Can''t copy adversarial batch locally', e) - # prepare output directory - if os.path.exists(LOCAL_OUTPUT_DIR): - sudo_remove_dirtree(LOCAL_OUTPUT_DIR) - os.mkdir(LOCAL_OUTPUT_DIR) - output_filname = os.path.join(LOCAL_OUTPUT_DIR, 'result.csv') - # run defense - elapsed_time_sec = defense.run(input_dir, output_filname) - # evaluate defense result - batch_result = eval_lib.analyze_one_classification_result( - storage_client=None, - file_path=output_filname, - adv_batch=self.adv_batches.data[adversarial_batch_id], - dataset_batches=self.dataset_batches, - dataset_meta=self.dataset_meta) - # copy result of the defense into storage - try: - shell_call([ - 'gsutil', 'cp', output_filname, - os.path.join('gs://', self.storage_bucket, cloud_result_path) - ]) - except subprocess.CalledProcessError as e: - raise WorkerError('Can''t result to Cloud Storage', e) - return elapsed_time_sec, submission_id, batch_result - - def run_defenses(self): - """Method which evaluates all defense work. - - In a loop this method queries not completed defense work, - picks one defense work and runs it. - """ - logging.info('******** Start evaluation of defenses ********') - prev_submission_id = None - need_reload_work = True - while True: - # wait until work is available - if need_reload_work: - if self.num_defense_shards: - shard_with_work = self.defense_work.read_undone_from_datastore( - shard_id=(self.worker_id % self.num_defense_shards), - num_shards=self.num_defense_shards) - else: - shard_with_work = self.defense_work.read_undone_from_datastore() - logging.info('Loaded %d records of undone work from shard %s', - len(self.defense_work), str(shard_with_work)) - if not self.defense_work.work: - logging.info('Work is not populated, waiting...') - time.sleep(SLEEP_TIME) - continue - if self.defense_work.is_all_work_competed(): - logging.info('All defense work completed.') - break - # download all defense data and dataset - self.fetch_defense_data() - need_reload_work = False - # pick piece of work - work_id = self.defense_work.try_pick_piece_of_work( - self.worker_id, submission_id=prev_submission_id) - if not work_id: - need_reload_work = True - logging.info('Failed to pick work, waiting...') - time.sleep(SLEEP_TIME_SHORT) - continue - logging.info('Selected work_id: %s', work_id) - # execute work - try: - elapsed_time_sec, prev_submission_id, batch_result = ( - self.run_defense_work(work_id)) - logging.info('Work %s is done', work_id) - # indicate that work is completed - is_work_update = self.defense_work.update_work_as_completed( - self.worker_id, work_id, - other_values={'elapsed_time': elapsed_time_sec, - 'stat_correct': batch_result[0], - 'stat_error': batch_result[1], - 'stat_target_class': batch_result[2], - 'stat_num_images': batch_result[3]}) - except WorkerError as e: - logging.info('Failed to run work:\n%s', str(e)) - if str(e).startswith('Docker returned non-zero retval'): - logging.info('Running nvidia-docker to ensure that GPU works') - shell_call(['nvidia-docker', 'run', '--rm', 'nvidia/cuda', - 'nvidia-smi']) - is_work_update = self.defense_work.update_work_as_completed( - self.worker_id, work_id, error=str(e)) - if not is_work_update: - logging.warning('Can''t update work "%s" as completed by worker %d', - work_id, self.worker_id) - need_reload_work = True - logging.info('******** Finished evaluation of defenses ********') - - def run_work(self): - """Run attacks and defenses""" - if os.path.exists(LOCAL_EVAL_ROOT_DIR): - sudo_remove_dirtree(LOCAL_EVAL_ROOT_DIR) - self.run_attacks() - self.run_defenses() - - -def main(args): - """Main function which runs worker.""" - title = '## Starting evaluation of round {0} ##'.format(args.round_name) - logging.info('\n' - + '#' * len(title) + '\n' - + '#' * len(title) + '\n' - + '##' + ' ' * (len(title)-2) + '##' + '\n' - + title + '\n' - + '#' * len(title) + '\n' - + '#' * len(title) + '\n' - + '##' + ' ' * (len(title)-2) + '##' + '\n') - if args.blacklisted_submissions: - logging.warning('BLACKLISTED SUBMISSIONS: %s', - args.blacklisted_submissions) - random.seed() - logging.info('Running nvidia-docker to ensure that GPU works') - shell_call(['docker', 'run', '--runtime=nvidia', - '--rm', 'nvidia/cuda', 'nvidia-smi']) - eval_worker = EvaluationWorker( - worker_id=args.worker_id, - storage_client=eval_lib.CompetitionStorageClient( - args.project_id, args.storage_bucket), - datastore_client=eval_lib.CompetitionDatastoreClient( - args.project_id, args.round_name), - storage_bucket=args.storage_bucket, - round_name=args.round_name, - dataset_name=args.dataset_name, - blacklisted_submissions=args.blacklisted_submissions, - num_defense_shards=args.num_defense_shards) - eval_worker.run_work() - - -if __name__ == '__main__': - parser = argparse.ArgumentParser( - description='Worker which executes work.') - parser.add_argument('--worker_id', - required=True, - type=int, - help='Numerical ID of the worker.') - parser.add_argument('--project_id', - required=True, - help='Your Google Cloud project ID.') - parser.add_argument('--storage_bucket', - required=True, - help='Cloud Storage bucket to store competition data.') - parser.add_argument('--round_name', - default='testing-round', - required=False, - help='Name of the round.') - parser.add_argument('--dataset_name', - default='dev', - required=False, - help='Which dataset to use, either dev or final.') - parser.add_argument('--blacklisted_submissions', - default='', - required=False, - help='Comma separated list of blacklisted submission ' - 'IDs.') - parser.add_argument('--num_defense_shards', - default=10, - required=False, - help='Number of defense shards') - parser.add_argument('--log_file', - default='', - required=False, - help='Location of the logfile.') - worker_args = parser.parse_args() - logging_args = { - 'format': - '%(asctime)s - %(filename)s:%(lineno)d - %(levelname)s -- %(message)s', - 'level': logging.INFO, - 'datefmt': '%Y-%m-%d %H:%M:%S', - } - if worker_args.log_file: - logging_args['filename'] = worker_args.log_file - logging_args['filemode'] = 'a' - logging.basicConfig(**logging_args) - main(worker_args) diff --git a/examples/nips17_adversarial_competition/eval_infra/validation_tool/validate_and_copy_submissions.py b/examples/nips17_adversarial_competition/eval_infra/validation_tool/validate_and_copy_submissions.py deleted file mode 100644 index be078b623..000000000 --- a/examples/nips17_adversarial_competition/eval_infra/validation_tool/validate_and_copy_submissions.py +++ /dev/null @@ -1,280 +0,0 @@ -r"""Tool to validate all submission and copy them to proper location. - -Usage: - python validate_and_copy_submissions.py \ - --source_dir=SOURCE \ - --target_dir=TARGET \ - [--containers_file=CONTAINER_FILE] \ - [--log_file=LOG_FILE] \ - [--nouse_gpu] \ - [--nocopy] - -Where: - SOURCE - Google Cloud Storage directory with all submissions to verify. - Submissions in the source directory could be structured any way, this tool - will go though all subdirectories and look for zip, tar and tar.gz archives. - TARGET - Target directory in Google Cloud Storage, typically it should be - gs://${GOOGLE_CLOUD_STORAGE_BUCKET}/${ROUND_NAME}/submissions - CONTAINER_FILE - optional name of the file where to save list of all Docker - containers used by all submissions. - LOG_FILE - optional filename of the logfile. - --nouse_gpu - if argument is provided then submission will be run on CPU, - otherwise will be run on GPU. - --nocopy - if argument is provided then submissions will be validated, - but no copy will be performed. - -""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import argparse -import csv -import logging -import os -import random -import shutil -import subprocess -import tempfile -from six import iteritems -import validate_submission_lib - - -ALLOWED_EXTENSIONS = ['.zip', '.tar', '.tar.gz'] - -TYPE_ATTACK = 'attack' -TYPE_TARGETED_ATTACK = 'targeted_attack' -TYPE_DEFENSE = 'defense' - -TYPE_TO_DIR = { - 'attack': 'nontargeted', - 'targeted_attack': 'targeted', - 'defense': 'defense', -} - - -class ValidationStats(object): - """Class which stores statistics about validation of the submissions.""" - - def __init__(self): - # dictionary mapping submission_type to tuple (num_success, num_fail) - self.stats = {} - - def _update_stat(self, submission_type, increase_success, increase_fail): - """Common method to update submission statistics.""" - stat = self.stats.get(submission_type, (0, 0)) - stat = (stat[0] + increase_success, stat[1] + increase_fail) - self.stats[submission_type] = stat - - def add_success(self, submission_type): - """Add one successfull submission of given type.""" - self._update_stat(submission_type, 1, 0) - - def add_failure(self, submission_type='unknown'): - """Add one failed submission of given type.""" - self._update_stat(submission_type, 0, 1) - - def log_stats(self): - """Print statistics into log.""" - logging.info('Validation statistics: ') - for k, v in iteritems(self.stats): - logging.info('%s - %d valid out of %d total submissions', - k, v[0], v[0] + v[1]) - - -class SubmissionValidator(object): - """Helper class which performs validation of all submissions.""" - - def __init__(self, source_dir, target_dir, temp_dir, do_copy, use_gpu, - containers_file=None): - """Initializes SubmissionValidator. - - Args: - source_dir: source Google Cloud Storage directory with all submissions - target_dir: target Google Cloud Storage directory where to copy - submissions - temp_dir: local temporary directory - do_copy: if True then validate and copy submissions, if False then only - validate - use_gpu: if True then use GPU for validation, otherwise use CPU - containers_file: optional name of the local text file where list of - Docker containes of all submissions will be saved. - """ - self.source_dir = source_dir - self.target_dir = target_dir - self.do_copy = do_copy - self.containers_file = containers_file - self.list_of_containers = set() - self.local_id_to_path_mapping_file = os.path.join(temp_dir, - 'id_to_path_mapping.csv') - self.validate_dir = os.path.join(temp_dir, 'validate') - self.base_validator = validate_submission_lib.SubmissionValidator( - self.validate_dir, use_gpu) - self.stats = ValidationStats() - self.download_dir = os.path.join(temp_dir, 'download') - self.cur_submission_idx = 0 - self.id_to_path_mapping = {} - - def copy_submission_locally(self, cloud_path): - """Copies submission from Google Cloud Storage to local directory. - - Args: - cloud_path: path of the submission in Google Cloud Storage - - Returns: - name of the local file where submission is copied to - """ - local_path = os.path.join(self.download_dir, os.path.basename(cloud_path)) - cmd = ['gsutil', 'cp', cloud_path, local_path] - if subprocess.call(cmd) != 0: - logging.error('Can\'t copy submission locally') - return None - return local_path - - def copy_submission_to_destination(self, src_filename, dst_subdir, - submission_id): - """Copies submission to target directory. - - Args: - src_filename: source filename of the submission - dst_subdir: subdirectory of the target directory where submission should - be copied to - submission_id: ID of the submission, will be used as a new - submission filename (before extension) - """ - - extension = [e for e in ALLOWED_EXTENSIONS if src_filename.endswith(e)] - if len(extension) != 1: - logging.error('Invalid submission extension: %s', src_filename) - return - dst_filename = os.path.join(self.target_dir, dst_subdir, - submission_id + extension[0]) - cmd = ['gsutil', 'cp', src_filename, dst_filename] - if subprocess.call(cmd) != 0: - logging.error('Can\'t copy submission to destination') - else: - logging.info('Submission copied to: %s', dst_filename) - - def validate_and_copy_one_submission(self, submission_path): - """Validates one submission and copies it to target directory. - - Args: - submission_path: path in Google Cloud Storage of the submission file - """ - if os.path.exists(self.download_dir): - shutil.rmtree(self.download_dir) - os.makedirs(self.download_dir) - if os.path.exists(self.validate_dir): - shutil.rmtree(self.validate_dir) - os.makedirs(self.validate_dir) - logging.info('\n' + ('#' * 80) + '\n# Processing submission: %s\n' - + '#' * 80, submission_path) - local_path = self.copy_submission_locally(submission_path) - metadata = self.base_validator.validate_submission(local_path) - if not metadata: - logging.error('Submission "%s" is INVALID', submission_path) - self.stats.add_failure() - return - submission_type = metadata['type'] - container_name = metadata['container_gpu'] - logging.info('Submission "%s" is VALID', submission_path) - self.list_of_containers.add(container_name) - self.stats.add_success(submission_type) - if self.do_copy: - submission_id = '{0:04}'.format(self.cur_submission_idx) - self.cur_submission_idx += 1 - self.copy_submission_to_destination(submission_path, - TYPE_TO_DIR[submission_type], - submission_id) - self.id_to_path_mapping[submission_id] = submission_path - - def save_id_to_path_mapping(self): - """Saves mapping from submission IDs to original filenames. - - This mapping is saved as CSV file into target directory. - """ - if not self.id_to_path_mapping: - return - with open(self.local_id_to_path_mapping_file, 'w') as f: - writer = csv.writer(f) - writer.writerow(['id', 'path']) - for k, v in sorted(iteritems(self.id_to_path_mapping)): - writer.writerow([k, v]) - cmd = ['gsutil', 'cp', self.local_id_to_path_mapping_file, - os.path.join(self.target_dir, 'id_to_path_mapping.csv')] - if subprocess.call(cmd) != 0: - logging.error('Can\'t copy id_to_path_mapping.csv to target directory') - - def run(self): - """Runs validation of all submissions.""" - cmd = ['gsutil', 'ls', os.path.join(self.source_dir, '**')] - try: - files_list = subprocess.check_output(cmd).split('\n') - except subprocess.CalledProcessError: - logging.error('Can''t read source directory') - all_submissions = [ - s for s in files_list - if s.endswith('.zip') or s.endswith('.tar') or s.endswith('.tar.gz') - ] - for submission_path in all_submissions: - self.validate_and_copy_one_submission(submission_path) - self.stats.log_stats() - self.save_id_to_path_mapping() - if self.containers_file: - with open(self.containers_file, 'w') as f: - f.write('\n'.join(sorted(self.list_of_containers))) - - -def main(args): - """Validate all submissions and copy them into place""" - random.seed() - temp_dir = tempfile.mkdtemp() - logging.info('Created temporary directory: %s', temp_dir) - validator = SubmissionValidator( - source_dir=args.source_dir, - target_dir=args.target_dir, - temp_dir=temp_dir, - do_copy=args.copy, - use_gpu=args.use_gpu, - containers_file=args.containers_file) - validator.run() - logging.info('Deleting temporary directory: %s', temp_dir) - subprocess.call(['rm', '-rf', temp_dir]) - - -if __name__ == '__main__': - parser = argparse.ArgumentParser( - description='Submission validation script.') - parser.add_argument('--source_dir', - required=True, - help='Source directory.') - parser.add_argument('--target_dir', - required=True, - help='Target directory.') - parser.add_argument('--log_file', - default='', - required=False, - help='Location of the logfile.') - parser.add_argument('--containers_file', - default='', - required=False, - help='Local file with list of containers.') - parser.add_argument('--copy', dest='copy', action='store_true') - parser.add_argument('--nocopy', dest='copy', action='store_false') - parser.set_defaults(copy=True) - parser.add_argument('--use_gpu', dest='use_gpu', action='store_true') - parser.add_argument('--nouse_gpu', dest='use_gpu', action='store_false') - parser.set_defaults(use_gpu=True) - command_line_args = parser.parse_args() - logging_args = { - 'format': ('%(asctime)s - %(filename)s:%(lineno)d - %(levelname)s' - ' -- %(message)s'), - 'level': logging.INFO, - 'datefmt': '%Y-%m-%d %H:%M:%S', - } - if command_line_args.log_file: - logging_args['filename'] = command_line_args.log_file - logging_args['filemode'] = 'w' - logging.basicConfig(**logging_args) - main(command_line_args) diff --git a/examples/nips17_adversarial_competition/eval_infra/validation_tool/validate_submission_lib.py b/examples/nips17_adversarial_competition/eval_infra/validation_tool/validate_submission_lib.py deleted file mode 100644 index 6a6117625..000000000 --- a/examples/nips17_adversarial_competition/eval_infra/validation_tool/validate_submission_lib.py +++ /dev/null @@ -1,408 +0,0 @@ -"""Helper library which performs validation of the submission.""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from builtins import int - -import csv -import json -import logging -import os -import re -import subprocess -import sys - -import numpy as np -from PIL import Image - -from six import iteritems - - -EXTRACT_COMMAND = { - '.zip': ['unzip', '${src}', '-d', '${dst}'], - '.tar': ['tar', 'xvf', '${src}', '-C', '${dst}'], - '.tar.gz': ['tar', 'xvzf', '${src}', '-C', '${dst}'], -} - -ALLOWED_SUBMISSION_TYPES = ['attack', 'targeted_attack', 'defense'] - -REQUIRED_METADATA_JSON_FIELDS = ['entry_point', 'container', - 'container_gpu', 'type'] - -CMD_VARIABLE_RE = re.compile('^\\$\\{(\\w+)\\}$') - -BATCH_SIZE = 8 -IMAGE_NAME_PATTERN = 'IMG{0:04}.png' - -ALLOWED_EPS = [4, 8, 12, 16] - -MAX_SUBMISSION_SIZE_ZIPPED = 8*1024*1024*1024 # 8 GiB -MAX_SUBMISSION_SIZE_UNPACKED = 16*1024*1024*1024 # 16 GiB -MAX_DOCKER_IMAGE_SIZE = 8*1024*1024*1024 # 8 GiB - - -def get_extract_command_template(filename): - """Returns extraction command based on the filename extension.""" - for k, v in iteritems(EXTRACT_COMMAND): - if filename.endswith(k): - return v - return None - - -def shell_call(command, **kwargs): - """Calls shell command with parameter substitution. - - Args: - command: command to run as a list of tokens - **kwargs: dirctionary with substitutions - - Returns: - whether command was successful, i.e. returned 0 status code - - Example of usage: - shell_call(['cp', '${A}', '${B}'], A='src_file', B='dst_file') - will call shell command: - cp src_file dst_file - """ - command = list(command) - for i in range(len(command)): - m = CMD_VARIABLE_RE.match(command[i]) - if m: - var_id = m.group(1) - if var_id in kwargs: - command[i] = kwargs[var_id] - return subprocess.call(command) == 0 - - -def make_directory_writable(dirname): - """Makes directory readable and writable by everybody. - - Args: - dirname: name of the directory - - Returns: - True if operation was successfull - - If you run something inside Docker container and it writes files, then - these files will be written as root user with restricted permissions. - So to be able to read/modify these files outside of Docker you have to change - permissions to be world readable and writable. - """ - retval = shell_call(['docker', 'run', '-v', - '{0}:/output_dir'.format(dirname), - 'busybox:1.27.2', - 'chmod', '-R', 'a+rwx', '/output_dir']) - if not retval: - logging.error('Failed to change permissions on directory: %s', dirname) - return retval - - -def load_defense_output(filename): - """Loads output of defense from given file.""" - result = {} - with open(filename) as f: - for row in csv.reader(f): - try: - image_filename = row[0] - if not image_filename.endswith('.png'): - image_filename += '.png' - label = int(row[1]) - except (IndexError, ValueError): - continue - result[image_filename] = label - return result - - -class SubmissionValidator(object): - """Class which performs validation of the submission.""" - - def __init__(self, temp_dir, use_gpu): - """Initializes instance of SubmissionValidator. - - Args: - temp_dir: temporary working directory - use_gpu: whether to use GPU - """ - self._temp_dir = temp_dir - self._use_gpu = use_gpu - self._tmp_extracted_dir = os.path.join(self._temp_dir, 'tmp_extracted') - self._extracted_submission_dir = os.path.join(self._temp_dir, 'extracted') - self._sample_input_dir = os.path.join(self._temp_dir, 'input') - self._sample_output_dir = os.path.join(self._temp_dir, 'output') - - def _prepare_temp_dir(self): - """Cleans up and prepare temporary directory.""" - if not shell_call(['sudo', 'rm', '-rf', os.path.join(self._temp_dir, '*')]): - logging.error('Failed to cleanup temporary directory.') - sys.exit(1) - # NOTE: we do not create self._extracted_submission_dir - # this is intentional because self._tmp_extracted_dir or it's subdir - # will be renames into self._extracted_submission_dir - os.mkdir(self._tmp_extracted_dir) - os.mkdir(self._sample_input_dir) - os.mkdir(self._sample_output_dir) - # make output dir world writable - shell_call(['chmod', 'a+rwX', '-R', self._sample_output_dir]) - - def _extract_submission(self, filename): - """Extracts submission and moves it into self._extracted_submission_dir.""" - # verify filesize - file_size = os.path.getsize(filename) - if file_size > MAX_SUBMISSION_SIZE_ZIPPED: - logging.error('Submission archive size %d is exceeding limit %d', - file_size, MAX_SUBMISSION_SIZE_ZIPPED) - return False - # determime archive type - exctract_command_tmpl = get_extract_command_template(filename) - if not exctract_command_tmpl: - logging.error('Input file has to be zip, tar or tar.gz archive; however ' - 'found: %s', filename) - return False - # extract archive - submission_dir = os.path.dirname(filename) - submission_basename = os.path.basename(filename) - logging.info('Extracting archive %s', filename) - retval = shell_call( - ['docker', 'run', - '--network=none', - '-v', '{0}:/input_dir'.format(submission_dir), - '-v', '{0}:/output_dir'.format(self._tmp_extracted_dir), - 'busybox:1.27.2'] + exctract_command_tmpl, - src=os.path.join('/input_dir', submission_basename), - dst='/output_dir') - if not retval: - logging.error('Failed to extract submission from file %s', filename) - return False - if not make_directory_writable(self._tmp_extracted_dir): - return False - # find submission root - root_dir = self._tmp_extracted_dir - root_dir_content = [d for d in os.listdir(root_dir) if d != '__MACOSX'] - if (len(root_dir_content) == 1 - and os.path.isdir(os.path.join(root_dir, root_dir_content[0]))): - logging.info('Looks like submission root is in subdirectory "%s" of ' - 'the archive', root_dir_content[0]) - root_dir = os.path.join(root_dir, root_dir_content[0]) - # Move files to self._extracted_submission_dir. - # At this point self._extracted_submission_dir does not exist, - # so following command will simply rename root_dir into - # self._extracted_submission_dir - if not shell_call(['mv', root_dir, self._extracted_submission_dir]): - logging.error('Can''t move submission files from root directory') - return False - return True - - def _verify_submission_size(self): - submission_size = 0 - for dirname, _, filenames in os.walk(self._extracted_submission_dir): - for f in filenames: - submission_size += os.path.getsize(os.path.join(dirname, f)) - logging.info('Unpacked submission size: %d', submission_size) - if submission_size > MAX_SUBMISSION_SIZE_UNPACKED: - logging.error('Submission size exceeding limit %d', - MAX_SUBMISSION_SIZE_UNPACKED) - return submission_size <= MAX_SUBMISSION_SIZE_UNPACKED - - def _load_and_verify_metadata(self): - """Loads and verifies metadata. - - Returns: - dictionaty with metadata or None if metadata not found or invalid - """ - metadata_filename = os.path.join(self._extracted_submission_dir, - 'metadata.json') - if not os.path.isfile(metadata_filename): - logging.error('metadata.json not found') - return None - try: - with open(metadata_filename, 'r') as f: - metadata = json.load(f) - except IOError as e: - logging.error('Failed to load metadata: %s', e) - return None - for field_name in REQUIRED_METADATA_JSON_FIELDS: - if field_name not in metadata: - logging.error('Field %s not found in metadata', field_name) - return None - # Verify submission type - if metadata['type'] not in ALLOWED_SUBMISSION_TYPES: - logging.error('Invalid submission type in metadata: %s', - metadata['type']) - return None - # Check submission entry point - entry_point = metadata['entry_point'] - if not os.path.isfile(os.path.join(self._extracted_submission_dir, - entry_point)): - logging.error('Entry point not found: %s', entry_point) - return None - if not entry_point.endswith('.sh'): - logging.warning('Entry point is not an .sh script. ' - 'This is not necessarily a problem, but if submission ' - 'won''t run double check entry point first: %s', - entry_point) - # Metadata verified - return metadata - - def _verify_docker_image_size(self, image_name): - """Verifies size of Docker image. - - Args: - image_name: name of the Docker image. - - Returns: - True if image size is within the limits, False otherwise. - """ - shell_call(['docker', 'pull', image_name]) - try: - image_size = subprocess.check_output( - ['docker', 'inspect', '--format={{.Size}}', image_name]).strip() - image_size = int(image_size) - except (ValueError, subprocess.CalledProcessError) as e: - logging.error('Failed to determine docker image size: %s', e) - return False - logging.info('Size of docker image %s is %d', image_name, image_size) - if image_size > MAX_DOCKER_IMAGE_SIZE: - logging.error('Image size exceeds limit %d', MAX_DOCKER_IMAGE_SIZE) - return image_size <= MAX_DOCKER_IMAGE_SIZE - - def _prepare_sample_data(self, submission_type): - """Prepares sample data for the submission. - - Args: - submission_type: type of the submission. - """ - # write images - images = np.random.randint(0, 256, - size=[BATCH_SIZE, 299, 299, 3], dtype=np.uint8) - for i in range(BATCH_SIZE): - Image.fromarray(images[i, :, :, :]).save( - os.path.join(self._sample_input_dir, IMAGE_NAME_PATTERN.format(i))) - # write target class for targeted attacks - if submission_type == 'targeted_attack': - target_classes = np.random.randint(1, 1001, size=[BATCH_SIZE]) - target_class_filename = os.path.join(self._sample_input_dir, - 'target_class.csv') - with open(target_class_filename, 'w') as f: - for i in range(BATCH_SIZE): - f.write((IMAGE_NAME_PATTERN + ',{1}\n').format(i, target_classes[i])) - - def _run_submission(self, metadata): - """Runs submission inside Docker container. - - Args: - metadata: dictionary with submission metadata - - Returns: - True if status code of Docker command was success (i.e. zero), - False otherwise. - """ - container_name = (metadata['container_gpu'] - if self._use_gpu else metadata['container']) - if metadata['type'] == 'defense': - cmd = ['--network=none', - '-m=24g', - '-v', '{0}:/input_images:ro'.format(self._sample_input_dir), - '-v', '{0}:/output_data'.format(self._sample_output_dir), - '-v', '{0}:/code'.format(self._extracted_submission_dir), - '-w', '/code', - container_name, - './' + metadata['entry_point'], - '/input_images', - '/output_data/result.csv'] - else: - epsilon = np.random.choice(ALLOWED_EPS) - cmd = ['--network=none', - '-m=24g', - '-v', '{0}:/input_images:ro'.format(self._sample_input_dir), - '-v', '{0}:/output_images'.format(self._sample_output_dir), - '-v', '{0}:/code'.format(self._extracted_submission_dir), - '-w', '/code', - container_name, - './' + metadata['entry_point'], - '/input_images', - '/output_images', - str(epsilon)] - if self._use_gpu: - cmd = ['docker', 'run', '--runtime=nvidia'] + cmd - else: - cmd = ['docker', 'run'] + cmd - logging.info('Command to run submission: %s', ' '.join(cmd)) - result = shell_call(cmd) - make_directory_writable(self._extracted_submission_dir) - make_directory_writable(self._sample_output_dir) - return result - - def _verify_output(self, submission_type): - """Verifies correctness of the submission output. - - Args: - submission_type: type of the submission - - Returns: - True if output looks valid - """ - result = True - if submission_type == 'defense': - try: - image_classification = load_defense_output( - os.path.join(self._sample_output_dir, 'result.csv')) - expected_keys = [IMAGE_NAME_PATTERN.format(i) - for i in range(BATCH_SIZE)] - if set(image_classification.keys()) != set(expected_keys): - logging.error('Classification results are not saved for all images') - result = False - except IOError as e: - logging.error('Failed to read defense output file: %s', e) - result = False - else: - for i in range(BATCH_SIZE): - image_filename = os.path.join(self._sample_output_dir, - IMAGE_NAME_PATTERN.format(i)) - try: - img = np.array(Image.open(image_filename).convert('RGB')) - if list(img.shape) != [299, 299, 3]: - logging.error('Invalid image size %s for image %s', - str(img.shape), image_filename) - result = False - except IOError as e: - result = False - return result - - def validate_submission(self, filename): - """Validates submission. - - Args: - filename: submission filename - - Returns: - submission metadata or None if submission is invalid - """ - self._prepare_temp_dir() - # Convert filename to be absolute path, relative path might cause problems - # with mounting directory in Docker - filename = os.path.abspath(filename) - # extract submission - if not self._extract_submission(filename): - return None - # verify submission size - if not self._verify_submission_size(): - return None - # Load metadata - metadata = self._load_and_verify_metadata() - if not metadata: - return None - submission_type = metadata['type'] - # verify docker container size - if not self._verify_docker_image_size(metadata['container_gpu']): - return None - # Try to run submission on sample data - self._prepare_sample_data(submission_type) - if not self._run_submission(metadata): - logging.error('Failure while running submission') - return None - if not self._verify_output(submission_type): - logging.warning('Some of the outputs of your submission are invalid or ' - 'missing. You submission still will be evaluation ' - 'but you might get lower score.') - return metadata diff --git a/examples/robust_vision_benchmark/cleverhans_attack_example/utils.py b/examples/robust_vision_benchmark/cleverhans_attack_example/utils.py deleted file mode 100644 index 75ba81cab..000000000 --- a/examples/robust_vision_benchmark/cleverhans_attack_example/utils.py +++ /dev/null @@ -1,88 +0,0 @@ -import cleverhans.model -import tensorflow as tf -import numpy as np - - -def cleverhans_attack_wrapper(cleverhans_attack_fn, reset=True): - def attack(a): - session = tf.Session() - with session.as_default(): - model = RVBCleverhansModel(a) - adversarial_image = cleverhans_attack_fn(model, session, a) - adversarial_image = np.squeeze(adversarial_image, axis=0) - if reset: - # optionally, reset to ignore other adversarials - # found during the search - a._reset() - # run predictions to make sure the returned adversarial - # is taken into account - min_, max_ = a.bounds() - adversarial_image = np.clip(adversarial_image, min_, max_) - a.predictions(adversarial_image) - return attack - - -def py_func_grad(func, inp, Tout, stateful=True, name=None, grad=None): - """Custom py_func with gradient support - - """ - # Need to generate a unique name to avoid duplicates: - rnd_name = 'PyFuncGrad' + str(np.random.randint(0, 1E+8)) - - tf.RegisterGradient(rnd_name)(grad) - g = tf.get_default_graph() - with g.gradient_override_map({"PyFunc": rnd_name, - "PyFuncStateless": rnd_name}): - return tf.py_func(func, inp, Tout, stateful=stateful, name=name) - - -class RVBCleverhansModel(cleverhans.model.Model): - """This is a cleverhans model that wraps a robust vision benchmark model. - - """ - - def __init__(self, adversarial, **kwargs): - del kwargs - cleverhans.model.Model.__init__( - self, "model", self.adversarial.nb_classes(), locals()) - self.adversarial = adversarial - - def fprop(self, x): - return {self.O_LOGITS: self._logits_op(x)} - - def _logits_op(self, x, name=None): - with tf.name_scope(name, "logits", [x]) as name: - - nb_classes = self.adversarial.nb_classes() - - def _backward_py(gradient_y, x): - x = np.squeeze(x, axis=0) - gradient_y = np.squeeze(gradient_y, axis=0) - gradient_x = self.adversarial.backward(gradient_y, x) - gradient_x = gradient_x.astype(np.float32) - return gradient_x[np.newaxis] - - def _backward_tf(op, grad): - images = op.inputs[0] - gradient_x = tf.py_func( - _backward_py, [grad, images], tf.float32) - gradient_x.set_shape(images.shape) - return gradient_x - - def _forward_py(x): - predictions = self.adversarial.batch_predictions( - x, strict=False)[0] - predictions = predictions.astype(np.float32) - return predictions - - op = py_func_grad( - _forward_py, - [x], - [tf.float32], - name=name, - grad=_backward_tf) - - logits = op[0] - logits.set_shape((x.shape[0], nb_classes)) - - return logits diff --git a/examples/test_imagenet_attacks.py b/examples/test_imagenet_attacks.py deleted file mode 100644 index 7287aaea6..000000000 --- a/examples/test_imagenet_attacks.py +++ /dev/null @@ -1,250 +0,0 @@ -"""Test attack success against ImageNet models for a few images. - -Many of the tests require using flags to specify a pre-trained ImageNet model, -as well as image data. The easiest way to provide these is using the data from -cleverhans/examples/nips17_adversarial_competition, and then the default flag -values will just work. - -Setup: see SETUP_INSTRUCTIONS -""" -# pylint: disable=missing-docstring -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - -import csv -import os -import unittest - -import numpy as np -from six.moves import xrange -import tensorflow as tf -from tensorflow.contrib import slim -# The following line is affected by a pylint bug when using python3 and tf 1.12 -from tensorflow.contrib.slim.nets import inception # pylint: disable=no-name-in-module -from PIL import Image -from cleverhans.attacks import SPSA -from cleverhans.devtools.checks import CleverHansTest -from cleverhans.model import Model -from cleverhans.utils import CLEVERHANS_ROOT - -SETUP_INSTRUCTIONS = """ -$ ./examples/nips17_adversarial_competition/dev_toolkit/download_data.sh -""" - -DEFAULT_INCEPTION_PATH = os.path.join( - CLEVERHANS_ROOT, - ('examples/nips17_adversarial_competition/dev_toolkit/sample_attacks/fgsm/' - 'inception_v3.ckpt')) - -tf.flags.DEFINE_string( - 'master', '', 'The address of the TensorFlow master to use.') - -tf.flags.DEFINE_string( - 'checkpoint_path', - DEFAULT_INCEPTION_PATH, 'Path to checkpoint for inception network.') - -tf.flags.DEFINE_string( - 'input_image_dir', - os.path.join(CLEVERHANS_ROOT, - 'examples/nips17_adversarial_competition/dataset/images'), - 'Path to image directory.') - -tf.flags.DEFINE_string( - 'metadata_file_path', - os.path.join( - CLEVERHANS_ROOT, - 'examples/nips17_adversarial_competition/dataset/dev_dataset.csv'), - 'Path to metadata file.') - -FLAGS = tf.flags.FLAGS - - -def load_images(input_dir, metadata_file_path, batch_shape): - """Retrieve numpy arrays of images and labels, read from a directory.""" - num_images = batch_shape[0] - with open(metadata_file_path) as input_file: - reader = csv.reader(input_file) - header_row = next(reader) - rows = list(reader) - - row_idx_image_id = header_row.index('ImageId') - row_idx_true_label = header_row.index('TrueLabel') - images = np.zeros(batch_shape) - labels = np.zeros(num_images, dtype=np.int32) - for idx in xrange(num_images): - row = rows[idx] - filepath = os.path.join(input_dir, row[row_idx_image_id] + '.png') - - with tf.gfile.Open(filepath, 'rb') as f: - image = np.array( - Image.open(f).convert('RGB')).astype(np.float) / 255.0 - images[idx, :, :, :] = image - labels[idx] = int(row[row_idx_true_label]) - return images, labels - - -class InceptionModel(Model): - """Model class for CleverHans library.""" - - def __init__(self, nb_classes): - super(InceptionModel, self).__init__(nb_classes=nb_classes, - needs_dummy_fprop=True) - self.built = False - - def __call__(self, x_input, return_logits=False): - """Constructs model and return probabilities for given input.""" - reuse = True if self.built else None - with slim.arg_scope(inception.inception_v3_arg_scope()): - # Inception preprocessing uses [-1, 1]-scaled input. - x_input = x_input * 2.0 - 1.0 - _, end_points = inception.inception_v3( - x_input, num_classes=self.nb_classes, is_training=False, - reuse=reuse) - self.built = True - self.logits = end_points['Logits'] - # Strip off the extra reshape op at the output - self.probs = end_points['Predictions'].op.inputs[0] - if return_logits: - return self.logits - else: - return self.probs - - def get_logits(self, x_input): - return self(x_input, return_logits=True) - - def get_probs(self, x_input): - return self(x_input) - - -def _top_1_accuracy(logits, labels): - return tf.reduce_mean( - tf.cast(tf.nn.in_top_k(logits, labels, 1), tf.float32)) - - -class TestInception(CleverHansTest): - def test_clean_accuracy(self): - """Check model is accurate on unperturbed images.""" - input_dir = FLAGS.input_image_dir - metadata_file_path = FLAGS.metadata_file_path - num_images = 16 - batch_shape = (num_images, 299, 299, 3) - images, labels = load_images( - input_dir, metadata_file_path, batch_shape) - nb_classes = 1001 - - tf.logging.set_verbosity(tf.logging.INFO) - with tf.Graph().as_default(): - # Prepare graph - x_input = tf.placeholder(tf.float32, shape=batch_shape) - y_label = tf.placeholder(tf.int32, shape=(num_images,)) - model = InceptionModel(nb_classes) - logits = model.get_logits(x_input) - acc = _top_1_accuracy(logits, y_label) - - # Run computation - saver = tf.train.Saver(slim.get_model_variables()) - - session_creator = tf.train.ChiefSessionCreator( - scaffold=tf.train.Scaffold(saver=saver), - checkpoint_filename_with_path=FLAGS.checkpoint_path, - master=FLAGS.master) - - with tf.train.MonitoredSession(session_creator=session_creator) as sess: - acc_val = sess.run(acc, feed_dict={x_input: images, y_label: labels}) - tf.logging.info('Accuracy: %s', acc_val) - assert acc_val > 0.8 - - -class TestSPSA(CleverHansTest): - def test_attack_bounds(self): - """Check SPSA respects perturbation limits.""" - epsilon = 4. / 255 - input_dir = FLAGS.input_image_dir - metadata_file_path = FLAGS.metadata_file_path - num_images = 8 - batch_shape = (num_images, 299, 299, 3) - images, labels = load_images( - input_dir, metadata_file_path, batch_shape) - nb_classes = 1001 - - tf.logging.set_verbosity(tf.logging.INFO) - with tf.Graph().as_default(): - # Prepare graph - x_input = tf.placeholder(tf.float32, shape=(1,) + batch_shape[1:]) - y_label = tf.placeholder(tf.int32, shape=(1,)) - model = InceptionModel(nb_classes) - - attack = SPSA(model) - x_adv = attack.generate( - x_input, y=y_label, epsilon=epsilon, num_steps=10, - early_stop_loss_threshold=-1., spsa_samples=32, spsa_iters=1, - is_debug=True) - - # Run computation - saver = tf.train.Saver(slim.get_model_variables()) - session_creator = tf.train.ChiefSessionCreator( - scaffold=tf.train.Scaffold(saver=saver), - checkpoint_filename_with_path=FLAGS.checkpoint_path, - master=FLAGS.master) - - with tf.train.MonitoredSession(session_creator=session_creator) as sess: - for i in xrange(num_images): - x_expanded = np.expand_dims(images[i], axis=0) - y_expanded = np.expand_dims(labels[i], axis=0) - - adv_image = sess.run(x_adv, feed_dict={x_input: x_expanded, - y_label: y_expanded}) - diff = adv_image - images[i] - assert np.max(np.abs(diff)) < epsilon + 1e-4 - assert np.max(adv_image < 1. + 1e-4) - assert np.min(adv_image > -1e-4) - - def test_attack_success(self): - """Check SPSA creates misclassified images.""" - epsilon = 4. / 255 - input_dir = FLAGS.input_image_dir - metadata_file_path = FLAGS.metadata_file_path - num_images = 8 - batch_shape = (num_images, 299, 299, 3) - images, labels = load_images( - input_dir, metadata_file_path, batch_shape) - nb_classes = 1001 - - tf.logging.set_verbosity(tf.logging.INFO) - with tf.Graph().as_default(): - # Prepare graph - x_input = tf.placeholder(tf.float32, shape=(1,) + batch_shape[1:]) - y_label = tf.placeholder(tf.int32, shape=(1,)) - model = InceptionModel(nb_classes) - - attack = SPSA(model) - x_adv = attack.generate( - x_input, y=y_label, epsilon=epsilon, num_steps=30, - early_stop_loss_threshold=-1., spsa_samples=32, spsa_iters=16, - is_debug=True) - - logits = model.get_logits(x_adv) - acc = _top_1_accuracy(logits, y_label) - - # Run computation - saver = tf.train.Saver(slim.get_model_variables()) - session_creator = tf.train.ChiefSessionCreator( - scaffold=tf.train.Scaffold(saver=saver), - checkpoint_filename_with_path=FLAGS.checkpoint_path, - master=FLAGS.master) - - num_correct = 0. - with tf.train.MonitoredSession(session_creator=session_creator) as sess: - for i in xrange(num_images): - feed_dict_i = {x_input: np.expand_dims(images[i], axis=0), - y_label: np.expand_dims(labels[i], axis=0)} - acc_val = sess.run(acc, feed_dict=feed_dict_i) - tf.logging.info('Accuracy: %s', acc_val) - num_correct += acc_val - assert (num_correct / num_images) < 0.1 - - -if __name__ == '__main__': - unittest.main() diff --git a/requirements/requirements-dev.txt b/requirements/requirements-dev.txt new file mode 100644 index 000000000..74f7c74e3 --- /dev/null +++ b/requirements/requirements-dev.txt @@ -0,0 +1,6 @@ +black>=20.8b1 +flake8>=3.8.0 +mypy>=0.790 +mypy-extensions>=0.4.3 +pytest>=6.2.0 +pre-commit>=2.9.0 diff --git a/requirements/requirements-gpu.txt b/requirements/requirements-gpu.txt new file mode 100644 index 000000000..4c3553872 --- /dev/null +++ b/requirements/requirements-gpu.txt @@ -0,0 +1,2 @@ +--find-links https://storage.googleapis.com/jax-releases/jax_releases.html +jaxlib>=0.1.60+cuda101 diff --git a/requirements/requirements-jax.txt b/requirements/requirements-jax.txt new file mode 100644 index 000000000..9ab66c59b --- /dev/null +++ b/requirements/requirements-jax.txt @@ -0,0 +1,2 @@ +jax>=0.2.8 +jaxlib>=0.1.60 \ No newline at end of file diff --git a/requirements/requirements-pytorch.txt b/requirements/requirements-pytorch.txt new file mode 100644 index 000000000..45b930f18 --- /dev/null +++ b/requirements/requirements-pytorch.txt @@ -0,0 +1,2 @@ +torch>=1.7.0 +torchvision>=0.8.2 \ No newline at end of file diff --git a/requirements/requirements-tf2.txt b/requirements/requirements-tf2.txt new file mode 100644 index 000000000..cdfa3c4ab --- /dev/null +++ b/requirements/requirements-tf2.txt @@ -0,0 +1,2 @@ +tensorflow>=2.3.0 +tensorflow-datasets>=4.2.0 \ No newline at end of file diff --git a/requirements/requirements.txt b/requirements/requirements.txt new file mode 100644 index 000000000..5d4aa08ce --- /dev/null +++ b/requirements/requirements.txt @@ -0,0 +1,4 @@ +numpy>=1.19.0 +scipy>=1.5.0 +easydict>=1.9 +absl-py>=0.10.0 diff --git a/scripts/compute_accuracy.py b/scripts/compute_accuracy.py deleted file mode 100755 index 81ceaaec0..000000000 --- a/scripts/compute_accuracy.py +++ /dev/null @@ -1,191 +0,0 @@ -#!/usr/bin/env python3 -""" -compute_accuracy.py -Usage: - compute_accuracy.py model.joblib - - where model.joblib is a file created by cleverhans.serial.save containing - a picklable cleverhans.model.Model instance. - -This script will run the model on a variety of types of data and print out -the accuracy for each data type. - clean : Clean data - semantic : Semantic adversarial examples - pgd: PGD adversarial examples - -This script works by running a single attack on each example. -This is useful for quick evaluation during development, but for final -publication it would be better to use attack bundling. -""" -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - -import logging -import time - -import tensorflow as tf - -from cleverhans.attacks import ProjectedGradientDescent, Semantic -from cleverhans.compat import flags -from cleverhans.evaluation import accuracy -from cleverhans.serial import load -from cleverhans.utils import set_log_level -from cleverhans.utils_tf import infer_devices -from cleverhans.utils_tf import silence -silence() -devices = infer_devices() -num_devices = len(devices) -BATCH_SIZE = 128 -TRAIN_START = 0 -TRAIN_END = 60000 -TEST_START = 0 -TEST_END = 10000 -WHICH_SET = 'test' -NB_ITER = 40 -BASE_EPS_ITER = None # Differs by dataset - - -FLAGS = flags.FLAGS - - -def print_accuracies(filepath, train_start=TRAIN_START, train_end=TRAIN_END, - test_start=TEST_START, test_end=TEST_END, - batch_size=BATCH_SIZE, which_set=WHICH_SET, - base_eps_iter=BASE_EPS_ITER, - nb_iter=NB_ITER): - """ - Load a saved model and print out its accuracy on different data distributions - - This function works by running a single attack on each example. - This provides a reasonable estimate of the true failure rate quickly, so - long as the model does not suffer from gradient masking. - However, this estimate is mostly intended for development work and not - for publication. A more accurate estimate may be obtained by running - an attack bundler instead. - - :param filepath: path to model to evaluate - :param train_start: index of first training set example to use - :param train_end: index of last training set example to use - :param test_start: index of first test set example to use - :param test_end: index of last test set example to use - :param batch_size: size of evaluation batches - :param which_set: 'train' or 'test' - :param base_eps_iter: step size if the data were in [0,1] - (Step size will be rescaled proportional to the actual data range) - :param nb_iter: Number of iterations of PGD to run per class - """ - - # Set TF random seed to improve reproducibility - tf.set_random_seed(20181014) - set_log_level(logging.INFO) - sess = tf.Session() - - with sess.as_default(): - model = load(filepath) - assert len(model.get_params()) > 0 - factory = model.dataset_factory - factory.kwargs['train_start'] = train_start - factory.kwargs['train_end'] = train_end - factory.kwargs['test_start'] = test_start - factory.kwargs['test_end'] = test_end - dataset = factory() - - - x_data, y_data = dataset.get_set(which_set) - - impl(sess, model, dataset, factory, x_data, y_data, base_eps_iter, nb_iter) - -def impl(sess, model, dataset, factory, x_data, y_data, - base_eps_iter=BASE_EPS_ITER, nb_iter=NB_ITER, - batch_size=BATCH_SIZE): - """ - The actual implementation of the evaluation. - :param sess: tf.Session - :param model: cleverhans.model.Model - :param dataset: cleverhans.dataset.Dataset - :param factory: the dataset factory corresponding to `dataset` - :param x_data: numpy array of input examples - :param y_data: numpy array of class labels - :param base_eps_iter: step size for PGD if data were in [0, 1] - :param nb_iter: number of PGD iterations - :returns: dict mapping string adversarial example names to accuracies - """ - - center = dataset.kwargs['center'] - max_val = dataset.kwargs['max_val'] - value_range = max_val * (1. + center) - min_value = 0. - center * max_val - - if 'CIFAR' in str(factory.cls): - base_eps = 8. / 255. - if base_eps_iter is None: - base_eps_iter = 2. / 255. - elif 'MNIST' in str(factory.cls): - base_eps = .3 - if base_eps_iter is None: - base_eps_iter = .1 - else: - raise NotImplementedError(str(factory.cls)) - - pgd_params = {'eps': base_eps * value_range, - 'eps_iter': base_eps_iter * value_range, - 'nb_iter': nb_iter, - 'clip_min': min_value, - 'clip_max': max_val} - - semantic = Semantic(model, center, max_val, sess) - pgd = ProjectedGradientDescent(model, sess=sess) - - jobs = [('clean', None, None, None), - ('Semantic', semantic, None, None), - ('pgd', pgd, pgd_params, None)] - - out = {} - - for job in jobs: - name, attack, attack_params, job_batch_size = job - if job_batch_size is None: - job_batch_size = batch_size - t1 = time.time() - acc = accuracy(sess, model, x_data, y_data, batch_size=job_batch_size, - devices=devices, attack=attack, attack_params=attack_params) - t2 = time.time() - out[name] = acc - print("Accuracy on " + name + " examples: ", acc) - print("Evaluation took", t2 - t1, "seconds") - - return out - - -def main(argv=None): - """ - Print accuracies - """ - try: - _name_of_script, filepath = argv - except ValueError: - raise ValueError(argv) - print_accuracies(filepath=filepath, test_start=FLAGS.test_start, - test_end=FLAGS.test_end, which_set=FLAGS.which_set, - nb_iter=FLAGS.nb_iter, base_eps_iter=FLAGS.base_eps_iter, - batch_size=FLAGS.batch_size) - - -if __name__ == '__main__': - flags.DEFINE_integer('train_start', TRAIN_START, 'Starting point (inclusive)' - 'of range of train examples to use') - flags.DEFINE_integer('train_end', TRAIN_END, 'Ending point (non-inclusive) ' - 'of range of train examples to use') - flags.DEFINE_integer('test_start', TEST_START, 'Starting point (inclusive) ' - 'of range of test examples to use') - flags.DEFINE_integer('test_end', TEST_END, 'End point (non-inclusive) of ' - 'range of test examples to use') - flags.DEFINE_integer('nb_iter', NB_ITER, 'Number of iterations of PGD') - flags.DEFINE_string('which_set', WHICH_SET, '"train" or "test"') - flags.DEFINE_integer('batch_size', BATCH_SIZE, - 'Batch size for most jobs') - flags.DEFINE_float('base_eps_iter', BASE_EPS_ITER, - 'epsilon per iteration, if data were in [0, 1]') - tf.app.run() diff --git a/scripts/make_confidence_report_bundle_examples.py b/scripts/make_confidence_report_bundle_examples.py deleted file mode 100755 index b3974fd84..000000000 --- a/scripts/make_confidence_report_bundle_examples.py +++ /dev/null @@ -1,110 +0,0 @@ -#!/usr/bin/env python3 -""" -make_confidence_report_bundle_examples.py -Usage: - make_confidence_report_bundle_examples.py model.joblib a.npy - make_confidence_report_bundle_examples.py model.joblib a.npy b.npy c.npy - - where model.joblib is a file created by cleverhans.serial.save containing - a picklable cleverhans.model.Model instance and each examples_i.npy is - a saved numpy array containing adversarial examples for a whole dataset. - Usually example_i.npy is the output of make_confidence_report.py or - make_confidence_report_bundled.py. - -This script uses max-confidence attack bundling -( https://openreview.net/forum?id=H1g0piA9tQ ) -to combine adversarial example datasets that were created earlier. -It will save a ConfidenceReport to to model_bundled_examples_report.joblib. -The report can be later loaded by another -script using cleverhans.serial.load. -""" -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - -import warnings - -import numpy as np -import tensorflow as tf - -from cleverhans.utils_tf import silence -# We need to disable pylint's complaints about import order because `silence` -# works only if it is called before the other imports. -# pylint: disable=C0413 -silence() -from cleverhans.attack_bundling import bundle_examples_with_goal, MaxConfidence -from cleverhans import serial -from cleverhans.compat import flags -from cleverhans.confidence_report import BATCH_SIZE -from cleverhans.confidence_report import TRAIN_START, TRAIN_END -from cleverhans.confidence_report import TEST_START, TEST_END -from cleverhans.confidence_report import WHICH_SET - - -FLAGS = flags.FLAGS - - -def main(argv=None): - """ - Make a confidence report and save it to disk. - """ - assert len(argv) >= 3 - _name_of_script = argv[0] - model_filepath = argv[1] - adv_x_filepaths = argv[2:] - - sess = tf.Session() - with sess.as_default(): - model = serial.load(model_filepath) - - factory = model.dataset_factory - factory.kwargs['train_start'] = FLAGS.train_start - factory.kwargs['train_end'] = FLAGS.train_end - factory.kwargs['test_start'] = FLAGS.test_start - factory.kwargs['test_end'] = FLAGS.test_end - dataset = factory() - - adv_x_list = [np.load(filepath) for filepath in adv_x_filepaths] - x, y = dataset.get_set(FLAGS.which_set) - for adv_x in adv_x_list: - assert adv_x.shape == x.shape, (adv_x.shape, x.shape) - # Make sure these were made for the right dataset with right scaling - # arguments, etc. - assert adv_x.min() >= 0. - dataset.kwargs['center'] * dataset.max_val - assert adv_x.max() <= dataset.max_val - data_range = dataset.max_val * (1. + dataset.kwargs['center']) - - if adv_x.max() - adv_x.min() <= .8 * data_range: - warnings.warn("Something is weird. Your adversarial examples use " - "less than 80% of the data range." - "This might mean you generated them for a model with " - "inputs in [0, 1] and are now using them for a model " - "with inputs in [0, 255] or something like that. " - "Or it could be OK if you're evaluating on a very small " - "batch.") - - report_path = FLAGS.report_path - if report_path is None: - suffix = "_bundled_examples_report.joblib" - assert model_filepath.endswith('.joblib') - report_path = model_filepath[:-len('.joblib')] + suffix - - goal = MaxConfidence() - bundle_examples_with_goal(sess, model, adv_x_list, y, goal, - report_path, batch_size=FLAGS.batch_size) - - -if __name__ == '__main__': - flags.DEFINE_string('report_path', None, 'Report path') - flags.DEFINE_integer('train_start', TRAIN_START, 'Starting point (inclusive)' - 'of range of train examples to use') - flags.DEFINE_integer('train_end', TRAIN_END, 'Ending point (non-inclusive) ' - 'of range of train examples to use') - flags.DEFINE_integer('test_start', TEST_START, 'Starting point ' - '(inclusive) of range of test examples to use') - flags.DEFINE_integer('test_end', TEST_END, 'End point (non-inclusive) of ' - 'range of test examples to use') - flags.DEFINE_string('which_set', WHICH_SET, '"train" or "test"') - flags.DEFINE_integer('batch_size', BATCH_SIZE, 'batch size') - tf.app.run() diff --git a/scripts/make_confidence_report_spsa.py b/scripts/make_confidence_report_spsa.py deleted file mode 100755 index 581e9a37e..000000000 --- a/scripts/make_confidence_report_spsa.py +++ /dev/null @@ -1,171 +0,0 @@ -#!/usr/bin/env python3 -""" -make_confidence_report.py -Usage: - python make_confidence_report_spsa.py model.joblib - - where model.joblib is a file created by cleverhans.serial.save containing - a picklable cleverhans.model.Model instance. - -This script will run the model on a variety of types of data and save a -ConfidenceReport to model_report.joblib. -The report can be later loaded by another script using cleverhans.serial.load. -This script puts the following entries in the report: - clean : Clean data - mc: MaxConfidence SPSA adversarial examples - -This script works by running a single MaxConfidence attack on each example. -( https://openreview.net/forum?id=H1g0piA9tQ ) -The MaxConfidence attack uses the SPSA optimizer. -This is not intended to be a generic strong attack; rather it is intended -to be a test for gradient masking. -""" -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - -import logging - -import numpy as np -import tensorflow as tf - -from cleverhans.utils_tf import silence -silence() -# The call to silence() must precede the other imports or they will log. -# pylint does not like this. -# pylint: disable=C0413 -from cleverhans.attacks import SPSA -from cleverhans.attack_bundling import spsa_max_confidence_recipe -from cleverhans.serial import load -from cleverhans.utils import set_log_level -from cleverhans.compat import flags -from cleverhans.confidence_report import BATCH_SIZE -from cleverhans.confidence_report import TRAIN_START -from cleverhans.confidence_report import TRAIN_END -from cleverhans.confidence_report import TEST_START -from cleverhans.confidence_report import TEST_END -from cleverhans.confidence_report import WHICH_SET -from cleverhans.confidence_report import REPORT_PATH -NB_ITER_SPSA = 80 -SPSA_SAMPLES = SPSA.DEFAULT_SPSA_SAMPLES - - -FLAGS = flags.FLAGS - -def make_confidence_report_spsa(filepath, train_start=TRAIN_START, - train_end=TRAIN_END, - test_start=TEST_START, test_end=TEST_END, - batch_size=BATCH_SIZE, which_set=WHICH_SET, - report_path=REPORT_PATH, - nb_iter=NB_ITER_SPSA, - spsa_samples=SPSA_SAMPLES, - spsa_iters=SPSA.DEFAULT_SPSA_ITERS): - """ - Load a saved model, gather its predictions, and save a confidence report. - - - This function works by running a single MaxConfidence attack on each example, - using SPSA as the underyling optimizer. - This is not intended to be a strong generic attack. - It is intended to be a test to uncover gradient masking. - - :param filepath: path to model to evaluate - :param train_start: index of first training set example to use - :param train_end: index of last training set example to use - :param test_start: index of first test set example to use - :param test_end: index of last test set example to use - :param batch_size: size of evaluation batches - :param which_set: 'train' or 'test' - :param nb_iter: Number of iterations of PGD to run per class - :param spsa_samples: Number of samples for SPSA - """ - - # Set TF random seed to improve reproducibility - tf.set_random_seed(1234) - - # Set logging level to see debug information - set_log_level(logging.INFO) - - # Create TF session - sess = tf.Session() - - if report_path is None: - assert filepath.endswith('.joblib') - report_path = filepath[:-len('.joblib')] + "_spsa_report.joblib" - - with sess.as_default(): - model = load(filepath) - assert len(model.get_params()) > 0 - factory = model.dataset_factory - factory.kwargs['train_start'] = train_start - factory.kwargs['train_end'] = train_end - factory.kwargs['test_start'] = test_start - factory.kwargs['test_end'] = test_end - dataset = factory() - - center = dataset.kwargs['center'] - center = np.float32(center) - max_val = dataset.kwargs['max_val'] - max_val = np.float32(max_val) - value_range = max_val * (1. + center) - min_value = np.float32(0. - center * max_val) - - if 'CIFAR' in str(factory.cls): - base_eps = 8. / 255. - elif 'MNIST' in str(factory.cls): - base_eps = .3 - else: - raise NotImplementedError(str(factory.cls)) - - eps = np.float32(base_eps * value_range) - clip_min = min_value - clip_max = max_val - - x_data, y_data = dataset.get_set(which_set) - - nb_classes = dataset.NB_CLASSES - - spsa_max_confidence_recipe(sess, model, x_data, y_data, nb_classes, eps, - clip_min, clip_max, nb_iter, report_path, - spsa_samples=spsa_samples, - spsa_iters=spsa_iters, - eval_batch_size=batch_size) - -def main(argv=None): - """ - Make a confidence report and save it to disk. - """ - try: - _name_of_script, filepath = argv - except ValueError: - raise ValueError(argv) - make_confidence_report_spsa(filepath=filepath, test_start=FLAGS.test_start, - test_end=FLAGS.test_end, - which_set=FLAGS.which_set, - report_path=FLAGS.report_path, - nb_iter=FLAGS.nb_iter, - batch_size=FLAGS.batch_size, - spsa_samples=FLAGS.spsa_samples, - spsa_iters=FLAGS.spsa_iters) - -if __name__ == '__main__': - flags.DEFINE_integer('spsa_samples', SPSA_SAMPLES, 'Number samples for SPSA') - flags.DEFINE_integer('spsa_iters', SPSA.DEFAULT_SPSA_ITERS, - 'Passed to SPSA.generate') - flags.DEFINE_integer('train_start', TRAIN_START, 'Starting point (inclusive)' - 'of range of train examples to use') - flags.DEFINE_integer('train_end', TRAIN_END, 'Ending point (non-inclusive) ' - 'of range of train examples to use') - flags.DEFINE_integer('test_start', TEST_START, - 'Starting point (inclusive) of range' - ' of test examples to use') - flags.DEFINE_integer('test_end', TEST_END, - 'End point (non-inclusive) of range' - ' of test examples to use') - flags.DEFINE_integer('nb_iter', NB_ITER_SPSA, 'Number of iterations of SPSA') - flags.DEFINE_string('which_set', WHICH_SET, '"train" or "test"') - flags.DEFINE_string('report_path', REPORT_PATH, 'Path to save to') - flags.DEFINE_integer('batch_size', BATCH_SIZE, - 'Batch size for most jobs') - tf.app.run() diff --git a/scripts/print_report.py b/scripts/print_report.py deleted file mode 100755 index 00703cac0..000000000 --- a/scripts/print_report.py +++ /dev/null @@ -1,71 +0,0 @@ -#!/usr/bin/env python3 -""" -print_report.py -Usage: - print_report.py model_report.joblib -Prints out some basic statistics stored in a pickled ConfidenceReport -""" -import sys -import warnings - -from cleverhans.confidence_report import ConfidenceReport -from cleverhans.serial import load - - -if len(sys.argv) == 2: - # pylint doesn't realize that sys.argv will change at runtime - # pylint:disable=unbalanced-tuple-unpacking - _, path = sys.argv -else: - raise ValueError("Wrong number of arguments") -the_report = load(path) - -def current(report): - """ - The current implementation of report printing. - :param report: ConfidenceReport - """ - if hasattr(report, "completed"): - if report.completed: - print("Report completed") - else: - print("REPORT NOT COMPLETED") - else: - warnings.warn("This report does not indicate whether it is completed. Support for reports without a `completed`" - "field may be dropped on or after 2019-05-11.") - for key in report: - covered = report[key].confidence > 0.5 - wrong = 1. - report[key].correctness - failure_rate = (covered * wrong).mean() - print(key, 'failure rate at t=.5', failure_rate) - print(key, 'accuracy at t=0', report[key].correctness.mean()) - -def deprecated(report): - """ - The deprecated implementation of report printing. - :param report: dict - """ - warnings.warn("Printing dict-based reports is deprecated. This function " - "is included only to support a private development branch " - "and may be removed without warning.") - - for key in report: - confidence_name = 'confidence' - correctness_name = 'correctness' - if confidence_name not in report[key]: - confidence_name = 'all_probs' - correctness_name = 'correctness_mask' - warnings.warn("'all_probs' is used only to temporarily support " - "the private development branch. This name can be " - "removed at any time without warning.") - covered = report[key][confidence_name] > 0.5 - wrong = 1. - report[key][correctness_name] - failure_rate = (covered * wrong).mean() - print(key, 'failure rate at t=.5', failure_rate) - print(key, 'accuracy at t=0', report[key][correctness_name].mean()) - - -if isinstance(the_report, ConfidenceReport): - current(the_report) -else: - deprecated(the_report) diff --git a/setup.py b/setup.py index 83dfdce4f..76bccc736 100644 --- a/setup.py +++ b/setup.py @@ -1,25 +1,28 @@ from setuptools import find_packages from setuptools import setup -setup(name='cleverhans', - version='3.0.1', - url='https://github.com/tensorflow/cleverhans', - license='MIT', - install_requires=[ - 'nose', - 'pycodestyle', - 'scipy', - 'matplotlib', - "mnist ~= 0.2", - "numpy", - "tensorflow-probability", - "joblib", - ], - # Explicit dependence on TensorFlow is not supported. - # See https://github.com/tensorflow/tensorflow/issues/7166 - extras_require={ - "tf": ["tensorflow>=1.0.0"], - "tf_gpu": ["tensorflow-gpu>=1.0.0"], - "pytorch": ["torch>=1.1.0", "torchvision==0.3.0"], - }, - packages=find_packages()) +setup( + name="cleverhans", + version="4.0.0", + url="https://github.com/cleverhans-lab/cleverhans", + license="MIT", + install_requires=[ + "nose", + "pycodestyle", + "scipy", + "matplotlib", + "mnist", + "numpy", + "tensorflow-probability", + "joblib", + "easydict", + "absl-py", + "six", + ], + extras_require={ + "jax": ["jax>=0.2.9", "jaxlib"], + "tf": ["tensorflow>=2.4.0", "tensorflow-probability", "tensorflow-datasets"], + "pytorch": ["torch>=1.7.0", "torchvision>=0.8.0"], + }, + packages=find_packages(), +) diff --git a/tests_tf/test_attack_bundling.py b/tests_tf/test_attack_bundling.py deleted file mode 100644 index 89a92c0bf..000000000 --- a/tests_tf/test_attack_bundling.py +++ /dev/null @@ -1,71 +0,0 @@ -""" -Tests for cleverhans.attack_bundling -""" -import numpy as np -from cleverhans.attack_bundling import AttackConfig -from cleverhans.attack_bundling import Misclassify -from cleverhans.attack_bundling import unfinished_attack_configs - - -def test_unfinished_attack_configs(): - """ - Test that tracking of which attack configs are unfinished is correct - """ - - new_work_goal = {} - work_before = {} - run_counts = {} - - expected_unfinished = [] - expected_finished = [] - - easy_finished = AttackConfig(None, None) - new_work_goal[easy_finished] = 1 - work_before[easy_finished] = np.array([0, 0]) - run_counts[easy_finished] = np.array([1, 1]) - expected_finished.append(easy_finished) - - easy_unfinished = AttackConfig(None, None) - new_work_goal[easy_unfinished] = 1 - work_before[easy_unfinished] = np.array([0, 0]) - run_counts[easy_unfinished] = np.array([0, 0]) - expected_unfinished.append(easy_unfinished) - - only_partly_finished = AttackConfig(None, None) - new_work_goal[only_partly_finished] = 1 - work_before[only_partly_finished] = np.array([0, 0]) - run_counts[only_partly_finished] = np.array([1, 0]) - expected_unfinished.append(only_partly_finished) - - work_not_new = AttackConfig(None, None) - new_work_goal[work_not_new] = 1 - work_before[work_not_new] = np.array([1, 1]) - run_counts[work_not_new] = np.array([1, 1]) - expected_unfinished.append(work_not_new) - - actual_unfinished = unfinished_attack_configs(new_work_goal, work_before, - run_counts) - - assert all(e in actual_unfinished for e in expected_unfinished) - assert all(e not in actual_unfinished for e in expected_finished) - - -def test_misclassify_request_examples(): - """ - Test Misclassify.request_examples - """ - cfg = AttackConfig(None, None) - goal = Misclassify(new_work_goal={cfg: 1}) - correctness = np.array([0, 1, 0, 1, 0, 1, 0, 1, 0, 1], dtype=np.bool) - run_counts = np.array([1, 1, 1, 0, 0, 0, 1, 1, 1, 0], dtype=np.int64) - criteria = {'correctness': correctness} - batch_size = 3 - idxs = goal.request_examples(cfg, criteria, {cfg: run_counts}, batch_size) - assert idxs.shape == (batch_size,) - idxs = list(idxs) - for already_misclassified in [0, 2, 4, 6, 8]: - assert already_misclassified not in idxs - for already_run in [1, 7]: - assert already_run not in idxs - for needed in [3, 5, 9]: - assert needed in idxs diff --git a/tests_tf/test_attacks.py b/tests_tf/test_attacks.py deleted file mode 100644 index 17d4177ee..000000000 --- a/tests_tf/test_attacks.py +++ /dev/null @@ -1,1714 +0,0 @@ -# pylint: disable=missing-docstring -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - -import functools -import unittest - -import numpy as np -from nose.plugins.skip import SkipTest -import tensorflow as tf -# pylint bug on next line -import tensorflow.contrib.slim as slim # pylint: disable=no-name-in-module - -from cleverhans.devtools.checks import CleverHansTest -from cleverhans import attacks -from cleverhans.attacks import Attack, SPSA -from cleverhans.attacks import FastGradientMethod -from cleverhans.attacks import BasicIterativeMethod -from cleverhans.attacks import MomentumIterativeMethod -from cleverhans.attacks import VirtualAdversarialMethod -from cleverhans.attacks import SaliencyMapMethod -from cleverhans.attacks import CarliniWagnerL2 -from cleverhans.attacks import ElasticNetMethod -from cleverhans.attacks import DeepFool -from cleverhans.attacks import MadryEtAl -from cleverhans.attacks import ProjectedGradientDescent -from cleverhans.attacks import FastFeatureAdversaries -from cleverhans.attacks import LBFGS -from cleverhans.attacks import SpatialTransformationMethod -from cleverhans.attacks import HopSkipJumpAttack -from cleverhans.attacks import SparseL1Descent -from cleverhans.initializers import HeReLuNormalInitializer -from cleverhans.model import Model - - -class SimpleModel(Model): - """ - A very simple neural network - """ - - def __init__(self, scope='simple', nb_classes=2, **kwargs): - del kwargs - Model.__init__(self, scope, nb_classes, locals()) - - def fprop(self, x, **kwargs): - del kwargs - with tf.variable_scope(self.scope, reuse=tf.AUTO_REUSE): - w1 = tf.constant([[1.5, .3], [-2, 0.3]], - dtype=tf.as_dtype(x.dtype)) - w2 = tf.constant([[-2.4, 1.2], [0.5, -2.3]], - dtype=tf.as_dtype(x.dtype)) - h1 = tf.nn.sigmoid(tf.matmul(x, w1)) - res = tf.matmul(h1, w2) - return {self.O_LOGITS: res, - self.O_PROBS: tf.nn.softmax(res)} - - -class TrivialModel(Model): - """ - A linear model with two weights - """ - - def __init__(self, scope='trivial', nb_classes=2, **kwargs): - del kwargs - Model.__init__(self, scope, nb_classes, locals()) - - def fprop(self, x, **kwargs): - del kwargs - with tf.variable_scope(self.scope, reuse=tf.AUTO_REUSE): - w1 = tf.constant([[1, -1]], dtype=tf.float32) - res = tf.matmul(x, w1) - return {self.O_LOGITS: res, - self.O_PROBS: tf.nn.softmax(res)} - - -class DummyModel(Model): - """ - A simple model based on slim - """ - - def __init__(self, scope='dummy_model', nb_classes=10, **kwargs): - del kwargs - Model.__init__(self, scope, nb_classes, locals()) - - def fprop(self, x, **kwargs): - del kwargs - with tf.variable_scope(self.scope, reuse=tf.AUTO_REUSE): - net = slim.flatten(x) - net = slim.fully_connected(net, 60) - logits = slim.fully_connected(net, 10, activation_fn=None) - return {self.O_LOGITS: logits, - self.O_PROBS: tf.nn.softmax(logits)} - - -class TestAttackClassInitArguments(CleverHansTest): - - def test_model(self): - sess = tf.Session() - - # Exception is thrown when model does not have __call__ attribute - with self.assertRaises(Exception) as context: - model = tf.placeholder(tf.float32, shape=(None, 10)) - Attack(model, sess=sess) - self.assertTrue(context.exception) - - def test_sess(self): - # Test that it is permitted to provide no session. - # The session still needs to be created prior to running the attack. - # TODO: does anyone know why we need to make an unused session and put it in a with statement? - with tf.Session(): - Attack(Model('model', 10, {}), sess=None) - - def test_sess_generate_np(self): - model = Model('model', 10, {}) - - class DummyAttack(Attack): - def generate(self, x, **kwargs): - return x - - # Test that generate_np is NOT permitted without a session. - # The session still needs to be created prior to running the attack. - # TODO: does anyone know why we need to make an unused session and put it in a with statement? - with tf.Session(): - attack = DummyAttack(model, sess=None) - with self.assertRaises(Exception) as context: - attack.generate_np(0.) - self.assertTrue(context.exception) - - -class TestParseParams(CleverHansTest): - def test_parse(self): - sess = tf.Session() - - test_attack = Attack(Model('model', 10, {}), sess=sess) - self.assertTrue(test_attack.parse_params({})) - - -class TestVirtualAdversarialMethod(CleverHansTest): - def setUp(self): - super(TestVirtualAdversarialMethod, self).setUp() - - self.sess = tf.Session() - self.sess.as_default() - self.model = DummyModel('virtual_adv_dummy_model') - self.attack = VirtualAdversarialMethod(self.model, sess=self.sess) - - # initialize model - with tf.name_scope('virtual_adv_dummy_model'): - self.model.get_probs(tf.placeholder(tf.float32, shape=(None, 1000))) - self.sess.run(tf.global_variables_initializer()) - - def test_parse_params(self): - self.attack.parse_params() - # test default values - self.assertEqual(self.attack.eps, 2.0) - self.assertEqual(self.attack.num_iterations, 1) - self.assertEqual(self.attack.xi, 1e-6) - self.assertEqual(self.attack.clip_min, None) - self.assertEqual(self.attack.clip_max, None) - - def test_generate_np(self): - x_val = np.random.rand(100, 1000) - perturbation = self.attack.generate_np(x_val) - x_val - perturbation_norm = np.sqrt(np.sum(perturbation ** 2, axis=1)) - # test perturbation norm - self.assertClose(perturbation_norm, self.attack.eps) - - -class CommonAttackProperties(CleverHansTest): - """ - Abstract base class shared by the tests for many attacks that want - to check the same properties. - """ - - def setUp(self): - # Inheritance doesn't really work with tests. - # nosetests always wants to run this class because it is a - # CleverHansTest subclass, but this class is meant to just - # be abstract. - # Before this class was the tests for FastGradientMethod but - # people kept inheriting from it for other attacks so it was - # impossible to write tests specifically for FastGradientMethod. - # pylint: disable=unidiomatic-typecheck - if type(self) is CommonAttackProperties: - raise SkipTest() - - super(CommonAttackProperties, self).setUp() - self.sess = tf.Session() - self.model = SimpleModel() - - def generate_adversarial_examples_np(self, ord, eps, **kwargs): - x_val = np.random.rand(100, 2) - x_val = np.array(x_val, dtype=np.float32) - - x_adv = self.attack.generate_np(x_val, eps=eps, ord=ord, - clip_min=-5, clip_max=5, **kwargs) - if ord == np.inf: - delta = np.max(np.abs(x_adv - x_val), axis=1) - elif ord == 1: - delta = np.sum(np.abs(x_adv - x_val), axis=1) - elif ord == 2: - delta = np.sum(np.square(x_adv - x_val), axis=1) ** .5 - - return x_val, x_adv, delta - - def help_generate_np_gives_adversarial_example(self, ord, eps=.5, - **kwargs): - x_val, x_adv, delta = self.generate_adversarial_examples_np(ord, eps, - **kwargs) - self.assertLess(np.max(np.abs(delta-eps)), 1e-3) - orig_labs = np.argmax(self.sess.run(self.model.get_logits(x_val)), axis=1) - new_labs = np.argmax(self.sess.run(self.model.get_logits(x_adv)), axis=1) - self.assertLess(np.max(np.mean(orig_labs == new_labs)), .5) - - def test_invalid_input(self): - x_val = -np.ones((2, 2), dtype='float32') - with self.assertRaises(tf.errors.InvalidArgumentError) as context: - self.attack.generate_np(x_val, eps=1., clip_min=0., clip_max=1.) - self.assertTrue(context.exception) - - def test_generate_np_gives_adversarial_example_linfinity(self): - self.help_generate_np_gives_adversarial_example(np.infty) - - def test_generate_np_gives_adversarial_example_l1(self): - self.help_generate_np_gives_adversarial_example(1) - - def test_generate_np_gives_adversarial_example_l2(self): - self.help_generate_np_gives_adversarial_example(2) - - def test_generate_respects_dtype(self): - self.attack = FastGradientMethod(self.model, sess=self.sess, - dtypestr='float64') - x = tf.placeholder(dtype=tf.float64, shape=(100, 2)) - x_adv = self.attack.generate(x) - self.assertEqual(x_adv.dtype, tf.float64) - - def test_targeted_generate_np_gives_adversarial_example(self): - random_labs = np.random.random_integers(0, 1, 100) - random_labs_one_hot = np.zeros((100, 2)) - random_labs_one_hot[np.arange(100), random_labs] = 1 - - try: - _, x_adv, delta = self.generate_adversarial_examples_np( - eps=.5, ord=np.inf, y_target=random_labs_one_hot) - except NotImplementedError: - raise SkipTest() - - self.assertLessEqual(np.max(delta), 0.5001) - - new_labs = np.argmax(self.sess.run(self.model.get_logits(x_adv)), axis=1) - self.assertTrue(np.mean(random_labs == new_labs) > 0.7) - - def test_generate_np_can_be_called_with_different_eps(self): - x_val = np.random.rand(100, 2) - x_val = np.array(x_val, dtype=np.float32) - - for eps in [0.1, 0.2, 0.3, 0.4]: - x_adv = self.attack.generate_np(x_val, eps=eps, ord=np.inf, - clip_min=-5.0, clip_max=5.0) - - delta = np.max(np.abs(x_adv - x_val), axis=1) - self.assertLessEqual(np.max(delta), eps+1e-4) - - def test_generate_can_be_called_with_different_eps(self): - # It is crtical that this test uses generate and not generate_np. - # All the other tests use generate_np. Even though generate_np calls - # generate, it does so in a very standardized way, e.g. with eps - # always converted to a tensorflow placeholder, so the other tests - # based on generate_np do not exercise the generate API very well. - x_val = np.random.rand(100, 2) - x_val = np.array(x_val, dtype=np.float32) - x = tf.placeholder(tf.float32, x_val.shape) - - for eps in [0.1, 0.2, 0.3, 0.4]: - x_adv = self.attack.generate(x, eps=eps, ord=np.inf, - clip_min=-5.0, clip_max=5.0) - x_adv = self.sess.run(x_adv, feed_dict={x: x_val}) - - delta = np.max(np.abs(x_adv - x_val), axis=1) - self.assertLessEqual(np.max(delta), eps + 1e-4) - - def test_generate_np_clip_works_as_expected(self): - x_val = np.random.rand(100, 2) - x_val = np.array(x_val, dtype=np.float32) - - x_adv = self.attack.generate_np(x_val, eps=0.5, ord=np.inf, - clip_min=-0.2, clip_max=0.1, - sanity_checks=False) - - self.assertClose(np.min(x_adv), -0.2) - self.assertClose(np.max(x_adv), 0.1) - - -class TestFastGradientMethod(CommonAttackProperties): - - def setUp(self): - super(TestFastGradientMethod, self).setUp() - - self.attack = FastGradientMethod(self.model, sess=self.sess) - - -class TestOptimizeLinear(CleverHansTest): - """ - Tests for the `optimize_linear` function - """ - - def setUp(self): - super(TestOptimizeLinear, self).setUp() - self.sess = tf.Session() - self.model = SimpleModel() - - def test_optimize_linear_linf(self): - - grad = tf.placeholder(tf.float32, shape=[1, 2]) - - # Build the graph for the attack - eta = attacks.optimize_linear(grad, eps=1., ord=np.inf) - objective = tf.reduce_sum(grad * eta) - - grad_val = np.array([[1., -2.]]) - eta, objective = self.sess.run([eta, objective], - feed_dict={grad: grad_val}) - - # Make sure the objective is optimal. - # This is the solution obtained by doing the algebra by hand. - self.assertClose(objective, np.abs(grad_val).sum()) - # Make sure the constraint is respected. - # Also, for a linear function, the constraint will always be tight. - self.assertClose(np.abs(eta), 1.) - - def test_optimize_linear_l2(self): - - grad = tf.placeholder(tf.float32, shape=[1, 2]) - - # Build the graph for the attack - eta = attacks.optimize_linear(grad, eps=1., ord=2) - objective = tf.reduce_sum(grad * eta) - - grad_val = np.array([[np.sqrt(.5), -np.sqrt(.5)]]) - eta, objective = self.sess.run([eta, objective], - feed_dict={grad: grad_val}) - - # Make sure the objective is optimal. - # This is the solution obtained by doing the algebra by hand. - self.assertClose(objective, 1.) - # Make sure the constraint is respected. - # Also, for a linear function, the constraint will always be tight. - self.assertClose(np.sqrt(np.square(eta).sum()), 1.) - - def test_optimize_linear_l1(self): - - # This test makes sure that `optimize_linear` actually finds the optimal - # perturbation for ord=1. - # A common misconcpetion is that FGM for ord=1 consists of dividing - # the gradient by its L1 norm. - # If you do that for the problem in this unit test, you'll get an - # objective function value of ~1.667. The optimal result is 2. - - # We need just one example in the batch and two features to show the - # common misconception is suboptimal. - grad = tf.placeholder(tf.float32, shape=[1, 2]) - - # Build the graph for the attack - eta = attacks.optimize_linear(grad, eps=1., ord=1) - objective = tf.reduce_sum(grad * eta) - - # Make sure the largest entry of the gradient for the test case is - # negative, to catch - # the potential bug where we forget to handle the sign of the gradient - eta, objective = self.sess.run([eta, objective], - feed_dict={grad: np.array([[1., -2.]])}) - - # Make sure the objective is optimal. - # This is the solution obtained by doing the algebra by hand. - self.assertClose(objective, 2.) - # Make sure the constraint is respected. - # Also, for a linear function, the constraint will always be tight. - self.assertClose(np.abs(eta).sum(), 1.) - - def test_optimize_linear_l1_ties(self): - - # This test makes sure that `optimize_linear` handles ties in gradient - # magnitude correctly when ord=1. - - # We need just one example in the batch and two features to construct - # a tie. - grad = tf.placeholder(tf.float32, shape=[1, 2]) - - # Build the graph for the attack - eta = attacks.optimize_linear(grad, eps=1., ord=1) - objective = tf.reduce_sum(grad * eta) - - # Run a test case with a tie for largest absolute value. - # Make one feature negative to make sure we're checking for ties in - # absolute value, not raw value. - eta, objective = self.sess.run([eta, objective], - feed_dict={grad: np.array([[2., -2.]])}) - - # Make sure the objective is optimal. - # This is the solution obtained by doing the algebra by hand. - self.assertClose(objective, 2.) - # Make sure the constraint is respected. - # Also, for a linear function, the constraint will always be tight. - self.assertClose(np.abs(eta).sum(), 1.) - - -class TestSPSA(CleverHansTest): - def setUp(self): - super(TestSPSA, self).setUp() - - self.sess = tf.Session() - self.model = SimpleModel() - self.attack = SPSA(self.model, sess=self.sess) - - def test_attack_strength(self): - n_samples = 10 - x_val = np.random.rand(n_samples, 2) - x_val = np.array(x_val, dtype=np.float32) - - # The SPSA attack currently uses non-one-hot labels - # TODO: change this to use standard cleverhans label conventions - feed_labs = np.random.randint(0, 2, n_samples) - - x_input = tf.placeholder(tf.float32, shape=(1, 2)) - y_label = tf.placeholder(tf.int32, shape=(1,)) - - x_adv_op = self.attack.generate( - x_input, y=y_label, - epsilon=.5, num_steps=100, batch_size=64, spsa_iters=1, - clip_min=0., clip_max=1. - ) - - all_x_adv = [] - for i in range(n_samples): - x_adv_np = self.sess.run(x_adv_op, feed_dict={ - x_input: np.expand_dims(x_val[i], axis=0), - y_label: np.expand_dims(feed_labs[i], axis=0), - }) - all_x_adv.append(x_adv_np[0]) - - x_adv = np.vstack(all_x_adv) - new_labs = np.argmax(self.sess.run(self.model.get_logits(x_adv)), axis=1) - self.assertTrue(np.mean(feed_labs == new_labs) < 0.1) - - def test_attack_strength_np(self): - # Same test as test_attack_strength, but uses generate_np interface - n_samples = 10 - x_val = np.random.rand(n_samples, 2) - x_val = np.array(x_val, dtype=np.float32) - - feed_labs = np.random.randint(0, 2, n_samples, dtype='int32') - - all_x_adv = [] - for i in range(n_samples): - x_adv_np = self.attack.generate_np( - np.expand_dims(x_val[i], axis=0), - y=np.expand_dims(feed_labs[i], axis=0), - eps=.5, nb_iter=100, spsa_samples=64, spsa_iters=1, - clip_min=0., clip_max=1. - ) - all_x_adv.append(x_adv_np[0]) - - x_adv = np.vstack(all_x_adv) - new_labs = np.argmax(self.sess.run(self.model.get_logits(x_adv)), axis=1) - self.assertLess(np.mean(feed_labs == new_labs), 0.1) - - def test_attack_strength_np_batched(self): - # Same test as test_attack_strength_np, but batched - n_samples = 10 - x_val = np.random.rand(n_samples, 2) - x_val = np.array(x_val, dtype=np.float32) - - feed_labs = np.random.randint(0, 2, n_samples, dtype='int32') - x_adv = self.attack.generate_np( - x_val, y=feed_labs, eps=.5, nb_iter=100, spsa_samples=64, - spsa_iters=1, clip_min=0., clip_max=1.) - - new_labs = np.argmax(self.sess.run(self.model.get_logits(x_adv)), axis=1) - self.assertLess(np.mean(feed_labs == new_labs), 0.1) - - def test_label_argument_int64(self): - x_val = np.random.rand(1, 2) - x_val = np.array(x_val, dtype=np.float32) - - # Try calling alternating with int32 and int64 and ensure it works - for dtype in [np.int32, np.int64, np.int32, np.int64]: - self.attack.generate_np(x_val, y=np.zeros(1, dtype=dtype), eps=.5, - nb_iter=5, spsa_samples=64, - spsa_iters=1, clip_min=0., clip_max=1.) - - -class TestProjectedGradientDescent(CommonAttackProperties): - def setUp(self): - super(TestProjectedGradientDescent, self).setUp() - - self.sess = tf.Session() - self.model = SimpleModel() - self.attack = ProjectedGradientDescent(self.model, sess=self.sess) - - def test_generate_np_gives_adversarial_example_linfinity(self): - self.help_generate_np_gives_adversarial_example(ord=np.infty, eps=.5, - nb_iter=20) - - def test_generate_np_gives_adversarial_example_l1(self): - try: - self.help_generate_np_gives_adversarial_example(ord=1, eps=.5, - nb_iter=20) - except NotImplementedError: - raise SkipTest() - - def test_generate_np_gives_adversarial_example_l2(self): - self.help_generate_np_gives_adversarial_example(ord=2, eps=.5, - nb_iter=20) - - def test_do_not_reach_lp_boundary(self): - """ - Make sure that iterative attack don't reach boundary of Lp - neighbourhood if nb_iter * eps_iter is relatively small compared to - epsilon. - """ - for ord in [1, 2, np.infty]: - try: - _, _, delta = self.generate_adversarial_examples_np( - ord=ord, eps=.5, nb_iter=10, eps_iter=.01) - except NotImplementedError: - # Don't raise SkipTest because it will skip the rest of the for loop - continue - self.assertTrue(np.max(0.5 - delta) > 0.25) - - def test_attack_strength_linf(self): - """ - If clipping is not done at each iteration (not passing clip_min and - clip_max to fgm), this attack fails by - np.mean(orig_labels == new_labels) == .39. - """ - x_val = np.random.rand(100, 2) - x_val = np.array(x_val, dtype=np.float32) - - # sanity checks turned off because this test initializes outside - # the valid range. - x_adv = self.attack.generate_np(x_val, eps=1.0, ord=np.inf, - clip_min=0.5, clip_max=0.7, - nb_iter=5, sanity_checks=False) - - orig_labs = np.argmax(self.sess.run(self.model.get_logits(x_val)), axis=1) - new_labs = np.argmax(self.sess.run(self.model.get_logits(x_adv)), axis=1) - self.assertLess(np.mean(orig_labs == new_labs), 0.1) - - def test_attack_strength_l2(self): - x_val = np.random.rand(100, 2) - x_val = np.array(x_val, dtype=np.float32) - - # sanity checks turned off because this test initializes outside - # the valid range. - x_adv = self.attack.generate_np(x_val, eps=1, ord=2, - clip_min=0.5, clip_max=0.7, - nb_iter=5, sanity_checks=False) - - orig_labs = np.argmax(self.sess.run(self.model.get_logits(x_val)), axis=1) - new_labs = np.argmax(self.sess.run(self.model.get_logits(x_adv)), axis=1) - self.assertLess(np.mean(orig_labs == new_labs), 0.1) - - def test_grad_clip_l2(self): - x_val = np.random.rand(100, 2) - x_val = np.array(x_val, dtype=np.float32) - - # sanity checks turned off because this test initializes outside - # the valid range. - x_adv = self.attack.generate_np(x_val, eps=1, ord=2, - clip_min=0.5, clip_max=0.7, clip_grad=True, - nb_iter=10, sanity_checks=False) - - orig_labs = np.argmax(self.sess.run(self.model.get_logits(x_val)), axis=1) - new_labs = np.argmax(self.sess.run(self.model.get_logits(x_adv)), axis=1) - self.assertLess(np.mean(orig_labs == new_labs), 0.1) - - def test_clip_eta_linf(self): - x_val = np.random.rand(100, 2) - x_val = np.array(x_val, dtype=np.float32) - - x_adv = self.attack.generate_np(x_val, ord=np.inf, eps=1.0, eps_iter=0.1, - nb_iter=5) - - delta = np.max(np.abs(x_adv - x_val), axis=1) - self.assertLessEqual(np.max(delta), 1.) - - def test_clip_eta_l2(self): - x_val = np.random.rand(100, 2) - x_val = np.array(x_val, dtype=np.float32) - - x_adv = self.attack.generate_np(x_val, ord=2, eps=1.0, eps_iter=0.1, - nb_iter=5) - - delta = np.sqrt(np.sum(np.square(x_adv - x_val), axis=1)) - # this projection is less numerically stable so give it some slack - self.assertLessEqual(np.max(delta), 1. + 1e-6) - - def test_generate_np_gives_clipped_adversarial_examples(self): - x_val = np.random.rand(100, 2) - x_val = np.array(x_val, dtype=np.float32) - - for ord in [1, 2, np.infty]: - try: - x_adv = self.attack.generate_np(x_val, ord=ord, eps=1.0, eps_iter=0.1, - nb_iter=5, - clip_min=-0.2, clip_max=0.3, - sanity_checks=False) - - self.assertLess(-0.201, np.min(x_adv)) - self.assertLess(np.max(x_adv), .301) - except NotImplementedError: - # Don't raise SkipTest because it will skip the rest of the for loop - continue - - def test_generate_np_does_not_cache_graph_computation_for_nb_iter(self): - x_val = np.random.rand(100, 2) - x_val = np.array(x_val, dtype=np.float32) - - # Call it once - x_adv = self.attack.generate_np(x_val, eps=1.0, ord=np.inf, - clip_min=-5.0, clip_max=5.0, - nb_iter=10) - - # original labels - np.argmax(self.sess.run(self.model.get_logits(x_val)), axis=1) - # new labels - np.argmax(self.sess.run(self.model.get_logits(x_adv)), axis=1) - - # Call it again - ok = [False] - old_grads = tf.gradients - try: - def fn(*x, **y): - ok[0] = True - return old_grads(*x, **y) - - tf.gradients = fn - - x_adv = self.attack.generate_np(x_val, eps=1.0, ord=np.inf, - clip_min=-5.0, clip_max=5.0, - nb_iter=11) - finally: - tf.gradients = old_grads - - # original labels - np.argmax(self.sess.run(self.model.get_logits(x_val)), axis=1) - # new labels - np.argmax(self.sess.run(self.model.get_logits(x_adv)), axis=1) - - self.assertTrue(ok[0]) - - def test_multiple_initial_random_step(self): - """ - This test generates multiple adversarial examples until an adversarial - example is generated with a different label compared to the original - label. This is the procedure suggested in Madry et al. (2017). - - This test will fail if an initial random step is not taken (error>0.5). - """ - x_val = np.array(np.random.rand(100, 2), dtype=np.float32) - - orig_labs = np.argmax(self.sess.run(self.model.get_logits(x_val)), axis=1) - new_labs_multi = orig_labs.copy() - - # Generate multiple adversarial examples - for _ in range(10): - x_adv = self.attack.generate_np(x_val, eps=.5, eps_iter=0.05, - clip_min=0.5, clip_max=0.7, - nb_iter=2, sanity_checks=False) - new_labs = np.argmax(self.sess.run(self.model.get_logits(x_adv)), axis=1) - - # Examples for which we have not found adversarial examples - I = (orig_labs == new_labs_multi) - new_labs_multi[I] = new_labs[I] - - self.assertLess(np.mean(orig_labs == new_labs_multi), 0.5) - - -class TestSparseL1Descent(CleverHansTest): - def setUp(self): - super(TestSparseL1Descent, self).setUp() - - self.sess = tf.Session() - self.model = SimpleModel() - self.attack = SparseL1Descent(self.model, sess=self.sess) - - def generate_adversarial_examples_np(self, eps, **kwargs): - x_val = np.random.rand(100, 2) - x_val = np.array(x_val, dtype=np.float32) - - x_adv = self.attack.generate_np(x_val, eps=eps, - clip_min=-5, clip_max=5, **kwargs) - delta = np.sum(np.abs(x_adv - x_val), axis=1) - - return x_val, x_adv, delta - - def help_generate_np_gives_adversarial_example(self, eps=2.0, **kwargs): - x_val, x_adv, delta = self.generate_adversarial_examples_np(eps, **kwargs) - self.assertLess(np.max(np.abs(delta-eps)), 1e-3) - orig_labs = np.argmax(self.sess.run(self.model.get_logits(x_val)), axis=1) - new_labs = np.argmax(self.sess.run(self.model.get_logits(x_adv)), axis=1) - self.assertLess(np.max(np.mean(orig_labs == new_labs)), .5) - - def test_invalid_input(self): - x_val = -np.ones((2, 2), dtype='float32') - with self.assertRaises(tf.errors.InvalidArgumentError) as context: - self.attack.generate_np(x_val, eps=10.0, clip_min=0., clip_max=1.) - self.assertTrue(context.exception) - - def test_generate_np_gives_adversarial_example(self): - self.help_generate_np_gives_adversarial_example() - - def test_targeted_generate_np_gives_adversarial_example(self): - random_labs = np.random.random_integers(0, 1, 100) - random_labs_one_hot = np.zeros((100, 2)) - random_labs_one_hot[np.arange(100), random_labs] = 1 - - _, x_adv, delta = self.generate_adversarial_examples_np( - eps=10, y_target=random_labs_one_hot) - - self.assertLessEqual(np.max(delta), 10.001) - - new_labs = np.argmax(self.sess.run(self.model.get_logits(x_adv)), axis=1) - self.assertTrue(np.mean(random_labs == new_labs) > 0.7) - - def test_generate_np_can_be_called_with_different_eps(self): - x_val = np.random.rand(100, 2) - x_val = np.array(x_val, dtype=np.float32) - - for eps in [10, 20, 30, 40]: - x_adv = self.attack.generate_np(x_val, eps=eps, - clip_min=-5.0, clip_max=5.0) - - delta = np.max(np.abs(x_adv - x_val), axis=1) - self.assertLessEqual(np.max(delta), eps+1e-4) - - def test_generate_can_be_called_with_different_eps(self): - # It is crtical that this test uses generate and not generate_np. - # All the other tests use generate_np. Even though generate_np calls - # generate, it does so in a very standardized way, e.g. with eps - # always converted to a tensorflow placeholder, so the other tests - # based on generate_np do not exercise the generate API very well. - x_val = np.random.rand(100, 2) - x_val = np.array(x_val, dtype=np.float32) - x = tf.placeholder(tf.float32, x_val.shape) - - for eps in [10, 20, 30, 40]: - x_adv = self.attack.generate(x, eps=eps, clip_min=-5.0, clip_max=5.0) - x_adv = self.sess.run(x_adv, feed_dict={x: x_val}) - - delta = np.max(np.abs(x_adv - x_val), axis=1) - self.assertLessEqual(np.max(delta), eps + 1e-4) - - def test_generate_np_clip_works_as_expected(self): - x_val = np.random.rand(100, 2) - x_val = np.array(x_val, dtype=np.float32) - - x_adv = self.attack.generate_np(x_val, eps=10, nb_iter=20, rand_init=True, - clip_min=-0.2, clip_max=0.1, - sanity_checks=False) - - self.assertClose(np.min(x_adv), -0.2) - self.assertClose(np.max(x_adv), 0.1) - - def test_do_not_reach_lp_boundary(self): - """ - Make sure that iterative attack don't reach boundary of Lp - neighbourhood if nb_iter * eps_iter is relatively small compared to - epsilon. - """ - - _, _, delta = self.generate_adversarial_examples_np( - eps=.5, nb_iter=10, eps_iter=.01) - - self.assertTrue(np.max(0.5 - delta) > 0.25) - - def test_generate_np_gives_clipped_adversarial_examples(self): - x_val = np.random.rand(100, 2) - x_val = np.array(x_val, dtype=np.float32) - - x_adv = self.attack.generate_np(x_val, eps=1.0, eps_iter=0.1, - nb_iter=5, - clip_min=-0.2, clip_max=0.3, - sanity_checks=False) - - self.assertLess(-0.201, np.min(x_adv)) - self.assertLess(np.max(x_adv), .301) - - def test_generate_np_does_not_cache_graph_computation_for_nb_iter(self): - x_val = np.random.rand(100, 2) - x_val = np.array(x_val, dtype=np.float32) - - # Call it once - x_adv = self.attack.generate_np(x_val, eps=1.0, - clip_min=-5.0, clip_max=5.0, - nb_iter=10) - - # original labels - np.argmax(self.sess.run(self.model.get_logits(x_val)), axis=1) - # new labels - np.argmax(self.sess.run(self.model.get_logits(x_adv)), axis=1) - - # Call it again - ok = [False] - old_grads = tf.gradients - try: - def fn(*x, **y): - ok[0] = True - return old_grads(*x, **y) - - tf.gradients = fn - - x_adv = self.attack.generate_np(x_val, eps=1.0, - clip_min=-5.0, clip_max=5.0, - nb_iter=11) - finally: - tf.gradients = old_grads - - # original labels - np.argmax(self.sess.run(self.model.get_logits(x_val)), axis=1) - # new labels - np.argmax(self.sess.run(self.model.get_logits(x_adv)), axis=1) - - self.assertTrue(ok[0]) - - def test_clip_eta(self): - x_val = np.random.rand(100, 2) - x_val = np.array(x_val, dtype=np.float32) - - x_adv = self.attack.generate_np(x_val, eps=1.0, eps_iter=0.1, nb_iter=5) - - delta = np.sum(np.abs(x_adv - x_val), axis=1) - # this projection is less numerically stable so give it some slack - self.assertLessEqual(np.max(delta), 1. + 1e-6) - - def test_attack_strength(self): - """ - Without clipped gradients, we achieve - np.mean(orig_labels == new_labels) == 0.31. - """ - x_val = np.random.rand(100, 2) - x_val = np.array(x_val, dtype=np.float32) - - # sanity checks turned off because this test initializes outside - # the valid range. - x_adv = self.attack.generate_np(x_val, eps=10.0, rand_init=True, - clip_min=0.5, clip_max=0.7, - nb_iter=10, sanity_checks=False) - - orig_labs = np.argmax(self.sess.run(self.model.get_logits(x_val)), axis=1) - new_labs = np.argmax(self.sess.run(self.model.get_logits(x_adv)), axis=1) - self.assertLess(np.mean(orig_labs == new_labs), 0.5) - self.assertGreater(np.mean(orig_labs == new_labs), 0.2) - - def test_grad_clip(self): - """ - With clipped gradients, we achieve - np.mean(orig_labels == new_labels) == 0.0 - """ - x_val = np.random.rand(100, 2) - x_val = np.array(x_val, dtype=np.float32) - - # sanity checks turned off because this test initializes outside - # the valid range. - x_adv = self.attack.generate_np(x_val, eps=10.0, rand_init=True, - clip_min=0.5, clip_max=0.7, - clip_grad=True, - nb_iter=10, sanity_checks=False) - - orig_labs = np.argmax(self.sess.run(self.model.get_logits(x_val)), axis=1) - new_labs = np.argmax(self.sess.run(self.model.get_logits(x_adv)), axis=1) - self.assertLess(np.mean(orig_labs == new_labs), 0.1) - - def test_sparsity(self): - - # use a model with larger input dimensionality for this test. - self.model = DummyModel('sparse_l1_descent_dummy_model') - self.attack = SparseL1Descent(self.model, sess=self.sess) - - # initialize model - with tf.name_scope('sparse_l1_descent_dummy_model'): - self.model.get_probs(tf.placeholder(tf.float32, shape=(None, 1000))) - self.sess.run(tf.global_variables_initializer()) - - x_val = np.random.rand(100, 1000) - x_val = np.array(x_val, dtype=np.float32) - - for q in [1, 9, 25.8, 50, 75.4, 90.2, 99, 99.9]: - x_adv = self.attack.generate_np(x_val, eps=5.0, grad_sparsity=q, - nb_iter=1, sanity_checks=False) - - numzero = np.sum(x_adv - x_val == 0, axis=-1) - self.assertAlmostEqual(q * 1000.0 / 100.0, np.mean(numzero), delta=1) - - def test_grad_sparsity_checks(self): - # test that the attacks allows `grad_sparsity` to be specified as a scalar - # in (0, 100) or as a vector. - - x_val = np.random.rand(100, 2) - x_val = np.array(x_val, dtype=np.float32) - - # scalar values out of range - with self.assertRaises(ValueError) as context: - self.attack.generate(x_val, sanity_checks=False, grad_sparsity=0) - self.assertTrue(context.exception) - - with self.assertRaises(ValueError) as context: - self.attack.generate(x_val, sanity_checks=False, grad_sparsity=100) - self.assertTrue(context.exception) - - # sparsity as 2D array should fail - with self.assertRaises(ValueError) as context: - gs = tf.random.uniform(shape=(100, 2), minval=90, maxval=99) - self.attack.generate(x_val, sanity_checks=False, grad_sparsity=gs) - self.assertTrue(context.exception) - - # sparsity as 1D array should succeed - gs = tf.random.uniform(shape=(100,), minval=90, maxval=99) - x_adv = self.attack.generate(x_val, sanity_checks=False, grad_sparsity=gs) - self.assertTrue(np.array_equal(x_adv.get_shape().as_list(), [100, 2])) - - # sparsity vector of wrong size should fail - with self.assertRaises(ValueError) as context: - gs = tf.random.uniform(shape=(101,), minval=90, maxval=99) - x_adv = self.attack.generate(x_val, sanity_checks=False, grad_sparsity=gs) - self.assertTrue(context.exception) - - -class TestCarliniWagnerL2(CleverHansTest): - def setUp(self): - super(TestCarliniWagnerL2, self).setUp() - - self.sess = tf.Session() - self.model = SimpleModel() - self.attack = CarliniWagnerL2(self.model, sess=self.sess) - - def test_generate_np_untargeted_gives_adversarial_example(self): - x_val = np.random.rand(100, 2) - x_val = np.array(x_val, dtype=np.float32) - - x_adv = self.attack.generate_np(x_val, max_iterations=100, - binary_search_steps=3, - initial_const=1, - clip_min=-5, clip_max=5, - batch_size=10) - - orig_labs = np.argmax(self.sess.run(self.model.get_logits(x_val)), axis=1) - new_labs = np.argmax(self.sess.run(self.model.get_logits(x_adv)), axis=1) - - self.assertTrue(np.mean(orig_labs == new_labs) < 0.1) - - def test_generate_np_targeted_gives_adversarial_example(self): - x_val = np.random.rand(100, 2) - x_val = np.array(x_val, dtype=np.float32) - - feed_labs = np.zeros((100, 2)) - feed_labs[np.arange(100), np.random.randint(0, 1, 100)] = 1 - x_adv = self.attack.generate_np(x_val, max_iterations=100, - binary_search_steps=3, - initial_const=1, - clip_min=-5, clip_max=5, - batch_size=100, y_target=feed_labs) - - new_labs = np.argmax(self.sess.run(self.model.get_logits(x_adv)), axis=1) - - self.assertTrue(np.mean(np.argmax(feed_labs, axis=1) == new_labs) - > 0.9) - - def test_generate_gives_adversarial_example(self): - - x_val = np.random.rand(100, 2) - x_val = np.array(x_val, dtype=np.float32) - - orig_labs = np.argmax(self.sess.run(self.model.get_logits(x_val)), axis=1) - feed_labs = np.zeros((100, 2)) - feed_labs[np.arange(100), orig_labs] = 1 - x = tf.placeholder(tf.float32, x_val.shape) - y = tf.placeholder(tf.float32, feed_labs.shape) - - x_adv_p = self.attack.generate(x, max_iterations=100, - binary_search_steps=3, - initial_const=1, - clip_min=-5, clip_max=5, - batch_size=100, y=y) - self.assertEqual(x_val.shape, x_adv_p.shape) - x_adv = self.sess.run(x_adv_p, {x: x_val, y: feed_labs}) - - new_labs = np.argmax(self.sess.run(self.model.get_logits(x_adv)), axis=1) - - self.assertTrue(np.mean(orig_labs == new_labs) < 0.1) - - def test_generate_np_gives_clipped_adversarial_examples(self): - x_val = np.random.rand(100, 2) - x_val = np.array(x_val, dtype=np.float32) - - x_adv = self.attack.generate_np(x_val, max_iterations=10, - binary_search_steps=1, - learning_rate=1e-3, - initial_const=1, - clip_min=-0.2, clip_max=0.3, - batch_size=100) - - self.assertTrue(-0.201 < np.min(x_adv)) - self.assertTrue(np.max(x_adv) < .301) - - def test_generate_np_high_confidence_targeted_examples(self): - - trivial_model = TrivialModel() - - for CONFIDENCE in [0, 2.3]: - x_val = np.random.rand(10, 1) - .5 - x_val = np.array(x_val, dtype=np.float32) - - feed_labs = np.zeros((10, 2)) - feed_labs[np.arange(10), np.random.randint(0, 2, 10)] = 1 - attack = CarliniWagnerL2(trivial_model, sess=self.sess) - x_adv = attack.generate_np(x_val, - max_iterations=100, - binary_search_steps=2, - learning_rate=1e-2, - initial_const=1, - clip_min=-10, clip_max=10, - confidence=CONFIDENCE, - y_target=feed_labs, - batch_size=10) - - new_labs = self.sess.run(trivial_model.get_logits(x_adv)) - - good_labs = new_labs[np.arange(10), np.argmax(feed_labs, axis=1)] - bad_labs = new_labs[np.arange( - 10), 1 - np.argmax(feed_labs, axis=1)] - - self.assertClose(CONFIDENCE, np.min(good_labs - bad_labs), - atol=1e-1) - self.assertTrue(np.mean(np.argmax(new_labs, axis=1) == - np.argmax(feed_labs, axis=1)) > .9) - - def test_generate_np_high_confidence_untargeted_examples(self): - - trivial_model = TrivialModel() - - for CONFIDENCE in [0, 2.3]: - x_val = np.random.rand(10, 1) - .5 - x_val = np.array(x_val, dtype=np.float32) - - orig_labs = np.argmax( - self.sess.run(trivial_model.get_logits(x_val)), axis=1) - attack = CarliniWagnerL2(trivial_model, sess=self.sess) - x_adv = attack.generate_np(x_val, - max_iterations=100, - binary_search_steps=2, - learning_rate=1e-2, - initial_const=1, - clip_min=-10, clip_max=10, - confidence=CONFIDENCE, - batch_size=10) - - new_labs = self.sess.run(trivial_model.get_logits(x_adv)) - - good_labs = new_labs[np.arange(10), 1 - orig_labs] - bad_labs = new_labs[np.arange(10), orig_labs] - - self.assertTrue(np.mean(np.argmax(new_labs, axis=1) == orig_labs) - == 0) - self.assertTrue(np.isclose( - 0, np.min(good_labs - (bad_labs + CONFIDENCE)), atol=1e-1)) - - -class TestElasticNetMethod(CleverHansTest): - def setUp(self): - super(TestElasticNetMethod, self).setUp() - - self.sess = tf.Session() - self.model = SimpleModel() - self.attack = ElasticNetMethod(self.model, sess=self.sess) - - def test_generate_np_untargeted_gives_adversarial_example(self): - x_val = np.random.rand(100, 2) - x_val = np.array(x_val, dtype=np.float32) - - x_adv = self.attack.generate_np(x_val, max_iterations=100, - binary_search_steps=3, - initial_const=1, - clip_min=-5, clip_max=5, - batch_size=10) - - orig_labs = np.argmax(self.sess.run(self.model.get_logits(x_val)), axis=1) - new_labs = np.argmax(self.sess.run(self.model.get_logits(x_adv)), axis=1) - - self.assertTrue(np.mean(orig_labs == new_labs) < 0.1) - - def test_generate_np_targeted_gives_adversarial_example(self): - x_val = np.random.rand(100, 2) - x_val = np.array(x_val, dtype=np.float32) - - feed_labs = np.zeros((100, 2)) - feed_labs[np.arange(100), np.random.randint(0, 1, 100)] = 1 - x_adv = self.attack.generate_np(x_val, max_iterations=100, - binary_search_steps=3, - initial_const=1, - clip_min=-5, clip_max=5, - batch_size=100, y_target=feed_labs) - - new_labs = np.argmax(self.sess.run(self.model.get_logits(x_adv)), axis=1) - - self.assertTrue(np.mean(np.argmax(feed_labs, axis=1) == new_labs) > - 0.9) - - def test_generate_gives_adversarial_example(self): - - x_val = np.random.rand(100, 2) - x_val = np.array(x_val, dtype=np.float32) - - orig_labs = np.argmax(self.sess.run(self.model.get_logits(x_val)), axis=1) - feed_labs = np.zeros((100, 2)) - feed_labs[np.arange(100), orig_labs] = 1 - x = tf.placeholder(tf.float32, x_val.shape) - y = tf.placeholder(tf.float32, feed_labs.shape) - - x_adv_p = self.attack.generate(x, max_iterations=100, - binary_search_steps=3, - initial_const=1, - clip_min=-5, clip_max=5, - batch_size=100, y=y) - self.assertEqual(x_val.shape, x_adv_p.shape) - x_adv = self.sess.run(x_adv_p, {x: x_val, y: feed_labs}) - - new_labs = np.argmax(self.sess.run(self.model.get_logits(x_adv)), axis=1) - - self.assertTrue(np.mean(orig_labs == new_labs) < 0.1) - - def test_generate_np_gives_clipped_adversarial_examples(self): - x_val = np.random.rand(100, 2) - x_val = np.array(x_val, dtype=np.float32) - - x_adv = self.attack.generate_np(x_val, max_iterations=10, - binary_search_steps=1, - learning_rate=1e-3, - initial_const=1, - clip_min=-0.2, clip_max=0.3, - batch_size=100) - - self.assertTrue(-0.201 < np.min(x_adv)) - self.assertTrue(np.max(x_adv) < .301) - - def test_generate_np_high_confidence_targeted_examples(self): - - trivial_model = TrivialModel() - - for CONFIDENCE in [0, 2.3]: - x_val = np.random.rand(10, 1) - .5 - x_val = np.array(x_val, dtype=np.float32) - - feed_labs = np.zeros((10, 2)) - feed_labs[np.arange(10), np.random.randint(0, 2, 10)] = 1 - attack = CarliniWagnerL2(trivial_model, sess=self.sess) - x_adv = attack.generate_np(x_val, - max_iterations=100, - binary_search_steps=2, - learning_rate=1e-2, - initial_const=1, - clip_min=-10, clip_max=10, - confidence=CONFIDENCE, - y_target=feed_labs, - batch_size=10) - - new_labs = self.sess.run(trivial_model.get_logits(x_adv)) - - good_labs = new_labs[np.arange(10), np.argmax(feed_labs, axis=1)] - bad_labs = new_labs[np.arange( - 10), 1 - np.argmax(feed_labs, axis=1)] - - self.assertTrue(np.isclose( - 0, np.min(good_labs - (bad_labs + CONFIDENCE)), atol=1e-1)) - self.assertTrue(np.mean(np.argmax(new_labs, axis=1) == - np.argmax(feed_labs, axis=1)) > .9) - - def test_generate_np_high_confidence_untargeted_examples(self): - - trivial_model = TrivialModel() - - for CONFIDENCE in [0, 2.3]: - x_val = np.random.rand(10, 1) - .5 - x_val = np.array(x_val, dtype=np.float32) - - orig_labs = np.argmax( - self.sess.run(trivial_model.get_logits(x_val)), axis=1) - attack = CarliniWagnerL2(trivial_model, sess=self.sess) - x_adv = attack.generate_np(x_val, - max_iterations=100, - binary_search_steps=2, - learning_rate=1e-2, - initial_const=1, - clip_min=-10, clip_max=10, - confidence=CONFIDENCE, - batch_size=10) - - new_labs = self.sess.run(trivial_model.get_logits(x_adv)) - - good_labs = new_labs[np.arange(10), 1 - orig_labs] - bad_labs = new_labs[np.arange(10), orig_labs] - - self.assertTrue(np.mean(np.argmax(new_labs, axis=1) == orig_labs) - == 0) - self.assertTrue(np.isclose( - 0, np.min(good_labs - (bad_labs + CONFIDENCE)), atol=1e-1)) - - -class TestSaliencyMapMethod(CleverHansTest): - def setUp(self): - super(TestSaliencyMapMethod, self).setUp() - - self.sess = tf.Session() - self.sess.as_default() - self.model = DummyModel() - self.attack = SaliencyMapMethod(self.model, sess=self.sess) - - # initialize model - with tf.name_scope('dummy_model'): - self.model.get_logits(tf.placeholder(tf.float32, shape=(None, 1000))) - self.sess.run(tf.global_variables_initializer()) - - self.attack = SaliencyMapMethod(self.model, sess=self.sess) - - def test_generate_np_targeted_gives_adversarial_example(self): - x_val = np.random.rand(10, 1000) - x_val = np.array(x_val, dtype=np.float32) - - feed_labs = np.zeros((10, 10)) - feed_labs[np.arange(10), np.random.randint(0, 9, 10)] = 1 - x_adv = self.attack.generate_np(x_val, - clip_min=-5., clip_max=5., - y_target=feed_labs) - new_labs = np.argmax(self.sess.run(self.model.get_logits(x_adv)), axis=1) - - worked = np.mean(np.argmax(feed_labs, axis=1) == new_labs) - self.assertTrue(worked > .9) - - -class TestDeepFool(CleverHansTest): - def setUp(self): - super(TestDeepFool, self).setUp() - - self.sess = tf.Session() - self.model = SimpleModel() - self.attack = DeepFool(self.model, sess=self.sess) - - def test_generate_np_gives_adversarial_example(self): - x_val = np.random.rand(100, 2) - x_val = np.array(x_val, dtype=np.float32) - - x_adv = self.attack.generate_np(x_val, overshoot=0.02, max_iter=50, - nb_candidate=2, clip_min=-5, - clip_max=5) - - orig_labs = np.argmax(self.sess.run(self.model.get_logits(x_val)), axis=1) - new_labs = np.argmax(self.sess.run(self.model.get_logits(x_adv)), axis=1) - - self.assertTrue(np.mean(orig_labs == new_labs) < 0.1) - - def test_generate_gives_adversarial_example(self): - x_val = np.random.rand(100, 2) - x_val = np.array(x_val, dtype=np.float32) - - orig_labs = np.argmax(self.sess.run(self.model.get_logits(x_val)), axis=1) - x = tf.placeholder(tf.float32, x_val.shape) - - x_adv_p = self.attack.generate(x, overshoot=0.02, max_iter=50, - nb_candidate=2, clip_min=-5, clip_max=5) - self.assertEqual(x_val.shape, x_adv_p.shape) - x_adv = self.sess.run(x_adv_p, {x: x_val}) - - new_labs = np.argmax(self.sess.run(self.model.get_logits(x_adv)), axis=1) - - self.assertTrue(np.mean(orig_labs == new_labs) < 0.1) - - def test_generate_np_gives_clipped_adversarial_examples(self): - x_val = np.random.rand(100, 2) - x_val = np.array(x_val, dtype=np.float32) - - x_adv = self.attack.generate_np(x_val, overshoot=0.02, max_iter=50, - nb_candidate=2, clip_min=-0.2, - clip_max=0.3) - - self.assertTrue(-0.201 < np.min(x_adv)) - self.assertTrue(np.max(x_adv) < .301) - - -class TestMomentumIterativeMethod(TestProjectedGradientDescent): - def setUp(self): - super(TestMomentumIterativeMethod, self).setUp() - - self.attack = MomentumIterativeMethod(self.model, sess=self.sess) - - def test_generate_np_can_be_called_with_different_decay_factor(self): - x_val = np.random.rand(100, 2) - x_val = np.array(x_val, dtype=np.float32) - - for decay_factor in [0.0, 0.5, 1.0]: - x_adv = self.attack.generate_np(x_val, eps=0.5, ord=np.inf, - decay_factor=decay_factor, - clip_min=-5.0, clip_max=5.0) - - delta = np.max(np.abs(x_adv - x_val), axis=1) - self.assertClose(delta, 0.5) - - def test_multiple_initial_random_step(self): - # There is no initial random step, so nothing to test here - pass - - -class TestMadryEtAl(CleverHansTest): - def setUp(self): - super(TestMadryEtAl, self).setUp() - self.model = DummyModel('madryetal_dummy_model') - self.sess = tf.Session() - - def test_attack_can_be_constructed(self): - # The test passes if this does not raise an exception - self.attack = MadryEtAl(self.model, sess=self.sess) - - -class TestBasicIterativeMethod(CleverHansTest): - def setUp(self): - super(TestBasicIterativeMethod, self).setUp() - self.model = DummyModel('bim_dummy_model') - self.sess = tf.Session() - - def test_attack_can_be_constructed(self): - # The test passes if this raises no exceptions - self.attack = BasicIterativeMethod(self.model, sess=self.sess) - - -class TestFastFeatureAdversaries(CleverHansTest): - def setUp(self): - super(TestFastFeatureAdversaries, self).setUp() - - def make_imagenet_cnn(input_shape=(None, 224, 224, 3)): - """ - Similar CNN to AlexNet. - """ - - class ModelImageNetCNN(Model): - def __init__(self, scope, nb_classes=1000, **kwargs): - del kwargs - Model.__init__(self, scope, nb_classes, locals()) - - def fprop(self, x, **kwargs): - del kwargs - my_conv = functools.partial(tf.layers.conv2d, - kernel_size=3, - strides=2, - padding='valid', - activation=tf.nn.relu, - kernel_initializer=HeReLuNormalInitializer) - my_dense = functools.partial(tf.layers.dense, - kernel_initializer=HeReLuNormalInitializer) - with tf.variable_scope(self.scope, reuse=tf.AUTO_REUSE): - for depth in [96, 256, 384, 384, 256]: - x = my_conv(x, depth) - y = tf.layers.flatten(x) - y = my_dense(y, 4096, tf.nn.relu) - y = fc7 = my_dense(y, 4096, tf.nn.relu) - y = my_dense(y, 1000) - return {'fc7': fc7, - self.O_LOGITS: y, - self.O_PROBS: tf.nn.softmax(logits=y)} - - return ModelImageNetCNN('imagenet') - - self.input_shape = [10, 224, 224, 3] - self.sess = tf.Session() - self.model = make_imagenet_cnn(self.input_shape) - self.attack = FastFeatureAdversaries(self.model, sess=self.sess) - - def test_attack_strength(self): - """ - This test generates a random source and guide and feeds them in a - randomly initialized CNN. Checks if an adversarial example can get - at least 50% closer to the guide compared to the original distance of - the source and the guide. - """ - tf.set_random_seed(1234) - input_shape = self.input_shape - x_src = tf.abs(tf.random_uniform(input_shape, 0., 1.)) - x_guide = tf.abs(tf.random_uniform(input_shape, 0., 1.)) - - layer = 'fc7' - attack_params = {'eps': 5. / 256, 'clip_min': 0., 'clip_max': 1., - 'nb_iter': 10, 'eps_iter': 0.005, - 'layer': layer} - x_adv = self.attack.generate(x_src, x_guide, **attack_params) - h_adv = self.model.fprop(x_adv)[layer] - h_src = self.model.fprop(x_src)[layer] - h_guide = self.model.fprop(x_guide)[layer] - - init = tf.global_variables_initializer() - self.sess.run(init) - - ha, hs, hg, _xa, _xs, _xg = self.sess.run( - [h_adv, h_src, h_guide, x_adv, x_src, x_guide]) - d_as = np.sqrt(((hs - ha) * (hs - ha)).sum()) - d_ag = np.sqrt(((hg - ha) * (hg - ha)).sum()) - d_sg = np.sqrt(((hg - hs) * (hg - hs)).sum()) - print("L2 distance between source and adversarial example `%s`: %.4f" % - (layer, d_as)) - print("L2 distance between guide and adversarial example `%s`: %.4f" % - (layer, d_ag)) - print("L2 distance between source and guide `%s`: %.4f" % - (layer, d_sg)) - print("d_ag/d_sg*100 `%s`: %.4f" % (layer, d_ag * 100 / d_sg)) - self.assertTrue(d_ag * 100 / d_sg < 50.) - - -class TestLBFGS(CleverHansTest): - def setUp(self): - super(TestLBFGS, self).setUp() - - self.sess = tf.Session() - self.model = SimpleModel() - self.attack = LBFGS(self.model, sess=self.sess) - - def test_generate_np_targeted_gives_adversarial_example(self): - x_val = np.random.rand(100, 2) - x_val = np.array(x_val, dtype=np.float32) - - feed_labs = np.zeros((100, 2)) - feed_labs[np.arange(100), np.random.randint(0, 1, 100)] = 1 - x_adv = self.attack.generate_np(x_val, max_iterations=100, - binary_search_steps=3, - initial_const=1, - clip_min=-5, clip_max=5, - batch_size=100, y_target=feed_labs) - - new_labs = np.argmax(self.sess.run(self.model.get_logits(x_adv)), axis=1) - - self.assertTrue(np.mean(np.argmax(feed_labs, axis=1) == new_labs) - > 0.9) - - def test_generate_targeted_gives_adversarial_example(self): - x_val = np.random.rand(100, 2) - x_val = np.array(x_val, dtype=np.float32) - - feed_labs = np.zeros((100, 2)) - feed_labs[np.arange(100), np.random.randint(0, 1, 100)] = 1 - x = tf.placeholder(tf.float32, x_val.shape) - y = tf.placeholder(tf.float32, feed_labs.shape) - - x_adv_p = self.attack.generate(x, max_iterations=100, - binary_search_steps=3, - initial_const=1, - clip_min=-5, clip_max=5, - batch_size=100, y_target=y) - self.assertEqual(x_val.shape, x_adv_p.shape) - x_adv = self.sess.run(x_adv_p, {x: x_val, y: feed_labs}) - - new_labs = np.argmax(self.sess.run(self.model.get_logits(x_adv)), axis=1) - - self.assertTrue(np.mean(np.argmax(feed_labs, axis=1) == new_labs) - > 0.9) - - def test_generate_np_gives_clipped_adversarial_examples(self): - x_val = np.random.rand(100, 2) - x_val = np.array(x_val, dtype=np.float32) - - feed_labs = np.zeros((100, 2)) - feed_labs[np.arange(100), np.random.randint(0, 1, 100)] = 1 - x_adv = self.attack.generate_np(x_val, max_iterations=10, - binary_search_steps=1, - initial_const=1, - clip_min=-0.2, clip_max=0.3, - batch_size=100, y_target=feed_labs) - - self.assertTrue(-0.201 < np.min(x_adv)) - self.assertTrue(np.max(x_adv) < .301) - - -class SimpleSpatialBrightPixelModel(Model): - """ - If there is a bright pixel in the image returns the first class. - Otherwise returns the second class. Spatial attack should push the - bright pixels off of the image. - """ - - def __init__(self, scope='simple_spatial', nb_classes=2, **kwargs): - del kwargs - Model.__init__(self, scope, nb_classes, locals()) - - def fprop(self, x, **kwargs): - del kwargs - - flat_x = slim.flatten(x) - first_logit = tf.reduce_max(flat_x, axis=1) - second_logit = tf.ones_like(first_logit) * 0.5 - res = tf.stack([second_logit, first_logit], axis=1) - return {self.O_LOGITS: res, - self.O_PROBS: tf.nn.softmax(res)} - - -@unittest.skipIf( - [int(v) for v in tf.__version__.split('.')[:2]] < [1, 6], - "SpatialAttack requires tf 1.6 or higher") -class TestSpatialTransformationMethod(CleverHansTest): - """Tests for SpatialTransformationMethod""" - - def setUp(self): - """ - Allocate session, model, and attack + initialize tf Variables - """ - super(TestSpatialTransformationMethod, self).setUp() - - self.sess = tf.Session() - self.model = SimpleSpatialBrightPixelModel() - self.attack = SpatialTransformationMethod(self.model, sess=self.sess) - - # initialize model - with tf.name_scope('dummy_model_spatial'): - self.model.get_logits(tf.placeholder(tf.float32, shape=(None, 2, 2, 1))) - self.sess.run(tf.global_variables_initializer()) - - def test_no_transformation(self): - """Test that setting transformation params to 0. is a no-op""" - x_val = np.random.rand(100, 2, 2, 1) - x_val = np.array(x_val, dtype=np.float32) - x = tf.placeholder(tf.float32, shape=(None, 2, 2, 1)) - - x_adv_p = self.attack.generate(x, batch_size=100, dx_min=0.0, - dx_max=0.0, n_dxs=1, dy_min=0.0, - dy_max=0.0, n_dys=1, angle_min=0, - angle_max=0, n_angles=1) - x_adv = self.sess.run(x_adv_p, {x: x_val}) - self.assertClose(x_adv, x_val) - - def test_push_pixels_off_image(self): - """Test that the attack pushes some pixels off the image""" - x_val = np.random.rand(100, 2, 2, 1) - x_val = np.array(x_val, dtype=np.float32) - - # The correct answer is that they are bright - # So the attack must push the pixels off the edge - y = np.zeros([100, 2]) - y[:, 0] = 1. - - x = tf.placeholder(tf.float32, shape=(None, 2, 2, 1)) - x_adv_p = self.attack.generate(x, - y=y, batch_size=100, dx_min=-0.5, - dx_max=0.5, n_dxs=3, dy_min=-0.5, - dy_max=0.5, n_dys=3, angle_min=0, - angle_max=0, n_angles=1) - x_adv = self.sess.run(x_adv_p, {x: x_val}) - - old_labs = np.argmax(y, axis=1) - new_labs = np.argmax(self.sess.run(self.model.get_logits(x_adv)), axis=1) - print(np.mean(old_labs == new_labs)) - self.assertTrue(np.mean(old_labs == new_labs) < 0.3) - - def test_keep_pixels_on_image(self): - """Test that the attack does not push some pixels off the image""" - x_val = np.random.rand(100, 2, 2, 1) - x_val = np.array(x_val, dtype=np.float32) - - # The correct answer is that they are NOT bright - # So the attack must NOT push the pixels off the edge - y = np.zeros([100, 2]) - y[:, 0] = 1. - - x = tf.placeholder(tf.float32, shape=(None, 2, 2, 1)) - x_adv_p = self.attack.generate(x, - y=y, batch_size=100, dx_min=-0.5, - dx_max=0.5, n_dxs=3, dy_min=-0.5, - dy_max=0.5, n_dys=3, angle_min=0, - angle_max=0, n_angles=1) - x_adv = self.sess.run(x_adv_p, {x: x_val}) - - old_labs = np.argmax(y, axis=1) - new_labs = np.argmax(self.sess.run(self.model.get_logits(x_adv)), axis=1) - print(np.mean(old_labs == new_labs)) - self.assertTrue(np.mean(old_labs == new_labs) < 0.3) - - -class TestHopSkipJumpAttack(CleverHansTest): - """Tests for Test HopSkipJumpAttack""" - - def setUp(self): - super(TestHopSkipJumpAttack, self).setUp() - - self.sess = tf.Session() - self.model = SimpleModel() - self.attack = HopSkipJumpAttack(self.model, sess=self.sess) - - def test_generate_np_untargeted_l2(self): - x_val = np.random.rand(50, 2) - x_val = np.array(x_val, dtype=np.float32) - bapp_params = { - 'constraint': 'l2', - 'stepsize_search': 'geometric_progression', - 'num_iterations': 10, - 'verbose': True, - } - x_adv = self.attack.generate_np(x_val, **bapp_params) - - orig_labs = np.argmax(self.sess.run(self.model.get_logits(x_val)), axis=1) - new_labs = np.argmax(self.sess.run(self.model.get_logits(x_adv)), axis=1) - - self.assertTrue(np.mean(orig_labs == new_labs) < 0.1) - - def test_generate_untargeted_linf(self): - - x_val = np.random.rand(50, 2) - x_val = np.array(x_val, dtype=np.float32) - - orig_labs = np.argmax(self.sess.run(self.model.get_logits(x_val)), axis=1) - - # Requires input to have batchsize 1. - x = tf.placeholder(tf.float32, [1, 2]) - - bapp_params = { - 'constraint': 'linf', - 'stepsize_search': 'grid_search', - 'num_iterations': 10, - 'verbose': True, - } - x_adv_p = self.attack.generate(x, **bapp_params) - - self.assertEqual(x_adv_p.shape, [1, 2]) - x_adv = [] - for single_x_val in x_val: - single_x_adv = self.sess.run( - x_adv_p, {x: np.expand_dims(single_x_val, 0)}) - x_adv.append(single_x_adv) - - x_adv = np.concatenate(x_adv, axis=0) - - new_labs = np.argmax(self.sess.run(self.model.get_logits(x_adv)), axis=1) - - self.assertTrue(np.mean(orig_labs == new_labs) < 0.1) - - def test_generate_np_targeted_linf(self): - x_val = np.random.rand(200, 2) - x_val = np.array(x_val, dtype=np.float32) - - orig_labs = np.argmax(self.sess.run(self.model.get_logits(x_val)), axis=1) - x_val_pos = x_val[orig_labs == 1] - x_val_neg = x_val[orig_labs == 0] - - x_val_under_attack = np.concatenate( - (x_val_pos[:25], x_val_neg[:25]), axis=0) - y_target = np.eye(2)[np.concatenate( - (np.zeros(25), np.ones(25))).astype(int)] - image_target = np.concatenate((x_val_neg[25:50], x_val_pos[25:50]), axis=0) - - bapp_params = { - 'constraint': 'linf', - 'stepsize_search': 'geometric_progression', - 'num_iterations': 10, - 'verbose': True, - 'y_target': y_target, - 'image_target': image_target, - } - x_adv = self.attack.generate_np(x_val_under_attack, **bapp_params) - - new_labs = np.argmax(self.sess.run(self.model.get_logits(x_adv)), axis=1) - - self.assertTrue(np.mean(np.argmax(y_target, axis=1) == new_labs) - > 0.9) - - def test_generate_targeted_l2(self): - - # Create data in numpy arrays. - x_val = np.random.rand(200, 2) - x_val = np.array(x_val, dtype=np.float32) - orig_labs = np.argmax(self.sess.run(self.model.get_logits(x_val)), axis=1) - x_val_pos = x_val[orig_labs == 1] - x_val_neg = x_val[orig_labs == 0] - x_val_under_attack = np.concatenate( - (x_val_pos[:25], x_val_neg[:25]), axis=0) - y_target = np.eye(2)[np.concatenate( - (np.zeros(25), np.ones(25))).astype(int)] - image_target = np.concatenate((x_val_neg[25:50], x_val_pos[25:50]), axis=0) - - # Create placeholders. - # Require input has batchsize 1. - x = tf.placeholder(tf.float32, [1, 2]) - y_target_ph = tf.placeholder(tf.float32, [1, 2]) - image_target_ph = tf.placeholder(tf.float32, [1, 2]) - - # Create graph. - bapp_params = { - 'constraint': 'l2', - 'stepsize_search': 'grid_search', - 'num_iterations': 10, - 'verbose': True, - 'y_target': y_target_ph, - 'image_target': image_target_ph, - } - x_adv_p = self.attack.generate(x, **bapp_params) - self.assertEqual(x_adv_p.shape, [1, 2]) - - # Generate adversarial examples. - x_adv = [] - for i, single_x_val in enumerate(x_val_under_attack): - print(image_target.shape, y_target.shape) - single_x_adv = self.sess.run(x_adv_p, - {x: np.expand_dims(single_x_val, 0), - y_target_ph: np.expand_dims(y_target[i], 0), - image_target_ph: np.expand_dims(image_target[i], 0)}) - x_adv.append(single_x_adv) - x_adv = np.concatenate(x_adv, axis=0) - - new_labs = np.argmax(self.sess.run(self.model.get_logits(x_adv)), axis=1) - self.assertTrue(np.mean(np.argmax(y_target, axis=1) == new_labs) - > 0.9) diff --git a/tests_tf/test_attacks_tf.py b/tests_tf/test_attacks_tf.py deleted file mode 100644 index 34042852b..000000000 --- a/tests_tf/test_attacks_tf.py +++ /dev/null @@ -1,264 +0,0 @@ -"""Tests of cleverhans.attacks_tf - -""" -# pylint: disable=missing-docstring -from functools import partial -import unittest - -import numpy as np -import tensorflow as tf - -from cleverhans.devtools.checks import CleverHansTest -from cleverhans.attacks_tf import fgm, pgd_attack, \ - UnrolledAdam, UnrolledGradientDescent, parallel_apply_transformations -from cleverhans.devtools.mocks import random_feed_dict -from cleverhans.model import Model - - -class SimpleModel(Model): - """ - A very simple neural network - """ - - def __init__(self, scope='simple', nb_classes=2, **kwargs): - del kwargs - Model.__init__(self, scope, nb_classes, locals()) - - def fprop(self, x, **kwargs): - del kwargs - with tf.variable_scope(self.scope, reuse=tf.AUTO_REUSE): - w1 = tf.constant( - [[1.5, .3], [-2, 0.3]], dtype=tf.as_dtype(x.dtype)) - w2 = tf.constant( - [[-2.4, 1.2], [0.5, -2.3]], dtype=tf.as_dtype(x.dtype)) - h1 = tf.nn.sigmoid(tf.matmul(x, w1)) - res = tf.matmul(h1, w2) - return {self.O_LOGITS: res, self.O_PROBS: tf.nn.softmax(res)} - - -class TestAttackTF(CleverHansTest): - def setUp(self): - super(TestAttackTF, self).setUp() - self.sess = tf.Session() - self.model = SimpleModel() - - def test_fgm_gradient_max(self): - input_dim = 2 - nb_classes = 3 - batch_size = 4 - rng = np.random.RandomState([2017, 8, 23]) - x = tf.placeholder(tf.float32, [batch_size, input_dim]) - weights = tf.placeholder(tf.float32, [input_dim, nb_classes]) - logits = tf.matmul(x, weights) - probs = tf.nn.softmax(logits) - adv_x = fgm(x, probs) - random_example = rng.randint(batch_size) - random_feature = rng.randint(input_dim) - output = tf.slice(adv_x, [random_example, random_feature], [1, 1]) - dx, = tf.gradients(output, x) - # The following line catches GitHub issue #243 - self.assertIsNotNone(dx) - dx = self.sess.run(dx, feed_dict=random_feed_dict(rng, [x, weights])) - ground_truth = np.zeros((batch_size, input_dim)) - ground_truth[random_example, random_feature] = 1. - self.assertClose(dx, ground_truth) - - def helper_pgd_attack(self, - unrolled_optimizer, - targeted, - nb_iters=20, - epsilon=.5, - clip_min=-5., - clip_max=5., - assert_threshold=0.5): - def loss_fn(input_image, label, targeted): - res = self.model.fprop(input_image) - logits = res[self.model.O_LOGITS] - multiplier = 1. if targeted else -1. - return multiplier * tf.nn.sparse_softmax_cross_entropy_with_logits( - labels=label, logits=logits) - - x_val_ph = tf.placeholder(tf.float32, shape=[100, 2]) - x_val = np.random.randn(100, 2).astype(np.float32) - init_model_output = self.model.fprop(x_val_ph) - init_model_logits = init_model_output[self.model.O_LOGITS] - if targeted: - labels = np.random.random_integers(0, 1, size=(100, )) - else: - - labels = tf.stop_gradient(tf.argmax(init_model_logits, axis=1)) - - def _project_perturbation(perturbation, epsilon, input_image, - clip_min, clip_max): - clipped_perturbation = tf.clip_by_value(perturbation, -epsilon, - epsilon) - new_image = tf.clip_by_value(input_image + clipped_perturbation, - clip_min, clip_max) - return new_image - input_image - - x_adv = pgd_attack( - loss_fn=partial(loss_fn, targeted=targeted), - input_image=x_val_ph, - label=labels, - epsilon=epsilon, - num_steps=nb_iters, - optimizer=unrolled_optimizer, - project_perturbation=_project_perturbation, - clip_min=clip_min, clip_max=clip_max) - - final_model_output = self.model.fprop(x_adv) - final_model_logits = final_model_output[self.model.O_LOGITS] - - if not targeted: - logits1, logits2 = self.sess.run( - [init_model_logits, final_model_logits], - feed_dict={x_val_ph: x_val}) - preds1 = np.argmax(logits1, axis=1) - preds2 = np.argmax(logits2, axis=1) - - self.assertTrue( - np.mean(preds1 == preds2) < assert_threshold, - np.mean(preds1 == preds2)) - - else: - logits_adv = self.sess.run( - final_model_logits, feed_dict={x_val_ph: x_val}) - preds_adv = np.argmax(logits_adv, axis=1) - - self.assertTrue(np.mean(labels == preds_adv) > assert_threshold) - - def test_pgd_untargeted_attack_with_adam_optimizer(self): - unrolled_optimizer = UnrolledAdam(lr=0.1) - self.helper_pgd_attack( - unrolled_optimizer=unrolled_optimizer, - targeted=False, - epsilon=.5, - nb_iters=20, - clip_min=-10., - clip_max=10., - assert_threshold=0.7) - - def test_stronger_pgd_untargeted_attack_with_adam_optimizer(self): - unrolled_optimizer = UnrolledAdam(lr=0.1) - self.helper_pgd_attack( - unrolled_optimizer=unrolled_optimizer, - targeted=False, - epsilon=5., - nb_iters=100, - clip_min=-10., - clip_max=10., - assert_threshold=0.1) - - def test_pgd_targeted_attack_with_adam_optimizer(self): - unrolled_optimizer = UnrolledAdam(lr=0.1) - self.helper_pgd_attack( - unrolled_optimizer=unrolled_optimizer, - targeted=True, - epsilon=.5, - nb_iters=20, - clip_min=-10., - clip_max=10., - assert_threshold=0.7) - - def test_stronger_pgd_targeted_attack_with_adam_optimizer(self): - unrolled_optimizer = UnrolledAdam(lr=0.1) - self.helper_pgd_attack( - unrolled_optimizer=unrolled_optimizer, - targeted=True, - epsilon=5., - nb_iters=100, - clip_min=-10., - clip_max=10., - assert_threshold=0.9) - - def test_pgd_untargeted_attack_with_sgd_optimizer(self): - unrolled_optimizer = UnrolledGradientDescent(lr=1000.) - self.helper_pgd_attack( - unrolled_optimizer=unrolled_optimizer, - targeted=False, - epsilon=.5, - nb_iters=20, - clip_min=-10., - clip_max=10., - assert_threshold=0.6) - - def test_stronger_pgd_untargeted_attack_with_sgd_optimizer(self): - unrolled_optimizer = UnrolledGradientDescent(lr=1000.) - self.helper_pgd_attack( - unrolled_optimizer=unrolled_optimizer, - targeted=False, - epsilon=5., - nb_iters=100, - clip_min=-10., - clip_max=10., - assert_threshold=0.1) - - def test_pgd_targeted_attack_with_sgd_optimizer(self): - unrolled_optimizer = UnrolledGradientDescent(lr=1000.) - self.helper_pgd_attack( - unrolled_optimizer=unrolled_optimizer, - targeted=True, - epsilon=.5, - nb_iters=20, - clip_min=-10., - clip_max=10., - assert_threshold=0.6) - - def test_stronger_pgd_targeted_attack_with_sgd_optimizer(self): - unrolled_optimizer = UnrolledGradientDescent(lr=1000.) - self.helper_pgd_attack( - unrolled_optimizer=unrolled_optimizer, - targeted=True, - epsilon=5., - nb_iters=100, - clip_min=-10., - clip_max=10., - assert_threshold=0.9) - - @unittest.skip("This test requires human inspection of the images") - def test_parallel_apply(self): - def _save_image_to_png(image_np, filename): - from PIL import Image - import os - - dirname = os.path.dirname(filename) - if not os.path.exists(dirname): - os.makedirs(dirname) - - if image_np.shape[-1] == 3: - img = Image.fromarray(np.uint8(image_np * 255.), 'RGB') - else: - img = Image.fromarray(np.uint8(image_np[:, :, 0] * 255.), 'L') - img.save(filename) - - x = tf.ones([3, 200, 200, 3]) - - transforms = [ - [0.2, 0, 20], - [0, 0, 0], - # [-0.2, 0, 20], - # [-0.4, 0, 20], - ] - transformed_ims = parallel_apply_transformations( - x, transforms, black_border_size=30) - - worst_sample_idx = tf.convert_to_tensor([0, 1, 1]) - batch_size = tf.shape(x)[0] - keys = tf.stack([ - tf.range(batch_size, dtype=tf.int32), - tf.cast(worst_sample_idx, tf.int32) - ], axis=1) - - transformed_ims_bshwc = tf.einsum('sbhwc->bshwc', transformed_ims) - after_lookup = tf.gather_nd(transformed_ims_bshwc, keys) # BHWC - - with tf.Session() as sess: - img_batch_np = sess.run(after_lookup)[:, :, :, :] - - for i, img in enumerate(img_batch_np): - filename = "/tmp/test_image%s.png" % (i) - _save_image_to_png(img, filename) - - -if __name__ == '__main__': - unittest.main() diff --git a/tests_tf/test_confidence_report.py b/tests_tf/test_confidence_report.py deleted file mode 100644 index f3f95610f..000000000 --- a/tests_tf/test_confidence_report.py +++ /dev/null @@ -1,82 +0,0 @@ -""" -Tests for cleverhans.confidence_report -""" - -import numpy as np -import tensorflow as tf - -from cleverhans.attacks import Noise -from cleverhans.attack_bundling import AttackConfig -from cleverhans.attack_bundling import bundle_attacks -from cleverhans.attack_bundling import Misclassify -from cleverhans.confidence_report import ConfidenceReport -from cleverhans.confidence_report import ConfidenceReportEntry -from cleverhans.confidence_report import make_confidence_report_bundled -from cleverhans.devtools.mocks import SimpleDataset -from cleverhans.picklable_model import MLP, Linear -from cleverhans import serial - - -def test_confidence_report(): - """ - Test that we can make a confidence report, put an entry in it, and get - that entry back out - """ - report = ConfidenceReport() - entry = ConfidenceReportEntry(correctness=np.array([True, False]), - confidence=np.array([0.9, 0.1])) - report['clean'] = entry - assert report['clean'] is entry - - -def test_make_confidence_report_bundled(): - """ - A very simple test that just makes sure make_confidence_report_bundled can run without crashing - """ - - sess = tf.Session() - try: - nb_classes = 3 - nb_features = 2 - batch_size = 5 - nb_test_examples = batch_size * 2 - layer = Linear(num_hid=nb_classes) - model = MLP(layers=[layer], input_shape=(None, nb_features)) - dataset = SimpleDataset(test_end=nb_test_examples, nb_classes=nb_classes) - model.dataset_factory = dataset.get_factory() - filepath = ".test_model.joblib" - with sess.as_default(): - sess.run(tf.global_variables_initializer()) - serial.save(filepath, model) - def recipe(sess, model, x, y, nb_classes, eps, clip_min, - clip_max, eps_iter, nb_iter, - report_path, eps_iter_small, batch_size): - """ - Mock recipe that just runs the Noise attack so the test runs fast - """ - attack_configs = [AttackConfig(Noise(model, sess), {'eps': eps})] - new_work_goal = {config: 1 for config in attack_configs} - goals = [Misclassify(new_work_goal=new_work_goal)] - bundle_attacks(sess, model, x, y, attack_configs, goals, report_path, attack_batch_size=batch_size, - eval_batch_size=batch_size) - make_confidence_report_bundled(filepath, test_end=nb_test_examples, recipe=recipe, - base_eps=.1, base_eps_iter=.01, batch_size=batch_size) - finally: - sess.close() - -def test_save_load_confidence_report(): - """ - Test that a confidence report can be loaded and saved. - """ - report = ConfidenceReport() - num_examples = 2 - clean_correctness = np.zeros((num_examples,), dtype=np.bool) - clean_confidence = np.zeros((num_examples,), dtype=np.float32) - adv_correctness = clean_correctness.copy() - adv_confidence = clean_confidence.copy() - report['clean'] = ConfidenceReportEntry(clean_correctness, clean_confidence) - report['adv'] = ConfidenceReportEntry(adv_correctness, adv_confidence) - report.completed = True - filepath = ".test_confidence_report.joblib" - serial.save(filepath, report) - report = serial.load(filepath) diff --git a/tests_tf/test_dataset.py b/tests_tf/test_dataset.py deleted file mode 100644 index fb51776b7..000000000 --- a/tests_tf/test_dataset.py +++ /dev/null @@ -1,23 +0,0 @@ -"""Tests for cleverhans.dataset""" -from cleverhans.dataset import Dataset -from cleverhans.devtools.checks import CleverHansTest - - -class LightweightDataset(Dataset): - """ - A dataset that does not actually load any data so it is cheap to run - in tests. - """ - - -class TestDataset(CleverHansTest): - """ - Tests for the Dataset class - """ - - def test_factory(self): - """test_factory: Test that dataset->factory->dataset preserves type""" - d1 = LightweightDataset() - factory = d1.get_factory() - d2 = factory() - self.assertTrue(type(d1) is type(d2)) diff --git a/tests_tf/test_defenses.py b/tests_tf/test_defenses.py deleted file mode 100644 index e8a01e887..000000000 --- a/tests_tf/test_defenses.py +++ /dev/null @@ -1,107 +0,0 @@ -# pylint: disable=missing-docstring -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - -import unittest - -import numpy as np -import tensorflow as tf - -from cleverhans.attacks import FastGradientMethod -from cleverhans.loss import CrossEntropy, MixUp, FeaturePairing -from cleverhans.devtools.checks import CleverHansTest -from cleverhans.model import Model - - -class SimpleModel(Model): - """ - A very simple neural network - """ - - def __init__(self, scope='simple', nb_classes=2, **kwargs): - del kwargs - Model.__init__(self, scope, nb_classes, locals()) - - def fprop(self, x, **kwargs): - del kwargs - with tf.variable_scope(self.scope, reuse=tf.AUTO_REUSE): - w1 = tf.constant([[1.5, .3], [-2, 0.3]], - dtype=tf.as_dtype(x.dtype)) - w2 = tf.constant([[-2.4, 1.2], [0.5, -2.3]], - dtype=tf.as_dtype(x.dtype)) - h1 = tf.nn.sigmoid(tf.matmul(x, w1)) - res = tf.matmul(h1, w2) - return {self.O_FEATURES: [h1, res], - self.O_LOGITS: res, - self.O_PROBS: tf.nn.softmax(res)} - - -class TestDefenses(CleverHansTest): - def setUp(self): - super(TestDefenses, self).setUp() - self.model = SimpleModel() - self.vx = np.array(((1, -1), (-1, 1)), 'f') - self.vy = np.array(((1, 0), (0, 1)), 'f') - self.x = tf.placeholder(tf.float32, [None, 2], 'x') - self.y = tf.placeholder(tf.float32, [None, 2], 'y') - - def test_xe(self): - loss = CrossEntropy(self.model, smoothing=0.) - l = loss.fprop(self.x, self.y) - with tf.Session() as sess: - vl1 = sess.run(l, feed_dict={self.x: self.vx, self.y: self.vy}) - vl2 = sess.run(l, feed_dict={self.x: self.vx, self.y: self.vy}) - self.assertClose(vl1, sum([2.210599660, 1.53666997]) / 2., atol=1e-6) - self.assertClose(vl2, sum([2.210599660, 1.53666997]) / 2., atol=1e-6) - - def test_xe_smoothing(self): - loss = CrossEntropy(self.model, smoothing=0.1) - l = loss.fprop(self.x, self.y) - with tf.Session() as sess: - vl1 = sess.run(l, feed_dict={self.x: self.vx, self.y: self.vy}) - vl2 = sess.run(l, feed_dict={self.x: self.vx, self.y: self.vy}) - self.assertClose(vl1, sum([2.10587597, 1.47194624]) / 2., atol=1e-6) - self.assertClose(vl2, sum([2.10587597, 1.47194624]) / 2., atol=1e-6) - - def test_mixup(self): - def eval_loss(l, count=1000): - with tf.Session() as sess: - vl = np.zeros(2, 'f') - for _ in range(count): - vl += sess.run(l, feed_dict={self.x: self.vx, - self.y: self.vy}) - return vl / count - - loss = MixUp(self.model, beta=1.) - vl = eval_loss(loss.fprop(self.x, self.y)) - self.assertClose(vl, [1.23, 1.23], atol=5e-2) - - loss = MixUp(self.model, beta=0.5) - vl = eval_loss(loss.fprop(self.x, self.y)) - self.assertClose(vl, [1.40, 1.40], atol=5e-2) - - def test_feature_pairing(self): - sess = tf.Session() - fgsm = FastGradientMethod(self.model, sess=sess) - - def attack(x): - return fgsm.generate(x) - loss = FeaturePairing(self.model, weight=0.1, attack=attack) - l = loss.fprop(self.x, self.y) - vl1 = sess.run(l, feed_dict={self.x: self.vx, self.y: self.vy}) - vl2 = sess.run(l, feed_dict={self.x: self.vx, self.y: self.vy}) - self.assertClose(vl1, sum([4.296023369, 2.963884830]) / 2., atol=1e-6) - self.assertClose(vl2, sum([4.296023369, 2.963884830]) / 2., atol=1e-6) - - loss = FeaturePairing(self.model, weight=10., attack=attack) - l = loss.fprop(self.x, self.y) - vl1 = sess.run(l, feed_dict={self.x: self.vx, self.y: self.vy}) - vl2 = sess.run(l, feed_dict={self.x: self.vx, self.y: self.vy}) - self.assertClose(vl1, sum([4.333082676, 3.00094414]) / 2., atol=1e-6) - self.assertClose(vl2, sum([4.333082676, 3.00094414]) / 2., atol=1e-6) - - -if __name__ == '__main__': - unittest.main() diff --git a/tests_tf/test_evaluation.py b/tests_tf/test_evaluation.py deleted file mode 100644 index fa38d34a3..000000000 --- a/tests_tf/test_evaluation.py +++ /dev/null @@ -1,17 +0,0 @@ -"""Tests for cleverhans.evaluation""" -from cleverhans.devtools.checks import CleverHansTest -from cleverhans.evaluation import _CorrectFactory -from cleverhans.model import Model - - -class TestEvaluation(CleverHansTest): - """Tests for cleverhans.evaluation""" - - def test_cache(self): - """test_cache: Test that _CorrectFactory can be cached""" - model = Model() - factory_1 = _CorrectFactory(model) - factory_2 = _CorrectFactory(model) - cache = {} - cache[factory_1] = True - self.assertTrue(factory_2 in cache) diff --git a/tests_tf/test_mnist_blackbox.py b/tests_tf/test_mnist_blackbox.py deleted file mode 100644 index 7dc21971e..000000000 --- a/tests_tf/test_mnist_blackbox.py +++ /dev/null @@ -1,50 +0,0 @@ -# pylint: disable=missing-docstring -import unittest -import numpy as np -# pylint bug on next line -from tensorflow.python.client import device_lib # pylint: disable=no-name-in-module -from cleverhans.devtools.checks import CleverHansTest - -HAS_GPU = 'GPU' in {x.device_type for x in device_lib.list_local_devices()} - - -class TestMNISTBlackboxF(CleverHansTest): - def test_mnist_blackbox(self): - import tensorflow as tf - from cleverhans_tutorials import mnist_blackbox - - # Run the MNIST tutorial on a dataset of reduced size, reduced number - # of data augmentations, increased substitute holdout for faster runtime. - mnist_blackbox_args = {'train_start': 0, - 'train_end': 5000, - 'test_start': 0, - 'test_end': 2000, - 'data_aug': 1, - 'holdout': 1000, - 'nb_epochs': 2, - 'nb_epochs_s': 6} - g = tf.Graph() - with g.as_default(): - np.random.seed(42) - report = mnist_blackbox.mnist_blackbox(**mnist_blackbox_args) - - # Check accuracy values contained in the AccuracyReport object - self.assertTrue(report['bbox'] > 0.7, report['bbox']) - self.assertTrue(report['sub'] > 0.7, report['sub']) - self.assertTrue(report['bbox_on_sub_adv_ex'] < 0.3, - report['bbox_on_sub_adv_ex']) - - g = tf.Graph() - with g.as_default(): - np.random.seed(42) - report_2 = mnist_blackbox.mnist_blackbox(**mnist_blackbox_args) - - atol_fac = 1e-2 if HAS_GPU else 1e-6 - self.assertClose(report['bbox'], report_2['bbox'], atol=atol_fac * 1) - self.assertClose(report['sub'], report_2['sub'], atol=atol_fac * 1) - self.assertClose(report['bbox_on_sub_adv_ex'], - report_2['bbox_on_sub_adv_ex'], atol=atol_fac * 1) - - -if __name__ == '__main__': - unittest.main() diff --git a/tests_tf/test_mnist_tutorial_cw.py b/tests_tf/test_mnist_tutorial_cw.py deleted file mode 100644 index e3aa66b53..000000000 --- a/tests_tf/test_mnist_tutorial_cw.py +++ /dev/null @@ -1,53 +0,0 @@ -# pylint: disable=missing-docstring -import unittest -import numpy as np -from cleverhans.devtools.checks import CleverHansTest - - -class TestMNISTTutorialCW(CleverHansTest): - def test_mnist_tutorial_cw(self): - import tensorflow as tf - from cleverhans_tutorials import mnist_tutorial_cw - - # Run the MNIST tutorial on a dataset of reduced size - # and disable visualization. - cw_tutorial_args = {'train_start': 0, - 'train_end': 10000, - 'test_start': 0, - 'test_end': 1666, - 'viz_enabled': False} - g = tf.Graph() - with g.as_default(): - np.random.seed(42) - report = mnist_tutorial_cw.mnist_tutorial_cw(**cw_tutorial_args) - - # Check accuracy values contained in the AccuracyReport object - self.assertGreater(report.clean_train_clean_eval, 0.85) - self.assertEqual(report.clean_train_adv_eval, 0.00) - - # There is no adversarial training in the CW tutorial - self.assertEqual(report.adv_train_clean_eval, 0.) - self.assertEqual(report.adv_train_adv_eval, 0.) - - g = tf.Graph() - with g.as_default(): - np.random.seed(42) - report_2 = mnist_tutorial_cw.mnist_tutorial_cw(**cw_tutorial_args) - - atol_fac = 1e-6 - self.assertClose(report.train_clean_train_clean_eval, - report_2.train_clean_train_clean_eval, - atol=atol_fac * 1) - self.assertClose(report.train_clean_train_adv_eval, - report_2.train_clean_train_adv_eval, - atol=atol_fac * 1) - self.assertClose(report.train_adv_train_clean_eval, - report_2.train_adv_train_clean_eval, - atol=atol_fac * 1) - self.assertClose(report.train_adv_train_adv_eval, - report_2.train_adv_train_adv_eval, - atol=atol_fac * 1) - - -if __name__ == '__main__': - unittest.main() diff --git a/tests_tf/test_mnist_tutorial_jsma.py b/tests_tf/test_mnist_tutorial_jsma.py deleted file mode 100644 index 656377e96..000000000 --- a/tests_tf/test_mnist_tutorial_jsma.py +++ /dev/null @@ -1,58 +0,0 @@ -# pylint: disable=missing-docstring -import unittest -import numpy as np -from cleverhans.devtools.checks import CleverHansTest - - -class TestMNISTTutorialJSMA(CleverHansTest): - def test_mnist_tutorial_jsma(self): - - import tensorflow as tf - from cleverhans_tutorials import mnist_tutorial_jsma - - # Run the MNIST tutorial on a dataset of reduced size - # and disable visualization. - jsma_tutorial_args = {'train_start': 0, - 'train_end': 1000, - 'test_start': 0, - 'test_end': 1666, - 'viz_enabled': False, - 'source_samples': 1, - 'nb_epochs': 2} - g = tf.Graph() - with g.as_default(): - np.random.seed(42) - report = mnist_tutorial_jsma.mnist_tutorial_jsma(**jsma_tutorial_args) - - # Check accuracy values contained in the AccuracyReport object - # We already have JSMA tests in test_attacks.py, so just sanity - # check the values here. - self.assertTrue(report.clean_train_clean_eval > 0.65) - self.assertTrue(report.clean_train_adv_eval < 0.25) - - # There is no adversarial training in the JSMA tutorial - self.assertTrue(report.adv_train_clean_eval == 0.) - self.assertTrue(report.adv_train_adv_eval == 0.) - - g = tf.Graph() - with g.as_default(): - np.random.seed(42) - report_2 = mnist_tutorial_jsma.mnist_tutorial_jsma(**jsma_tutorial_args) - - atol_fac = 1e-6 - self.assertClose(report.train_clean_train_clean_eval, - report_2.train_clean_train_clean_eval, - atol=atol_fac * 1) - self.assertClose(report.train_clean_train_adv_eval, - report_2.train_clean_train_adv_eval, - atol=atol_fac * 1) - self.assertClose(report.train_adv_train_clean_eval, - report_2.train_adv_train_clean_eval, - atol=atol_fac * 1) - self.assertClose(report.train_adv_train_adv_eval, - report_2.train_adv_train_adv_eval, - atol=atol_fac * 1) - - -if __name__ == '__main__': - unittest.main() diff --git a/tests_tf/test_mnist_tutorial_keras.py b/tests_tf/test_mnist_tutorial_keras.py deleted file mode 100644 index 645027029..000000000 --- a/tests_tf/test_mnist_tutorial_keras.py +++ /dev/null @@ -1,56 +0,0 @@ -# pylint: disable=missing-docstring -import unittest -import numpy as np -# pylint bug on next line -from tensorflow.python.client import device_lib # pylint: disable=no-name-in-module -from cleverhans.devtools.checks import CleverHansTest - -HAS_GPU = 'GPU' in {x.device_type for x in device_lib.list_local_devices()} - - -class TestMNISTTutorialKeras(CleverHansTest): - def test_mnist_tutorial_keras(self): - - import tensorflow as tf - from cleverhans_tutorials import mnist_tutorial_keras - - # Run the MNIST tutorial on a dataset of reduced size - test_dataset_indices = {'train_start': 0, - 'train_end': 5000, - 'test_start': 0, - 'test_end': 333, - 'nb_epochs': 2, - 'testing': True} - g = tf.Graph() - with g.as_default(): - np.random.seed(42) - report = mnist_tutorial_keras.mnist_tutorial(**test_dataset_indices) - - # Check accuracy values contained in the AccuracyReport object - self.assertTrue(report.train_clean_train_clean_eval > 0.90) - self.assertTrue(report.train_clean_train_adv_eval < 0.05) - self.assertTrue(report.train_adv_train_clean_eval > 0.90) - self.assertTrue(report.train_adv_train_adv_eval > 0.30) - - atol_fac = 5e-2 if HAS_GPU else 1e-6 - g = tf.Graph() - with g.as_default(): - np.random.seed(42) - report_2 = mnist_tutorial_keras.mnist_tutorial(**test_dataset_indices) - - self.assertClose(report.train_clean_train_clean_eval, - report_2.train_clean_train_clean_eval, - atol=atol_fac * 1) - self.assertClose(report.train_clean_train_adv_eval, - report_2.train_clean_train_adv_eval, - atol=atol_fac * 1) - self.assertClose(report.train_adv_train_clean_eval, - report_2.train_adv_train_clean_eval, - atol=atol_fac * 1) - self.assertClose(report.train_adv_train_adv_eval, - report_2.train_adv_train_adv_eval, - atol=atol_fac * 1) - - -if __name__ == '__main__': - unittest.main() diff --git a/tests_tf/test_mnist_tutorial_keras_tf.py b/tests_tf/test_mnist_tutorial_keras_tf.py deleted file mode 100644 index 39b9f8972..000000000 --- a/tests_tf/test_mnist_tutorial_keras_tf.py +++ /dev/null @@ -1,59 +0,0 @@ -# pylint: disable=missing-docstring -import unittest -import numpy as np -# pylint bug on next line -from tensorflow.python.client import device_lib # pylint: disable=no-name-in-module -from cleverhans.devtools.checks import CleverHansTest - -HAS_GPU = 'GPU' in {x.device_type for x in device_lib.list_local_devices()} - - -class TestMNISTTutorialKerasTF(CleverHansTest): - def test_mnist_tutorial_keras_tf(self): - - import tensorflow as tf - from cleverhans_tutorials import mnist_tutorial_keras_tf - - # Run the MNIST tutorial on a dataset of reduced size - test_dataset_indices = {'train_start': 0, - 'train_end': 5000, - 'test_start': 0, - 'test_end': 333, - 'nb_epochs': 3, - 'train_dir': '/tmp', - 'filename': 'mnist.ckpt', - 'load_model': False, - 'testing': True} - g = tf.Graph() - with g.as_default(): - np.random.seed(42) - report = mnist_tutorial_keras_tf.mnist_tutorial(**test_dataset_indices) - - # Check accuracy values contained in the AccuracyReport object - self.assertTrue(report.train_clean_train_clean_eval > 0.90) - self.assertTrue(report.train_clean_train_adv_eval < 0.05) - self.assertTrue(report.train_adv_train_clean_eval > 0.90) - self.assertTrue(report.train_adv_train_adv_eval > 0.30) - - atol_fac = 2e-2 if HAS_GPU else 1e-6 - g = tf.Graph() - with g.as_default(): - np.random.seed(42) - report_2 = mnist_tutorial_keras_tf.mnist_tutorial(**test_dataset_indices) - - self.assertClose(report.train_clean_train_clean_eval, - report_2.train_clean_train_clean_eval, - atol=atol_fac * 1) - self.assertClose(report.train_clean_train_adv_eval, - report_2.train_clean_train_adv_eval, - atol=atol_fac * 1) - self.assertClose(report.train_adv_train_clean_eval, - report_2.train_adv_train_clean_eval, - atol=atol_fac * 1) - self.assertClose(report.train_adv_train_adv_eval, - report_2.train_adv_train_adv_eval, - atol=atol_fac * 1) - - -if __name__ == '__main__': - unittest.main() diff --git a/tests_tf/test_mnist_tutorial_tf.py b/tests_tf/test_mnist_tutorial_tf.py deleted file mode 100644 index 9538bc8a2..000000000 --- a/tests_tf/test_mnist_tutorial_tf.py +++ /dev/null @@ -1,58 +0,0 @@ -# pylint: disable=missing-docstring -import unittest -import numpy as np -# pylint bug on next line -from tensorflow.python.client import device_lib # pylint: disable=no-name-in-module -from cleverhans.devtools.checks import CleverHansTest - -HAS_GPU = 'GPU' in {x.device_type for x in device_lib.list_local_devices()} - - -class TestMNISTTutorialTF(CleverHansTest): - def test_mnist_tutorial_tf(self): - - import tensorflow as tf - from cleverhans_tutorials import mnist_tutorial_tf - - # Run the MNIST tutorial on a dataset of reduced size - test_dataset_indices = {'train_start': 0, - 'train_end': 5000, - 'test_start': 0, - 'test_end': 333, - 'nb_epochs': 2, - 'testing': True} - g = tf.Graph() - with g.as_default(): - np.random.seed(42) - report = mnist_tutorial_tf.mnist_tutorial(num_threads=1, - **test_dataset_indices) - - # Check accuracy values contained in the AccuracyReport object - self.assertGreater(report.train_clean_train_clean_eval, 0.97) - self.assertLess(report.train_clean_train_adv_eval, 0.05) - self.assertGreater(report.train_adv_train_clean_eval, 0.93) - self.assertGreater(report.train_adv_train_adv_eval, 0.4) - - # Check that the tutorial is deterministic (seeded properly) - atol_fac = 2e-2 if HAS_GPU else 1e-6 - g = tf.Graph() - with g.as_default(): - np.random.seed(42) - report_2 = mnist_tutorial_tf.mnist_tutorial(num_threads=1, - **test_dataset_indices) - self.assertClose(report.train_clean_train_clean_eval, - report_2.train_clean_train_clean_eval, - atol=atol_fac * 1) - self.assertClose(report.train_clean_train_adv_eval, - report_2.train_clean_train_adv_eval, - atol=atol_fac * 1) - self.assertClose(report.train_adv_train_clean_eval, - report_2.train_adv_train_clean_eval, - atol=atol_fac * 1) - self.assertClose(report.train_adv_train_adv_eval, - report_2.train_adv_train_adv_eval, - atol=atol_fac * 1) - - -if __name__ == '__main__': - unittest.main() diff --git a/tests_tf/test_model.py b/tests_tf/test_model.py deleted file mode 100644 index bdccb7527..000000000 --- a/tests_tf/test_model.py +++ /dev/null @@ -1,71 +0,0 @@ -""" -Tests for cleverhans.model -""" -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - -import unittest - -from cleverhans.model import Model, CallableModelWrapper - - -class TestModelClass(unittest.TestCase): - """ - Tests for cleverhans.model.Model - """ - # pylint: disable=missing-docstring - - def test_get_logits(self): - # Define empty model - model = Model('model', 10, {}) - x = [] - - # Exception is thrown when `get_logits` not implemented - with self.assertRaises(Exception) as context: - model.get_logits(x) - self.assertTrue(context.exception) - - def test_get_probs(self): - # Define empty model - model = Model('model', 10, {}) - x = [] - - # Exception is thrown when `get_probs` not implemented - with self.assertRaises(Exception) as context: - model.get_probs(x) - self.assertTrue(context.exception) - - def test_fprop(self): - # Define empty model - model = Model('model', 10, {}) - x = [] - - # Exception is thrown when `fprop` not implemented - with self.assertRaises(Exception) as context: - model.fprop(x) - self.assertTrue(context.exception) - - -class TestCallableModelWrapperInitArguments(unittest.TestCase): - """ - Tests for CallableModelWrapper's init argument - """ - - def test_output_layer(self): - """ - Test that the CallableModelWrapper can be constructed without causing Exceptions - """ - def model(**kwargs): - """Mock model""" - del kwargs - return True - - # The following two calls should not raise Exceptions - CallableModelWrapper(model, 'probs') - CallableModelWrapper(model, 'logits') - - -if __name__ == '__main__': - unittest.main() diff --git a/tests_tf/test_picklable_model.py b/tests_tf/test_picklable_model.py deleted file mode 100644 index e192006f3..000000000 --- a/tests_tf/test_picklable_model.py +++ /dev/null @@ -1,110 +0,0 @@ -"""Tests for cleverhans.picklable_model""" -import numpy as np -import tensorflow as tf - -from cleverhans.devtools.checks import CleverHansTest -from cleverhans.picklable_model import Dropout -from cleverhans.picklable_model import MLP -from cleverhans.picklable_model import PerImageStandardize - - -class TestPerImageStandardize(CleverHansTest): - """ - Tests for the PerImageStandardize class. - """ - - def setUp(self): - """ - Set up session and build model graph - """ - super(TestPerImageStandardize, self).setUp() - - self.input_shape = (128, 32, 32, 3) - self.sess = tf.Session() - self.model = MLP(input_shape=self.input_shape, - layers=[PerImageStandardize(name='output')]) - - self.x = tf.placeholder(shape=self.input_shape, - dtype=tf.float32) - self.y = self.model.get_layer(self.x, 'output') - - self.y_true = tf.map_fn(tf.image.per_image_standardization, self.x) - - def run_and_check_output(self, x): - """ - Make sure y and y_true evaluate to the same value - """ - y, y_true = self.sess.run([self.y, self.y_true], - feed_dict={self.x: x}) - self.assertClose(y, y_true) - - def test_random_inputs(self): - """ - Test on random inputs - """ - x = np.random.rand(*self.input_shape) - self.run_and_check_output(x) - - def test_ones_inputs(self): - """ - Test with input set to all ones. - """ - x = np.ones(self.input_shape) - self.run_and_check_output(x) - - -class TestDropout(CleverHansTest): - """ - Tests for the Dropout class - """ - - def test_no_drop(self): - """test_no_drop: Make sure dropout does nothing by default - (so it does not cause stochasticity at test time)""" - - model = MLP(input_shape=[1, 1], layers=[Dropout(name='output')]) - x = tf.constant([[1]], dtype=tf.float32) - y = model.get_layer(x, 'output') - sess = tf.Session() - # Do multiple runs because dropout is stochastic - for _ in range(10): - y_value = sess.run(y) - self.assertClose(y_value, 1.) - - def test_drop(self): - """test_drop: Make sure dropout is activated successfully""" - - # We would like to configure the test to deterministically drop, - # so that the test does not need to use multiple runs. - # However, tf.nn.dropout divides by include_prob, so zero or - # infinitesimal include_prob causes NaNs. - # 1e-8 does not cause NaNs and shouldn't be a significant source - # of test flakiness relative to dependency downloads failing, etc. - model = MLP(input_shape=[1, 1], layers=[Dropout(name='output', - include_prob=1e-8)]) - x = tf.constant([[1]], dtype=tf.float32) - y = model.get_layer(x, 'output', dropout=True) - sess = tf.Session() - y_value = sess.run(y) - # Subject to very rare random failure because include_prob is not exact 0 - self.assertClose(y_value, 0.) - - def test_override(self): - """test_override: Make sure dropout_dict changes dropout probabilities - successfully.""" - - # We would like to configure the test to deterministically drop, - # so that the test does not need to use multiple runs. - # However, tf.nn.dropout divides by include_prob, so zero or - # infinitesimal include_prob causes NaNs. - # For this test, random failure to drop will not cause the test to fail. - # The stochastic version should not even run if everything is working - # right. - model = MLP(input_shape=[1, 1], layers=[Dropout(name='output', - include_prob=1e-8)]) - x = tf.constant([[1]], dtype=tf.float32) - dropout_dict = {'output': 1.} - y = model.get_layer(x, 'output', dropout=True, dropout_dict=dropout_dict) - sess = tf.Session() - y_value = sess.run(y) - self.assertClose(y_value, 1.) diff --git a/tests_tf/test_projected_gradient_descent.py b/tests_tf/test_projected_gradient_descent.py deleted file mode 100644 index c9904a9c7..000000000 --- a/tests_tf/test_projected_gradient_descent.py +++ /dev/null @@ -1,45 +0,0 @@ -"""Tests for the ProjectGradientDescent attack -""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -from nose.tools import assert_raises -import tensorflow as tf - -from cleverhans.attacks import ProjectedGradientDescent -from cleverhans.model import Model - - -def test_no_logits(): - """test_no_logits: Check that a model without logits causes an error""" - batch_size = 2 - nb_classes = 3 - - class NoLogitsModel(Model): - """ - A model that neither defines logits nor makes it possible to find logits - by inspecting the inputs to a softmax op. - """ - - def fprop(self, x, **kwargs): - return {'probs': tf.ones((batch_size, nb_classes)) / nb_classes} - model = NoLogitsModel() - sess = tf.Session() - attack = ProjectedGradientDescent(model, sess=sess) - x = tf.ones((batch_size, 3)) - assert_raises(NotImplementedError, attack.generate, x) - - -def test_rejects_callable(): - """test_rejects_callable: Check that callables are not accepted as models""" - def model(x): - """Mock model""" - return x - sess = tf.Session() - assert_raises(TypeError, ProjectedGradientDescent, model, sess) - - -if __name__ == "__main__": - test_rejects_callable() diff --git a/tests_tf/test_serial.py b/tests_tf/test_serial.py deleted file mode 100644 index b8cec53b3..000000000 --- a/tests_tf/test_serial.py +++ /dev/null @@ -1,29 +0,0 @@ -"""Tests for cleverhans.serial""" -import numpy as np -import tensorflow as tf - -from cleverhans.devtools.checks import CleverHansTest -from cleverhans.serial import PicklableVariable -from cleverhans.serial import load -from cleverhans.serial import save - - -class TestSerial(CleverHansTest): - """ - Tests for cleverhans.serial - """ - - def test_save_and_load_var(self): - """test_save_and_load_var: Test that we can save and load a - PicklableVariable with joblib - """ - sess = tf.Session() - with sess.as_default(): - x = np.ones(1) - xv = PicklableVariable(x) - xv.var.initializer.run() - save("/tmp/var.joblib", xv) - sess.run(tf.assign(xv.var, np.ones(1) * 2)) - new_xv = load("/tmp/var.joblib") - self.assertClose(sess.run(xv.var), np.ones(1) * 2) - self.assertClose(sess.run(new_xv.var), np.ones(1)) diff --git a/tests_tf/test_utils.py b/tests_tf/test_utils.py deleted file mode 100644 index 619b1016d..000000000 --- a/tests_tf/test_utils.py +++ /dev/null @@ -1,96 +0,0 @@ -# pylint: disable=missing-docstring -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - -import unittest -import numpy as np - -from cleverhans import utils -from cleverhans.utils_keras import cnn_model -from cleverhans.utils_keras import KerasModelWrapper - - -class TestUtils(unittest.TestCase): - def test_to_categorical_with_nb_classes_arg(self): - vec = np.asarray([0]) - cat = np.asarray([[1, 0, 0]]) - self.assertTrue(np.all(utils.to_categorical(vec, 3) == cat)) - - def test_random_targets_vector(self): - # Test utils.random_targets with a vector of labels as the input - gt_labels = np.asarray([0, 1, 2, 3]) - rt = utils.random_targets(gt_labels, 5) - - # Make sure random_targets returns a one-hot encoded labels - self.assertTrue(len(rt.shape) == 2) - rt_labels = np.argmax(rt, axis=1) - - # Make sure all labels are different from the correct labels - self.assertTrue(np.all(rt_labels != gt_labels)) - - def test_random_targets_one_hot(self): - # Test utils.random_targets with one-hot encoded labels as the input - gt = np.asarray([[0, 0, 1, 0, 0], - [1, 0, 0, 0, 0], - [0, 0, 0, 1, 0], - [1, 0, 0, 0, 0]]) - gt_labels = np.argmax(gt, axis=1) - rt = utils.random_targets(gt, 5) - - # Make sure random_targets returns a one-hot encoded labels - self.assertTrue(len(rt.shape) == 2) - rt_labels = np.argmax(rt, axis=1) - - # Make sure all labels are different from the correct labels - self.assertTrue(np.all(rt_labels != gt_labels)) - - def test_random_targets_one_hot_single_label(self): - # Test utils.random_targets with a single one-hot encoded label - gt = np.asarray([0, 0, 1, 0, 0]) - gt = gt.reshape((1, 5)) - gt_labels = np.argmax(gt, axis=1) - rt = utils.random_targets(gt, 5) - - # Make sure random_targets returns a one-hot encoded labels - self.assertTrue(len(rt.shape) == 2) - rt_labels = np.argmax(rt, axis=1) - - # Make sure all labels are different from the correct labels - self.assertTrue(np.all(rt_labels != gt_labels)) - - def test_other_classes_neg_class_ind(self): - with self.assertRaises(Exception) as context: - utils.other_classes(10, -1) - self.assertTrue(context.exception) - - def test_other_classes_invalid_class_ind(self): - with self.assertRaises(Exception) as context: - utils.other_classes(5, 8) - self.assertTrue(context.exception) - - def test_other_classes_return_val(self): - res = utils.other_classes(5, 2) - res_expected = [0, 1, 3, 4] - self.assertTrue(res == res_expected) - - def test_get_logits_over_interval(self): - import tensorflow as tf - model = cnn_model() - wrap = KerasModelWrapper(model) - fgsm_params = {'eps': .5} - img = np.ones(shape=(28, 28, 1)) - num_points = 21 - with tf.Session() as sess: - tf.global_variables_initializer().run() - logits = utils.get_logits_over_interval(sess, wrap, - img, fgsm_params, - min_epsilon=-10, - max_epsilon=10, - num_points=num_points) - self.assertEqual(logits.shape[0], num_points) - - -if __name__ == '__main__': - unittest.main() diff --git a/tests_tf/test_utils_keras.py b/tests_tf/test_utils_keras.py deleted file mode 100644 index 6fedf1fd0..000000000 --- a/tests_tf/test_utils_keras.py +++ /dev/null @@ -1,95 +0,0 @@ -# pylint: disable=missing-docstring -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - -import unittest -import numpy as np - -# Weird imports / assignment because the normal import syntax doesn't work for tf.keras in tf 1.8 -from tensorflow import keras -# pylint:disable=wrong-import-position -Sequential = keras.models.Sequential -Dense = keras.layers.Dense -Activation = keras.layers.Activation - -from cleverhans.utils_keras import KerasModelWrapper - - -class TestKerasModelWrapper(unittest.TestCase): - def setUp(self): - import tensorflow as tf - - def dummy_model(): - input_shape = (100,) - return Sequential([Dense(20, name='l1', - input_shape=input_shape), - Dense(10, name='l2'), - Activation('softmax', name='softmax')]) - - self.sess = tf.Session() - self.sess.as_default() - self.model = dummy_model() - - def test_softmax_layer_name_is_softmax(self): - model = KerasModelWrapper(self.model) - softmax_name = model._get_softmax_name() - self.assertEqual(softmax_name, 'softmax') - - def test_logit_layer_name_is_logits(self): - model = KerasModelWrapper(self.model) - logits_name = model._get_logits_name() - self.assertEqual(logits_name, 'l2') - - def test_get_logits(self): - import tensorflow as tf - model = KerasModelWrapper(self.model) - x = tf.placeholder(tf.float32, shape=(None, 100)) - preds = model.get_probs(x) - logits = model.get_logits(x) - - x_val = np.random.rand(2, 100) - tf.global_variables_initializer().run(session=self.sess) - p_val, logits = self.sess.run([preds, logits], feed_dict={x: x_val}) - p_gt = np.exp(logits)/np.sum(np.exp(logits), axis=1, keepdims=True) - self.assertTrue(np.allclose(p_val, p_gt, atol=1e-6)) - - def test_get_probs(self): - import tensorflow as tf - model = KerasModelWrapper(self.model) - x = tf.placeholder(tf.float32, shape=(None, 100)) - preds = model.get_probs(x) - - x_val = np.random.rand(2, 100) - tf.global_variables_initializer().run(session=self.sess) - p_val = self.sess.run(preds, feed_dict={x: x_val}) - self.assertTrue(np.allclose(np.sum(p_val, axis=1), 1, atol=1e-6)) - self.assertTrue(np.all(p_val >= 0)) - self.assertTrue(np.all(p_val <= 1)) - - def test_get_layer_names(self): - model = KerasModelWrapper(self.model) - layer_names = model.get_layer_names() - self.assertEqual(layer_names, ['l1', 'l2', 'softmax']) - - def test_fprop(self): - import tensorflow as tf - model = KerasModelWrapper(self.model) - x = tf.placeholder(tf.float32, shape=(None, 100)) - out_dict = model.fprop(x) - - self.assertEqual(set(out_dict.keys()), set(['l1', 'l2', 'softmax'])) - # Test the dimension of the hidden represetation - self.assertEqual(int(out_dict['l1'].shape[1]), 20) - self.assertEqual(int(out_dict['l2'].shape[1]), 10) - - # Test the caching - x2 = tf.placeholder(tf.float32, shape=(None, 100)) - out_dict2 = model.fprop(x2) - self.assertEqual(set(out_dict2.keys()), set(['l1', 'l2', 'softmax'])) - self.assertEqual(int(out_dict2['l1'].shape[1]), 20) - - -if __name__ == '__main__': - unittest.main() diff --git a/tests_tf/test_utils_tf.py b/tests_tf/test_utils_tf.py deleted file mode 100644 index 547098ab3..000000000 --- a/tests_tf/test_utils_tf.py +++ /dev/null @@ -1,161 +0,0 @@ -"""Tests for cleverhans.utils_tf""" -# pylint: disable=missing-docstring -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - -import numpy as np -import tensorflow as tf - -from cleverhans import utils_tf -from cleverhans.devtools.checks import CleverHansTest - - -def numpy_kl_with_logits(p_logits, q_logits): - def numpy_softmax(logits): - logits -= np.max(logits, axis=1, keepdims=True) - exp_logits = np.exp(logits) - return exp_logits / np.sum(exp_logits, axis=1, keepdims=True) - - p = numpy_softmax(p_logits) - log_p = p_logits - np.log(np.sum(np.exp(p_logits), axis=1, keepdims=True)) - log_q = q_logits - np.log(np.sum(np.exp(q_logits), axis=1, keepdims=True)) - return (p * (log_p - log_q)).sum(axis=1).mean() - - -class TestUtilsTF(CleverHansTest): - """Test class for utils_tf""" - - def setUp(self): - super(TestUtilsTF, self).setUp() - self.sess = tf.Session() - - def test_clip_by_value_numpy_dtype(self): - # Test that it's possible to use clip_by_value while mixing numpy and tf - clip_min = np.zeros((1,)) - clip_max = tf.ones((1,)) - x = tf.ones((1,)) - # The point of this test is just to make sure the casting logic doesn't raise an exception - utils_tf.clip_by_value(x, clip_min, clip_max) - - def test_l2_batch_normalize(self): - x = tf.random_normal((100, 1000)) - x_norm = self.sess.run(utils_tf.l2_batch_normalize(x)) - self.assertClose(np.sum(x_norm**2, axis=1), 1, atol=1e-6) - - def test_kl_with_logits(self): - p_logits = tf.placeholder(tf.float32, shape=(100, 20)) - q_logits = tf.placeholder(tf.float32, shape=(100, 20)) - p_logits_np = np.random.normal(0, 10, size=(100, 20)) - q_logits_np = np.random.normal(0, 10, size=(100, 20)) - kl_div_tf = self.sess.run(utils_tf.kl_with_logits(p_logits, q_logits), - feed_dict={p_logits: p_logits_np, - q_logits: q_logits_np}) - kl_div_ref = numpy_kl_with_logits(p_logits_np, q_logits_np) - self.assertClose(kl_div_ref, kl_div_tf) - - def test_clip_eta_norm_0(self): - """test_clip_eta_norm_0: Test that `clip_eta` still works when the - norm of `eta` is zero. This used to cause a divide by zero for ord - 1 and ord 2.""" - eta = tf.zeros((5, 3)) - self.assertTrue(eta.dtype == tf.float32, eta.dtype) - eps = .25 - for ord_arg in [np.inf, 1, 2]: - try: - clipped = utils_tf.clip_eta(eta, ord_arg, eps) - except NotImplementedError: - # Don't raise SkipTest, it skips the rest of the for loop - continue - clipped = self.sess.run(clipped) - self.assertTrue(not np.any(np.isinf(clipped))) - self.assertTrue(not np.any(np.isnan(clipped)), (ord_arg, clipped)) - - def test_clip_eta_goldilocks(self): - """test_clip_eta_goldilocks: Test that the clipping handles perturbations - that are too small, just right, and too big correctly""" - eta = tf.constant([[2.], [3.], [4.]]) - self.assertTrue(eta.dtype == tf.float32, eta.dtype) - eps = 3. - for ord_arg in [np.inf, 1, 2]: - for sign in [-1., 1.]: - try: - clipped = utils_tf.clip_eta(eta * sign, ord_arg, eps) - except NotImplementedError: - # Don't raise SkipTest, it skips the rest of the for loop - continue - clipped_value = self.sess.run(clipped) - gold = sign * np.array([[2.], [3.], [3.]]) - self.assertClose(clipped_value, gold) - grad, = tf.gradients(clipped, eta) - grad_value = self.sess.run(grad) - # Note: the second 1. is debatable (the left-sided derivative - # and the right-sided derivative do not match, so formally - # the derivative is not defined). This test makes sure that - # we at least handle this oddity consistently across all the - # argument values we test - gold = sign * np.array([[1.], [1.], [0.]]) - self.assertClose(grad_value, gold) - - def test_zero_out_clipped_grads(self): - """ - test_zero_out_clipped_grads: Test that gradient gets zeroed out at positions - where no progress can be made due to clipping. - """ - - clip_min = -1 - clip_max = 1 - eta = tf.constant([[0.], [-1.], [1], [0.5], [-1], [1], [-0.9], [0.9]]) - grad = tf.constant([[1.], [-1.], [1.], [1.], [1.], [-1.], [-1.], [1.]]) - - grad2 = self.sess.run( - utils_tf.zero_out_clipped_grads(grad, eta, clip_min, clip_max)) - - expected = np.asarray([[1.], [0.], [0.], [1.], [1.], [-1.], [-1.], [1.]]) - self.assertClose(grad2, expected) - - def test_random_lp_vector_linf(self): - """ - test_random_lp_sample_linf: Test that `random_lp_vector` returns - random samples in the l-inf ball. - """ - - eps = 0.5 - d = 10 - - r = self.sess.run(utils_tf.random_lp_vector((1000, d), np.infty, eps)) - - # test that some values are close to the boundaries - self.assertLessEqual(np.max(r), eps) - self.assertGreaterEqual(np.max(r), 0.95*eps) - self.assertGreaterEqual(np.min(r), -eps) - self.assertLessEqual(np.min(r), -0.95*eps) - - # test that the mean value of each feature is close to zero - means = np.mean(r, axis=0) - self.assertClose(means, np.zeros(d), atol=0.05) - - def test_random_lp_srandom_lp_vector_l1_l2(self): - """ - test_random_lp_vector_l1_l2: Test that `random_lp_vector` returns - random samples in an l1 or l2 ball. - """ - - eps = 0.5 - d = 10 - - for ord in [1, 2]: - r = self.sess.run(utils_tf.random_lp_vector((1000, d), ord, eps)) - - norms = np.linalg.norm(r, axis=-1, ord=ord) - - # test that some values are close to the boundaries - self.assertLessEqual(np.max(norms), eps) - self.assertGreaterEqual(np.max(norms), 0.95 * eps) - - # The expected norm is eps * Exp[U[0,1]^(1/d)] where U is a standard - # uniform random variable and d is the dimension. The second term is - # equal to the expected value of a Beta(d, 1) variable which is d/(d+1). - expected_mean_norm = eps * (d / (d + 1.)) - self.assertClose(np.mean(norms), expected_mean_norm, atol=0.02) diff --git a/tutorials/README.md b/tutorials/README.md index ee0c3c7a0..d2b6d746e 100644 --- a/tutorials/README.md +++ b/tutorials/README.md @@ -1,3 +1,4 @@ -# Tutorials [Future] +# Tutorials -In the future, the content of `future/` will be moved to this folder. +This folder contains scripts demonstrating the features of CleverHans +implemented in one of the three supported frameworks (JAX, PyTorch, and TF2). diff --git a/tutorials/future/README.md b/tutorials/future/README.md deleted file mode 100644 index bb38a4e54..000000000 --- a/tutorials/future/README.md +++ /dev/null @@ -1,6 +0,0 @@ -# Tutorials [Future] - -In the future, this folder will be at the root of this repository `//tutorials` - -This folder contains scripts demonstrating the features of CleverHans -implemented in one of the three supported frameworks (JAX, PyTorch, and TF2). diff --git a/tutorials/future/jax/datasets.py b/tutorials/future/jax/datasets.py deleted file mode 100644 index 19e9fd4fd..000000000 --- a/tutorials/future/jax/datasets.py +++ /dev/null @@ -1,88 +0,0 @@ -# Copyright 2018 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Datasets used in examples.""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import array -import gzip -import os -from os import path -import struct -from six.moves.urllib.request import urlretrieve - -import numpy as np - -from cleverhans.future.jax.utils import one_hot, partial_flatten - - -_DATA = "/tmp/jax_example_data/" - - -def _download(url, filename): - """Download a url to a file in the JAX data temp directory.""" - if not path.exists(_DATA): - os.makedirs(_DATA) - out_file = path.join(_DATA, filename) - if not path.isfile(out_file): - urlretrieve(url, out_file) - print("downloaded {} to {}".format(url, _DATA)) - - -def mnist_raw(): - """Download and parse the raw MNIST dataset.""" - # CVDF mirror of http://yann.lecun.com/exdb/mnist/ - base_url = "https://storage.googleapis.com/cvdf-datasets/mnist/" - - def parse_labels(filename): - with gzip.open(filename, "rb") as fh: - _ = struct.unpack(">II", fh.read(8)) - return np.array(array.array("B", fh.read()), dtype=np.uint8) - - def parse_images(filename): - with gzip.open(filename, "rb") as fh: - _, num_data, rows, cols = struct.unpack(">IIII", fh.read(16)) - return np.array(array.array("B", fh.read()), - dtype=np.uint8).reshape(num_data, rows, cols) - - for filename in ["train-images-idx3-ubyte.gz", "train-labels-idx1-ubyte.gz", - "t10k-images-idx3-ubyte.gz", "t10k-labels-idx1-ubyte.gz"]: - _download(base_url + filename, filename) - - train_images = parse_images(path.join(_DATA, "train-images-idx3-ubyte.gz")) - train_labels = parse_labels(path.join(_DATA, "train-labels-idx1-ubyte.gz")) - test_images = parse_images(path.join(_DATA, "t10k-images-idx3-ubyte.gz")) - test_labels = parse_labels(path.join(_DATA, "t10k-labels-idx1-ubyte.gz")) - - return train_images, train_labels, test_images, test_labels - - -def mnist(permute_train=False): - """Download, parse and process MNIST data to unit scale and one-hot labels.""" - train_images, train_labels, test_images, test_labels = mnist_raw() - - train_images = partial_flatten(train_images) / np.float32(255.) - test_images = partial_flatten(test_images) / np.float32(255.) - train_labels = one_hot(train_labels, 10) - test_labels = one_hot(test_labels, 10) - - if permute_train: - perm = np.random.RandomState(0).permutation(train_images.shape[0]) - train_images = train_images[perm] - train_labels = train_labels[perm] - - return train_images, train_labels, test_images, test_labels diff --git a/tutorials/future/jax/mnist_tutorial.py b/tutorials/future/jax/mnist_tutorial.py deleted file mode 100644 index 3a8086e31..000000000 --- a/tutorials/future/jax/mnist_tutorial.py +++ /dev/null @@ -1,108 +0,0 @@ -from absl import app, flags - -import datasets -import itertools -import time -import jax.numpy as np -import numpy.random as npr -from jax.config import config -from jax import jit, grad, random -from jax.experimental import optimizers -from jax.experimental import stax -from jax.experimental.stax import Dense, Relu, logsoftmax - -from cleverhans.future.jax.attacks import fast_gradient_method, projected_gradient_descent - -FLAGS = flags.FLAGS - -def main(_): - rng = random.PRNGKey(0) - - # Load MNIST dataset - train_images, train_labels, test_images, test_labels = datasets.mnist() - - batch_size = 128 - batch_shape = (-1, 28, 28, 1) - num_train = train_images.shape[0] - num_complete_batches, leftover = divmod(num_train, batch_size) - num_batches = num_complete_batches + bool(leftover) - - train_images = np.reshape(train_images, batch_shape) - test_images = np.reshape(test_images, batch_shape) - - def data_stream(): - rng = npr.RandomState(0) - while True: - perm = rng.permutation(num_train) - for i in range(num_batches): - batch_idx = perm[i * batch_size:(i + 1) * batch_size] - yield train_images[batch_idx], train_labels[batch_idx] - batches = data_stream() - - # Model, loss, and accuracy functions - init_random_params, predict = stax.serial( - stax.Conv(32, (8, 8), strides=(2, 2), padding='SAME'), - stax.Relu, - stax.Conv(128, (6, 6), strides=(2, 2), padding='VALID'), - stax.Relu, - stax.Conv(128, (5, 5), strides=(1, 1), padding='VALID'), - stax.Flatten, - stax.Dense(128), - stax.Relu, - stax.Dense(10)) - - def loss(params, batch): - inputs, targets = batch - preds = predict(params, inputs) - return -np.mean(logsoftmax(preds) * targets) - - def accuracy(params, batch): - inputs, targets = batch - target_class = np.argmax(targets, axis=1) - predicted_class = np.argmax(predict(params, inputs), axis=1) - return np.mean(predicted_class == target_class) - - # Instantiate an optimizer - opt_init, opt_update, get_params = optimizers.adam(0.001) - - @jit - def update(i, opt_state, batch): - params = get_params(opt_state) - return opt_update(i, grad(loss)(params, batch), opt_state) - - # Initialize model - _, init_params = init_random_params(rng, batch_shape) - opt_state = opt_init(init_params) - itercount = itertools.count() - - # Training loop - print("\nStarting training...") - for epoch in range(FLAGS.nb_epochs): - start_time = time.time() - for _ in range(num_batches): - opt_state = update(next(itercount), opt_state, next(batches)) - epoch_time = time.time() - start_time - - # Evaluate model on clean data - params = get_params(opt_state) - train_acc = accuracy(params, (train_images, train_labels)) - test_acc = accuracy(params, (test_images, test_labels)) - - # Evaluate model on adversarial data - model_fn = lambda images: predict(params, images) - test_images_fgm = fast_gradient_method(model_fn, test_images, FLAGS.eps, np.inf) - test_images_pgd = projected_gradient_descent(model_fn, test_images, FLAGS.eps, 0.01, 40, np.inf) - test_acc_fgm = accuracy(params, (test_images_fgm, test_labels)) - test_acc_pgd = accuracy(params, (test_images_pgd, test_labels)) - - print("Epoch {} in {:0.2f} sec".format(epoch, epoch_time)) - print("Training set accuracy: {}".format(train_acc)) - print("Test set accuracy on clean examples: {}".format(test_acc)) - print("Test set accuracy on FGM adversarial examples: {}".format(test_acc_fgm)) - print("Test set accuracy on PGD adversarial examples: {}".format(test_acc_pgd)) - -if __name__ == '__main__': - flags.DEFINE_integer('nb_epochs', 8, 'Number of epochs.') - flags.DEFINE_float('eps', 0.3, 'Total epsilon for FGM and PGD attacks.') - - app.run(main) diff --git a/tutorials/future/tf2/cifar10_tutorial.py b/tutorials/future/tf2/cifar10_tutorial.py deleted file mode 100644 index 8ea148243..000000000 --- a/tutorials/future/tf2/cifar10_tutorial.py +++ /dev/null @@ -1,128 +0,0 @@ -import numpy as np -import math -import numpy as np -import tensorflow as tf -import tensorflow_datasets as tfds -from absl import app, flags -from easydict import EasyDict -from tensorflow.keras import Model -from tensorflow.keras.layers import AveragePooling2D, Dense, Flatten, Conv2D, MaxPool2D - -from cleverhans.future.tf2.attacks import projected_gradient_descent, fast_gradient_method - -FLAGS = flags.FLAGS - -class CNN(Model): - def __init__(self, nb_filters=64): - super(CNN, self).__init__() - img_size = 32 - log_resolution = int(round(math.log(img_size) / math.log(2))) - conv_args = dict( - activation=tf.nn.leaky_relu, - kernel_size=3, - padding='same') - self.layers_obj = [] - for scale in range(log_resolution - 2): - conv1 = Conv2D(nb_filters << scale, **conv_args) - conv2 = Conv2D(nb_filters << (scale + 1), **conv_args) - pool = AveragePooling2D(pool_size=(2, 2), strides=(2, 2)) - self.layers_obj.append(conv1) - self.layers_obj.append(conv2) - self.layers_obj.append(pool) - conv = Conv2D(10, **conv_args) - self.layers_obj.append(conv) - - def call(self, x): - for layer in self.layers_obj: - x = layer(x) - return tf.reduce_mean(x, [1, 2]) - - -def ld_cifar10(): - """Load training and test data.""" - - def convert_types(image, label): - image = tf.cast(image, tf.float32) - image /= 127.5 - image -= 1. - return image, label - - dataset, info = tfds.load('cifar10', - with_info=True, - as_supervised=True) - - def augment_mirror(x): - return tf.image.random_flip_left_right(x) - - def augment_shift(x, w=4): - y = tf.pad(x, [[w] * 2, [w] * 2, [0] * 2], mode='REFLECT') - return tf.image.random_crop(y, tf.shape(x)) - - mnist_train, mnist_test = dataset['train'], dataset['test'] - # Augmentation helps a lot in CIFAR10 - mnist_train = mnist_train.map(lambda x, y: (augment_mirror(augment_shift(x)), y)) - mnist_train = mnist_train.map(convert_types).shuffle(10000).batch(128) - mnist_test = mnist_test.map(convert_types).batch(128) - - return EasyDict(train=mnist_train, test=mnist_test) - - -def main(_): - # Load training and test data - data = ld_cifar10() - model = CNN() - loss_object = tf.losses.SparseCategoricalCrossentropy(from_logits=True) - optimizer = tf.optimizers.Adam(learning_rate=0.001) - - # Metrics to track the different accuracies. - train_loss = tf.metrics.Mean(name='train_loss') - test_acc_clean = tf.metrics.SparseCategoricalAccuracy() - test_acc_fgsm = tf.metrics.SparseCategoricalAccuracy() - test_acc_pgd = tf.metrics.SparseCategoricalAccuracy() - - @tf.function - def train_step(x, y): - with tf.GradientTape() as tape: - predictions = model(x) - loss = loss_object(y, predictions) - gradients = tape.gradient(loss, model.trainable_variables) - optimizer.apply_gradients(zip(gradients, model.trainable_variables)) - train_loss(loss) - - # Train model with adversarial training - for epoch in range(FLAGS.nb_epochs): - # keras like display of progress - progress_bar_train = tf.keras.utils.Progbar(50000) - for (x, y) in data.train: - if FLAGS.adv_train: - # Replace clean example with adversarial example for adversarial training - x = projected_gradient_descent(model, x, FLAGS.eps, 0.01, 40, np.inf) - train_step(x, y) - progress_bar_train.add(x.shape[0], values=[('loss', train_loss.result())]) - - # Evaluate on clean and adversarial data - progress_bar_test = tf.keras.utils.Progbar(10000) - for x, y in data.test: - y_pred = model(x) - test_acc_clean(y, y_pred) - - x_fgm = fast_gradient_method(model, x, FLAGS.eps, np.inf) - y_pred_fgm = model(x_fgm) - test_acc_fgsm(y, y_pred_fgm) - - x_pgd = projected_gradient_descent(model, x, FLAGS.eps, 0.01, 40, np.inf) - y_pred_pgd = model(x_pgd) - test_acc_pgd(y, y_pred_pgd) - - progress_bar_test.add(x.shape[0]) - - print('test acc on clean examples (%): {:.3f}'.format(test_acc_clean.result() * 100)) - print('test acc on FGM adversarial examples (%): {:.3f}'.format(test_acc_fgsm.result() * 100)) - print('test acc on PGD adversarial examples (%): {:.3f}'.format(test_acc_pgd.result() * 100)) - - -if __name__ == '__main__': - flags.DEFINE_integer('nb_epochs', 200, 'Number of epochs.') - flags.DEFINE_float('eps', 0.05, 'Total epsilon for FGM and PGD attacks.') - flags.DEFINE_bool('adv_train', False, 'Use adversarial training (on PGD adversarial examples).') - app.run(main) diff --git a/tutorials/future/tf2/mnist_tutorial.py b/tutorials/future/tf2/mnist_tutorial.py deleted file mode 100644 index 64a3938e6..000000000 --- a/tutorials/future/tf2/mnist_tutorial.py +++ /dev/null @@ -1,111 +0,0 @@ -import numpy as np -import tensorflow as tf -import tensorflow_datasets as tfds -from absl import app, flags -from easydict import EasyDict -from tensorflow.keras import Model -from tensorflow.keras.layers import Dense, Dropout, Flatten, Conv2D - -from cleverhans.future.tf2.attacks import projected_gradient_descent, fast_gradient_method - -FLAGS = flags.FLAGS - - -class Net(Model): - def __init__(self): - super(Net, self).__init__() - self.conv1 = Conv2D(64, 8, strides=(2, 2), activation='relu', padding='same') - self.conv2 = Conv2D(128, 6, strides=(2, 2), activation='relu', padding='valid') - self.conv3 = Conv2D(128, 5, strides=(1, 1), activation='relu', padding='valid') - self.dropout = Dropout(0.25) - self.flatten = Flatten() - self.dense1 = Dense(128, activation='relu') - self.dense2 = Dense(10) - - def call(self, x): - x = self.conv1(x) - x = self.conv2(x) - x = self.conv3(x) - x = self.dropout(x) - x = self.flatten(x) - x = self.dense1(x) - return self.dense2(x) - - -def ld_mnist(): - """Load training and test data.""" - - def convert_types(image, label): - image = tf.cast(image, tf.float32) - image /= 255 - return image, label - - dataset, info = tfds.load('mnist', - data_dir='gs://tfds-data/datasets', - with_info=True, - as_supervised=True) - mnist_train, mnist_test = dataset['train'], dataset['test'] - mnist_train = mnist_train.map(convert_types).shuffle(10000).batch(128) - mnist_test = mnist_test.map(convert_types).batch(128) - return EasyDict(train=mnist_train, test=mnist_test) - - -def main(_): - # Load training and test data - data = ld_mnist() - model = Net() - loss_object = tf.losses.SparseCategoricalCrossentropy(from_logits=True) - optimizer = tf.optimizers.Adam(learning_rate=0.001) - - # Metrics to track the different accuracies. - train_loss = tf.metrics.Mean(name='train_loss') - test_acc_clean = tf.metrics.SparseCategoricalAccuracy() - test_acc_fgsm = tf.metrics.SparseCategoricalAccuracy() - test_acc_pgd = tf.metrics.SparseCategoricalAccuracy() - - @tf.function - def train_step(x, y): - with tf.GradientTape() as tape: - predictions = model(x) - loss = loss_object(y, predictions) - gradients = tape.gradient(loss, model.trainable_variables) - optimizer.apply_gradients(zip(gradients, model.trainable_variables)) - train_loss(loss) - - # Train model with adversarial training - for epoch in range(FLAGS.nb_epochs): - # keras like display of progress - progress_bar_train = tf.keras.utils.Progbar(60000) - for (x, y) in data.train: - if FLAGS.adv_train: - # Replace clean example with adversarial example for adversarial training - x = projected_gradient_descent(model, x, FLAGS.eps, 0.01, 40, np.inf) - train_step(x, y) - progress_bar_train.add(x.shape[0], values=[('loss', train_loss.result())]) - - # Evaluate on clean and adversarial data - progress_bar_test = tf.keras.utils.Progbar(10000) - for x, y in data.test: - y_pred = model(x) - test_acc_clean(y, y_pred) - - x_fgm = fast_gradient_method(model, x, FLAGS.eps, np.inf) - y_pred_fgm = model(x_fgm) - test_acc_fgsm(y, y_pred_fgm) - - x_pgd = projected_gradient_descent(model, x, FLAGS.eps, 0.01, 40, np.inf) - y_pred_pgd = model(x_pgd) - test_acc_pgd(y, y_pred_pgd) - - progress_bar_test.add(x.shape[0]) - - print('test acc on clean examples (%): {:.3f}'.format(test_acc_clean.result() * 100)) - print('test acc on FGM adversarial examples (%): {:.3f}'.format(test_acc_fgsm.result() * 100)) - print('test acc on PGD adversarial examples (%): {:.3f}'.format(test_acc_pgd.result() * 100)) - - -if __name__ == '__main__': - flags.DEFINE_integer('nb_epochs', 8, 'Number of epochs.') - flags.DEFINE_float('eps', 0.3, 'Total epsilon for FGM and PGD attacks.') - flags.DEFINE_bool('adv_train', False, 'Use adversarial training (on PGD adversarial examples).') - app.run(main) diff --git a/tutorials/future/torch/cifar10_tutorial.py b/tutorials/future/torch/cifar10_tutorial.py deleted file mode 100644 index 9d7e974bd..000000000 --- a/tutorials/future/torch/cifar10_tutorial.py +++ /dev/null @@ -1,96 +0,0 @@ -from absl import app, flags -from easydict import EasyDict -import numpy as np -import torch -import torch.nn as nn -import torch.nn.functional as F -import torchvision - -from cleverhans.future.torch.attacks import fast_gradient_method, projected_gradient_descent - -FLAGS = flags.FLAGS - - -class CNN(torch.nn.Module): - """Basic CNN architecture.""" - - def __init__(self, in_channels=1): - super(CNN, self).__init__() - self.conv1 = nn.Conv2d(in_channels, 64, 8, 1) - self.conv2 = nn.Conv2d(64, 128, 6, 2) - self.conv3 = nn.Conv2d(128, 128, 5, 2) - self.fc = nn.Linear(128*3*3, 10) - - def forward(self, x): - x = F.relu(self.conv1(x)) - x = F.relu(self.conv2(x)) - x = F.relu(self.conv3(x)) - x = x.view(-1, 128*3*3) - x = self.fc(x) - return x - - -def ld_cifar10(): - """Load training and test data.""" - train_transforms = torchvision.transforms.Compose([torchvision.transforms.ToTensor()]) - test_transforms = torchvision.transforms.Compose([torchvision.transforms.ToTensor()]) - train_dataset = torchvision.datasets.CIFAR10(root='/tmp/data', train=True, transform=train_transforms, download=True) - test_dataset = torchvision.datasets.CIFAR10(root='/tmp/data', train=False, transform=test_transforms, download=True) - train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=128, shuffle=True, num_workers=2) - test_loader = torch.utils.data.DataLoader(test_dataset, batch_size=128, shuffle=False, num_workers=2) - return EasyDict(train=train_loader, test=test_loader) - - -def main(_): - # Load training and test data - data = ld_cifar10() - - # Instantiate model, loss, and optimizer for training - net = CNN(in_channels=3) - device = 'cuda' if torch.cuda.is_available() else 'cpu' - if device == 'cuda': - net = net.cuda() - loss_fn = torch.nn.CrossEntropyLoss(reduction='mean') - optimizer = torch.optim.Adam(net.parameters(), lr=1e-3) - - # Train vanilla model - net.train() - for epoch in range(1, FLAGS.nb_epochs + 1): - train_loss = 0. - for x, y in data.train: - x, y = x.to(device), y.to(device) - if FLAGS.adv_train: - # Replace clean example with adversarial example for adversarial training - x = projected_gradient_descent(net, x, FLAGS.eps, 0.01, 40, np.inf) - optimizer.zero_grad() - loss = loss_fn(net(x), y) - loss.backward() - optimizer.step() - train_loss += loss.item() - print('epoch: {}/{}, train loss: {:.3f}'.format(epoch, FLAGS.nb_epochs, train_loss)) - - # Evaluate on clean and adversarial data - net.eval() - report = EasyDict(nb_test=0, correct=0, correct_fgm=0, correct_pgd=0) - for x, y in data.test: - x, y = x.to(device), y.to(device) - x_fgm = fast_gradient_method(net, x, FLAGS.eps, np.inf) - x_pgd = projected_gradient_descent(net, x, FLAGS.eps, 0.01, 40, np.inf) - _, y_pred = net(x).max(1) # model prediction on clean examples - _, y_pred_fgm = net(x_fgm).max(1) # model prediction on FGM adversarial examples - _, y_pred_pgd = net(x_pgd).max(1) # model prediction on PGD adversarial examples - report.nb_test += y.size(0) - report.correct += y_pred.eq(y).sum().item() - report.correct_fgm += y_pred_fgm.eq(y).sum().item() - report.correct_pgd += y_pred_pgd.eq(y).sum().item() - print('test acc on clean examples (%): {:.3f}'.format(report.correct / report.nb_test * 100.)) - print('test acc on FGM adversarial examples (%): {:.3f}'.format(report.correct_fgm / report.nb_test * 100.)) - print('test acc on PGD adversarial examples (%): {:.3f}'.format(report.correct_pgd / report.nb_test * 100.)) - - -if __name__ == '__main__': - flags.DEFINE_integer('nb_epochs', 8, 'Number of epochs.') - flags.DEFINE_float('eps', 0.3, 'Total epsilon for FGM and PGD attacks.') - flags.DEFINE_bool('adv_train', False, 'Use adversarial training (on PGD adversarial examples).') - - app.run(main) diff --git a/tutorials/future/tf2/__init__.py b/tutorials/jax/__init__.py similarity index 100% rename from tutorials/future/tf2/__init__.py rename to tutorials/jax/__init__.py diff --git a/tutorials/jax/datasets.py b/tutorials/jax/datasets.py new file mode 100644 index 000000000..ff35b3a52 --- /dev/null +++ b/tutorials/jax/datasets.py @@ -0,0 +1,93 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Datasets used in examples.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import array +import gzip +import os +from os import path +import struct +from six.moves.urllib.request import urlretrieve + +import numpy as np + +from cleverhans.jax.utils import one_hot, partial_flatten + + +_DATA = "/tmp/jax_example_data/" + + +def _download(url, filename): + """Download a url to a file in the JAX data temp directory.""" + if not path.exists(_DATA): + os.makedirs(_DATA) + out_file = path.join(_DATA, filename) + if not path.isfile(out_file): + urlretrieve(url, out_file) + print("downloaded {} to {}".format(url, _DATA)) + + +def mnist_raw(): + """Download and parse the raw MNIST dataset.""" + # CVDF mirror of http://yann.lecun.com/exdb/mnist/ + base_url = "https://storage.googleapis.com/cvdf-datasets/mnist/" + + def parse_labels(filename): + with gzip.open(filename, "rb") as fh: + _ = struct.unpack(">II", fh.read(8)) + return np.array(array.array("B", fh.read()), dtype=np.uint8) + + def parse_images(filename): + with gzip.open(filename, "rb") as fh: + _, num_data, rows, cols = struct.unpack(">IIII", fh.read(16)) + return np.array(array.array("B", fh.read()), dtype=np.uint8).reshape( + num_data, rows, cols + ) + + for filename in [ + "train-images-idx3-ubyte.gz", + "train-labels-idx1-ubyte.gz", + "t10k-images-idx3-ubyte.gz", + "t10k-labels-idx1-ubyte.gz", + ]: + _download(base_url + filename, filename) + + train_images = parse_images(path.join(_DATA, "train-images-idx3-ubyte.gz")) + train_labels = parse_labels(path.join(_DATA, "train-labels-idx1-ubyte.gz")) + test_images = parse_images(path.join(_DATA, "t10k-images-idx3-ubyte.gz")) + test_labels = parse_labels(path.join(_DATA, "t10k-labels-idx1-ubyte.gz")) + + return train_images, train_labels, test_images, test_labels + + +def mnist(permute_train=False): + """Download, parse and process MNIST data to unit scale and one-hot labels.""" + train_images, train_labels, test_images, test_labels = mnist_raw() + + train_images = partial_flatten(train_images) / np.float32(255.0) + test_images = partial_flatten(test_images) / np.float32(255.0) + train_labels = one_hot(train_labels, 10) + test_labels = one_hot(test_labels, 10) + + if permute_train: + perm = np.random.RandomState(0).permutation(train_images.shape[0]) + train_images = train_images[perm] + train_labels = train_labels[perm] + + return train_images, train_labels, test_images, test_labels diff --git a/tutorials/jax/mnist_tutorial.py b/tutorials/jax/mnist_tutorial.py new file mode 100644 index 000000000..6b510c9fe --- /dev/null +++ b/tutorials/jax/mnist_tutorial.py @@ -0,0 +1,114 @@ +from absl import app, flags + +import datasets +import itertools +import time +import jax.numpy as np +import numpy.random as npr +from jax import jit, grad, random +from jax.experimental import optimizers +from jax.experimental import stax +from jax.experimental.stax import logsoftmax + +from cleverhans.jax.attacks.fast_gradient_method import fast_gradient_method +from cleverhans.jax.attacks.projected_gradient_descent import projected_gradient_descent + +FLAGS = flags.FLAGS + + +def main(_): + rng = random.PRNGKey(0) + + # Load MNIST dataset + train_images, train_labels, test_images, test_labels = datasets.mnist() + + batch_size = 128 + batch_shape = (-1, 28, 28, 1) + num_train = train_images.shape[0] + num_complete_batches, leftover = divmod(num_train, batch_size) + num_batches = num_complete_batches + bool(leftover) + + train_images = np.reshape(train_images, batch_shape) + test_images = np.reshape(test_images, batch_shape) + + def data_stream(): + rng = npr.RandomState(0) + while True: + perm = rng.permutation(num_train) + for i in range(num_batches): + batch_idx = perm[i * batch_size : (i + 1) * batch_size] + yield train_images[batch_idx], train_labels[batch_idx] + + batches = data_stream() + + # Model, loss, and accuracy functions + init_random_params, predict = stax.serial( + stax.Conv(32, (8, 8), strides=(2, 2), padding="SAME"), + stax.Relu, + stax.Conv(128, (6, 6), strides=(2, 2), padding="VALID"), + stax.Relu, + stax.Conv(128, (5, 5), strides=(1, 1), padding="VALID"), + stax.Flatten, + stax.Dense(128), + stax.Relu, + stax.Dense(10), + ) + + def loss(params, batch): + inputs, targets = batch + preds = predict(params, inputs) + return -np.mean(logsoftmax(preds) * targets) + + def accuracy(params, batch): + inputs, targets = batch + target_class = np.argmax(targets, axis=1) + predicted_class = np.argmax(predict(params, inputs), axis=1) + return np.mean(predicted_class == target_class) + + # Instantiate an optimizer + opt_init, opt_update, get_params = optimizers.adam(0.001) + + @jit + def update(i, opt_state, batch): + params = get_params(opt_state) + return opt_update(i, grad(loss)(params, batch), opt_state) + + # Initialize model + _, init_params = init_random_params(rng, batch_shape) + opt_state = opt_init(init_params) + itercount = itertools.count() + + # Training loop + print("\nStarting training...") + for epoch in range(FLAGS.nb_epochs): + start_time = time.time() + for _ in range(num_batches): + opt_state = update(next(itercount), opt_state, next(batches)) + epoch_time = time.time() - start_time + + # Evaluate model on clean data + params = get_params(opt_state) + train_acc = accuracy(params, (train_images, train_labels)) + test_acc = accuracy(params, (test_images, test_labels)) + + # Evaluate model on adversarial data + model_fn = lambda images: predict(params, images) + test_images_fgm = fast_gradient_method(model_fn, test_images, FLAGS.eps, np.inf) + test_images_pgd = projected_gradient_descent( + model_fn, test_images, FLAGS.eps, 0.01, 40, np.inf + ) + test_acc_fgm = accuracy(params, (test_images_fgm, test_labels)) + test_acc_pgd = accuracy(params, (test_images_pgd, test_labels)) + + print("Epoch {} in {:0.2f} sec".format(epoch, epoch_time)) + print("Training set accuracy: {}".format(train_acc)) + print("Test set accuracy on clean examples: {}".format(test_acc)) + print("Test set accuracy on FGM adversarial examples: {}".format(test_acc_fgm)) + print("Test set accuracy on PGD adversarial examples: {}".format(test_acc_pgd)) + + +if __name__ == "__main__": + flags.DEFINE_integer("nb_epochs", 8, "Number of epochs.") + flags.DEFINE_float("eps", 0.3, "Total epsilon for FGM and PGD attacks.") + + app.run(main) diff --git a/tutorials/future/torch/__init__.py b/tutorials/tf2/__init__.py similarity index 100% rename from tutorials/future/torch/__init__.py rename to tutorials/tf2/__init__.py diff --git a/tutorials/tf2/cifar10_tutorial.py b/tutorials/tf2/cifar10_tutorial.py new file mode 100644 index 000000000..9fb15475e --- /dev/null +++ b/tutorials/tf2/cifar10_tutorial.py @@ -0,0 +1,138 @@ +import math +import numpy as np +import tensorflow as tf +import tensorflow_datasets as tfds +from absl import app, flags +from easydict import EasyDict +from tensorflow.keras import Model +from tensorflow.keras.layers import AveragePooling2D, Conv2D + +from cleverhans.tf2.attacks.projected_gradient_descent import projected_gradient_descent +from cleverhans.tf2.attacks.fast_gradient_method import fast_gradient_method + +FLAGS = flags.FLAGS + + +class CNN(Model): + def __init__(self, nb_filters=64): + super(CNN, self).__init__() + img_size = 32 + log_resolution = int(round(math.log(img_size) / math.log(2))) + conv_args = dict(activation=tf.nn.leaky_relu, kernel_size=3, padding="same") + self.layers_obj = [] + for scale in range(log_resolution - 2): + conv1 = Conv2D(nb_filters << scale, **conv_args) + conv2 = Conv2D(nb_filters << (scale + 1), **conv_args) + pool = AveragePooling2D(pool_size=(2, 2), strides=(2, 2)) + self.layers_obj.append(conv1) + self.layers_obj.append(conv2) + self.layers_obj.append(pool) + conv = Conv2D(10, **conv_args) + self.layers_obj.append(conv) + + def call(self, x): + for layer in self.layers_obj: + x = layer(x) + return tf.reduce_mean(x, [1, 2]) + + +def ld_cifar10(): + """Load training and test data.""" + + def convert_types(image, label): + image = tf.cast(image, tf.float32) + image /= 127.5 + image -= 1.0 + return image, label + + dataset, info = tfds.load("cifar10", with_info=True, as_supervised=True) + + def augment_mirror(x): + return tf.image.random_flip_left_right(x) + + def augment_shift(x, w=4): + y = tf.pad(x, [[w] * 2, [w] * 2, [0] * 2], mode="REFLECT") + return tf.image.random_crop(y, tf.shape(x)) + + cifar10_train, cifar10_test = dataset["train"], dataset["test"] + # Augmentation helps a lot in CIFAR10 + cifar10_train = cifar10_train.map( + lambda x, y: (augment_mirror(augment_shift(x)), y) + ) + cifar10_train = cifar10_train.map(convert_types).shuffle(10000).batch(128) + cifar10_test = cifar10_test.map(convert_types).batch(128) + + return EasyDict(train=cifar10_train, test=cifar10_test) + + +def main(_): + # Load training and test data + data = ld_cifar10() + model = CNN() + loss_object = tf.losses.SparseCategoricalCrossentropy(from_logits=True) + optimizer = tf.optimizers.Adam(learning_rate=0.001) + + # Metrics to track the different accuracies. + train_loss = tf.metrics.Mean(name="train_loss") + test_acc_clean = tf.metrics.SparseCategoricalAccuracy() + test_acc_fgsm = tf.metrics.SparseCategoricalAccuracy() + test_acc_pgd = tf.metrics.SparseCategoricalAccuracy() + + @tf.function + def train_step(x, y): + with tf.GradientTape() as tape: + predictions = model(x) + loss = loss_object(y, predictions) + gradients = tape.gradient(loss, model.trainable_variables) + optimizer.apply_gradients(zip(gradients, model.trainable_variables)) + train_loss(loss) + + # Train model with adversarial training + for epoch in range(FLAGS.nb_epochs): + # keras like display of progress + progress_bar_train = tf.keras.utils.Progbar(50000) + for (x, y) in data.train: + if FLAGS.adv_train: + # Replace clean example with adversarial example for adversarial training + x = projected_gradient_descent(model, x, FLAGS.eps, 0.01, 40, np.inf) + train_step(x, y) + progress_bar_train.add(x.shape[0], values=[("loss", train_loss.result())]) + + # Evaluate on clean and adversarial data + progress_bar_test = tf.keras.utils.Progbar(10000) + for x, y in data.test: + y_pred = model(x) + test_acc_clean(y, y_pred) + + x_fgm = fast_gradient_method(model, x, FLAGS.eps, np.inf) + y_pred_fgm = model(x_fgm) + test_acc_fgsm(y, y_pred_fgm) + + x_pgd = projected_gradient_descent(model, x, FLAGS.eps, 0.01, 40, np.inf) + y_pred_pgd = model(x_pgd) + test_acc_pgd(y, y_pred_pgd) + + progress_bar_test.add(x.shape[0]) + + print( + "test acc on clean examples (%): {:.3f}".format(test_acc_clean.result() * 100) + ) + print( + "test acc on FGM adversarial examples (%): {:.3f}".format( + test_acc_fgsm.result() * 100 + ) + ) + print( + "test acc on PGD adversarial examples (%): {:.3f}".format( + test_acc_pgd.result() * 100 + ) + ) + + +if __name__ == "__main__": + flags.DEFINE_integer("nb_epochs", 8, "Number of epochs.") + flags.DEFINE_float("eps", 0.05, "Total epsilon for FGM and PGD attacks.") + flags.DEFINE_bool( + "adv_train", False, "Use adversarial training (on PGD adversarial examples)." + ) + app.run(main) diff --git a/tutorials/tf2/mnist_tutorial.py b/tutorials/tf2/mnist_tutorial.py new file mode 100644 index 000000000..4642a12c8 --- /dev/null +++ b/tutorials/tf2/mnist_tutorial.py @@ -0,0 +1,123 @@ +import numpy as np +import tensorflow as tf +import tensorflow_datasets as tfds +from absl import app, flags +from easydict import EasyDict +from tensorflow.keras import Model +from tensorflow.keras.layers import Dense, Dropout, Flatten, Conv2D + +from cleverhans.tf2.attacks.projected_gradient_descent import projected_gradient_descent +from cleverhans.tf2.attacks.fast_gradient_method import fast_gradient_method + +FLAGS = flags.FLAGS + + +class Net(Model): + def __init__(self): + super(Net, self).__init__() + self.conv1 = Conv2D(64, 8, strides=(2, 2), activation="relu", padding="same") + self.conv2 = Conv2D(128, 6, strides=(2, 2), activation="relu", padding="valid") + self.conv3 = Conv2D(128, 5, strides=(1, 1), activation="relu", padding="valid") + self.dropout = Dropout(0.25) + self.flatten = Flatten() + self.dense1 = Dense(128, activation="relu") + self.dense2 = Dense(10) + + def call(self, x): + x = self.conv1(x) + x = self.conv2(x) + x = self.conv3(x) + x = self.dropout(x) + x = self.flatten(x) + x = self.dense1(x) + return self.dense2(x) + + +def ld_mnist(): + """Load training and test data.""" + + def convert_types(image, label): + image = tf.cast(image, tf.float32) + image /= 255 + return image, label + + dataset, info = tfds.load( + "mnist", data_dir="gs://tfds-data/datasets", with_info=True, as_supervised=True + ) + mnist_train, mnist_test = dataset["train"], dataset["test"] + mnist_train = mnist_train.map(convert_types).shuffle(10000).batch(128) + mnist_test = mnist_test.map(convert_types).batch(128) + return EasyDict(train=mnist_train, test=mnist_test) + + +def main(_): + # Load training and test data + data = ld_mnist() + model = Net() + loss_object = tf.losses.SparseCategoricalCrossentropy(from_logits=True) + optimizer = tf.optimizers.Adam(learning_rate=0.001) + + # Metrics to track the different accuracies. + train_loss = tf.metrics.Mean(name="train_loss") + test_acc_clean = tf.metrics.SparseCategoricalAccuracy() + test_acc_fgsm = tf.metrics.SparseCategoricalAccuracy() + test_acc_pgd = tf.metrics.SparseCategoricalAccuracy() + + @tf.function + def train_step(x, y): + with tf.GradientTape() as tape: + predictions = model(x) + loss = loss_object(y, predictions) + gradients = tape.gradient(loss, model.trainable_variables) + optimizer.apply_gradients(zip(gradients, model.trainable_variables)) + train_loss(loss) + + # Train model with adversarial training + for epoch in range(FLAGS.nb_epochs): + # keras like display of progress + progress_bar_train = tf.keras.utils.Progbar(60000) + for (x, y) in data.train: + if FLAGS.adv_train: + # Replace clean example with adversarial example for adversarial training + x = projected_gradient_descent(model, x, FLAGS.eps, 0.01, 40, np.inf) + train_step(x, y) + progress_bar_train.add(x.shape[0], values=[("loss", train_loss.result())]) + + # Evaluate on clean and adversarial data + progress_bar_test = tf.keras.utils.Progbar(10000) + for x, y in data.test: + y_pred = model(x) + test_acc_clean(y, y_pred) + + x_fgm = fast_gradient_method(model, x, FLAGS.eps, np.inf) + y_pred_fgm = model(x_fgm) + test_acc_fgsm(y, y_pred_fgm) + + x_pgd = projected_gradient_descent(model, x, FLAGS.eps, 0.01, 40, np.inf) + y_pred_pgd = model(x_pgd) + test_acc_pgd(y, y_pred_pgd) + + progress_bar_test.add(x.shape[0]) + + print( + "test acc on clean examples (%): {:.3f}".format(test_acc_clean.result() * 100) + ) + print( + "test acc on FGM adversarial examples (%): {:.3f}".format( + test_acc_fgsm.result() * 100 + ) + ) + print( + "test acc on PGD adversarial examples (%): {:.3f}".format( + test_acc_pgd.result() * 100 + ) + ) + + +if __name__ == "__main__": + flags.DEFINE_integer("nb_epochs", 8, "Number of epochs.") + flags.DEFINE_float("eps", 0.3, "Total epsilon for FGM and PGD attacks.") + flags.DEFINE_bool( + "adv_train", False, "Use adversarial training (on PGD adversarial examples)." + ) + app.run(main) diff --git a/tutorials/torch/__init__.py b/tutorials/torch/__init__.py new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/tutorials/torch/__init__.py @@ -0,0 +1 @@ + diff --git a/tutorials/torch/cifar10_tutorial.py b/tutorials/torch/cifar10_tutorial.py new file mode 100644 index 000000000..9fc8d7793 --- /dev/null +++ b/tutorials/torch/cifar10_tutorial.py @@ -0,0 +1,133 @@ +from absl import app, flags +from easydict import EasyDict +import numpy as np +import torch +import torch.nn as nn +import torch.nn.functional as F +import torchvision + +from cleverhans.torch.attacks.fast_gradient_method import fast_gradient_method +from cleverhans.torch.attacks.projected_gradient_descent import ( + projected_gradient_descent, +) + +FLAGS = flags.FLAGS + + +class CNN(torch.nn.Module): + """Basic CNN architecture.""" + + def __init__(self, in_channels=1): + super(CNN, self).__init__() + self.conv1 = nn.Conv2d(in_channels, 64, 8, 1) + self.conv2 = nn.Conv2d(64, 128, 6, 2) + self.conv3 = nn.Conv2d(128, 128, 5, 2) + self.fc = nn.Linear(128 * 3 * 3, 10) + + def forward(self, x): + x = F.relu(self.conv1(x)) + x = F.relu(self.conv2(x)) + x = F.relu(self.conv3(x)) + x = x.view(-1, 128 * 3 * 3) + x = self.fc(x) + return x + + +def ld_cifar10(): + """Load training and test data.""" + train_transforms = torchvision.transforms.Compose( + [torchvision.transforms.ToTensor()] + ) + test_transforms = torchvision.transforms.Compose( + [torchvision.transforms.ToTensor()] + ) + train_dataset = torchvision.datasets.CIFAR10( + root="/tmp/data", train=True, transform=train_transforms, download=True + ) + test_dataset = torchvision.datasets.CIFAR10( + root="/tmp/data", train=False, transform=test_transforms, download=True + ) + train_loader = torch.utils.data.DataLoader( + train_dataset, batch_size=128, shuffle=True, num_workers=2 + ) + test_loader = torch.utils.data.DataLoader( + test_dataset, batch_size=128, shuffle=False, num_workers=2 + ) + return EasyDict(train=train_loader, test=test_loader) + + +def main(_): + # Load training and test data + data = ld_cifar10() + + # Instantiate model, loss, and optimizer for training + net = CNN(in_channels=3) + device = "cuda" if torch.cuda.is_available() else "cpu" + if device == "cuda": + net = net.cuda() + loss_fn = torch.nn.CrossEntropyLoss(reduction="mean") + optimizer = torch.optim.Adam(net.parameters(), lr=1e-3) + + # Train vanilla model + net.train() + for epoch in range(1, FLAGS.nb_epochs + 1): + train_loss = 0.0 + for x, y in data.train: + x, y = x.to(device), y.to(device) + if FLAGS.adv_train: + # Replace clean example with adversarial example for adversarial training + x = projected_gradient_descent(net, x, FLAGS.eps, 0.01, 40, np.inf) + optimizer.zero_grad() + loss = loss_fn(net(x), y) + loss.backward() + optimizer.step() + train_loss += loss.item() + print( + "epoch: {}/{}, train loss: {:.3f}".format( + epoch, FLAGS.nb_epochs, train_loss + ) + ) + + # Evaluate on clean and adversarial data + net.eval() + report = EasyDict(nb_test=0, correct=0, correct_fgm=0, correct_pgd=0) + for x, y in data.test: + x, y = x.to(device), y.to(device) + x_fgm = fast_gradient_method(net, x, FLAGS.eps, np.inf) + x_pgd = projected_gradient_descent(net, x, FLAGS.eps, 0.01, 40, np.inf) + _, y_pred = net(x).max(1) # model prediction on clean examples + _, y_pred_fgm = net(x_fgm).max( + 1 + ) # model prediction on FGM adversarial examples + _, y_pred_pgd = net(x_pgd).max( + 1 + ) # model prediction on PGD adversarial examples + report.nb_test += y.size(0) + report.correct += y_pred.eq(y).sum().item() + report.correct_fgm += y_pred_fgm.eq(y).sum().item() + report.correct_pgd += y_pred_pgd.eq(y).sum().item() + print( + "test acc on clean examples (%): {:.3f}".format( + report.correct / report.nb_test * 100.0 + ) + ) + print( + "test acc on FGM adversarial examples (%): {:.3f}".format( + report.correct_fgm / report.nb_test * 100.0 + ) + ) + print( + "test acc on PGD adversarial examples (%): {:.3f}".format( + report.correct_pgd / report.nb_test * 100.0 + ) + ) + + +if __name__ == "__main__": + flags.DEFINE_integer("nb_epochs", 8, "Number of epochs.") + flags.DEFINE_float("eps", 0.3, "Total epsilon for FGM and PGD attacks.") + flags.DEFINE_bool( + "adv_train", False, "Use adversarial training (on PGD adversarial examples)." + ) + + app.run(main)