├── datasets └── pubfig65_imagenet_test.h5 ├── LICENSE ├── .gitignore ├── pubfig65_fingerprint_vgg16.py ├── pubfig65_fingerprint_vggface.py ├── README.md ├── pubfig65_vggface_mimic_penalty_dssim.py ├── msssim_tf.py ├── utils_translearn.py ├── pubfig65_patch_neuron_distance.py └── mimic_penalty_dssim.py /datasets/pubfig65_imagenet_test.h5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bolunwang/translearn/HEAD/datasets/pubfig65_imagenet_test.h5 -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Bolun Wang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | 106 | sftp-config.json 107 | datasets/pubfig65_imagenet.h5 108 | datasets/pubfig65_mimic_samples_penalty_dssim_0.003.h5 109 | models/pubfig65_vggface_trans_nbtrain_90.h5 110 | models/vggface.h5 111 | -------------------------------------------------------------------------------- /pubfig65_fingerprint_vgg16.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # @Date : 2018-08-07 10:58:15 4 | # @Author : Bolun Wang (bolunwang@cs.ucsb.edu) 5 | # @Link : http://cs.ucsb.edu/~bolunwang 6 | 7 | import os 8 | import time 9 | 10 | import numpy as np 11 | import keras.backend as K 12 | K.set_learning_phase(0) 13 | from keras.models import Model 14 | 15 | from mimic_penalty_dssim import MimicPenaltyDSSIM 16 | 17 | import random 18 | random.seed(1234) 19 | 20 | from keras.models import load_model 21 | 22 | import utils_translearn 23 | 24 | 25 | ############################## 26 | # PARAMETERS # 27 | ############################## 28 | 29 | # parameters about the model 30 | 31 | NB_CLASSES = 65 32 | IMG_ROW = 224 33 | IMG_COL = 224 34 | IMG_COLOR = 3 35 | INTENSITY_RANGE = 'imagenet' 36 | 37 | # parameters about dataset/model/result path 38 | 39 | STUDENT_MODEL_FILE = 'models/pubfig65-vggface-trans-nb-train-90.h5' 40 | 41 | # parameters used for attack 42 | 43 | DEVICE = '0' # which GPU to use 44 | 45 | BATCH_SIZE = 1 46 | NB_IMGS = 1 47 | 48 | DSSIM_THRESHOLD = 1 49 | CUTOFF_LAYER = 22 50 | 51 | INITIAL_CONST = 1e10 52 | LR = 1 53 | MAX_ITER = 2000 54 | 55 | ############################## 56 | # END PARAMETERS # 57 | ############################## 58 | 59 | 60 | def load_and_build_models(student_model_file=STUDENT_MODEL_FILE, 61 | cutoff_layer=CUTOFF_LAYER): 62 | 63 | # load the student model 64 | print('loading student model') 65 | student_model = load_model(student_model_file) 66 | 67 | print('loading teacher model') 68 | from keras.applications.vgg16 import VGG16 69 | teacher_model = VGG16() 70 | 71 | # load the bottleneck model 72 | print('building bottleneck model') 73 | bottleneck_model = Model(teacher_model.input, 74 | teacher_model.layers[cutoff_layer - 1].output) 75 | bottleneck_model.compile(loss='categorical_crossentropy', 76 | optimizer='adam', 77 | metrics=['accuracy']) 78 | 79 | return bottleneck_model, student_model 80 | 81 | 82 | def pubfig_65_fingerprint_vgg16(): 83 | 84 | os.environ["CUDA_VISIBLE_DEVICES"] = DEVICE 85 | 86 | sess = utils_translearn.fix_gpu_memory() 87 | 88 | bottleneck_model, student_model = load_and_build_models() 89 | 90 | # form attacker class 91 | # setting mimic_img to False, since we are mimicking a specific vector 92 | print('loading attacker') 93 | attacker = MimicPenaltyDSSIM(sess, 94 | bottleneck_model, 95 | mimic_img=False, 96 | batch_size=BATCH_SIZE, 97 | intensity_range=INTENSITY_RANGE, 98 | initial_const=INITIAL_CONST, 99 | learning_rate=LR, 100 | max_iterations=MAX_ITER, 101 | l_threshold=DSSIM_THRESHOLD, 102 | verbose=1) 103 | 104 | print('building fingerprinting input') 105 | # initializing input with random noise 106 | X_source_raw = np.random.random((NB_IMGS, IMG_ROW, IMG_COL, IMG_COLOR)) 107 | X_source_raw *= 255.0 108 | X_source = utils_translearn.preprocess(X_source_raw, INTENSITY_RANGE) 109 | 110 | # build target bottleneck neuron vector as all-zero vector 111 | bottleneck_shape = ( 112 | [X_source.shape[0]] + 113 | list(bottleneck_model.layers[-1].output_shape[1:])) 114 | X_target_bottleneck = np.zeros(bottleneck_shape) 115 | 116 | # build fingerprinting input 117 | X_adv = attacker.attack(X_source, X_target_bottleneck) 118 | 119 | print('testing fingerprint image on student') 120 | gini_list = [] 121 | max_conf_list = [] 122 | Y_pred = student_model.predict(X_adv) 123 | Y_conf = np.max(Y_pred, axis=1) 124 | for idx in xrange(NB_IMGS): 125 | gini_list.append(utils_translearn.gini(Y_pred[idx])) 126 | max_conf_list.append(Y_conf[idx]) 127 | 128 | avg_gini = np.mean(gini_list) 129 | avg_max_conf = np.mean(max_conf_list) 130 | print('INFO: avg_gini: %f, avg_max_conf: %f' % (avg_gini, avg_max_conf)) 131 | 132 | pass 133 | 134 | 135 | if __name__ == '__main__': 136 | 137 | start_time = time.time() 138 | pubfig_65_fingerprint_vgg16() 139 | elapsed_time = time.time() - start_time 140 | print('elapsed time %f s' % (elapsed_time)) 141 | -------------------------------------------------------------------------------- /pubfig65_fingerprint_vggface.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # @Date : 2018-08-07 10:16:40 4 | # @Author : Bolun Wang (bolunwang@cs.ucsb.edu) 5 | # @Link : http://cs.ucsb.edu/~bolunwang 6 | 7 | import os 8 | import time 9 | 10 | import numpy as np 11 | import keras.backend as K 12 | K.set_learning_phase(0) 13 | from keras.models import Model 14 | 15 | from mimic_penalty_dssim import MimicPenaltyDSSIM 16 | 17 | import random 18 | random.seed(1234) 19 | 20 | from keras.models import load_model 21 | 22 | import utils_translearn 23 | 24 | 25 | ############################## 26 | # PARAMETERS # 27 | ############################## 28 | 29 | # parameters about the model 30 | 31 | NB_CLASSES = 65 32 | IMG_ROW = 224 33 | IMG_COL = 224 34 | IMG_COLOR = 3 35 | INTENSITY_RANGE = 'imagenet' 36 | 37 | # parameters about dataset/model/result path 38 | 39 | TEACHER_MODEL_FILE = 'models/vggface.h5' 40 | STUDENT_MODEL_FILE = 'models/pubfig65-vggface-trans-nb-train-90.h5' 41 | 42 | # parameters used for attack 43 | 44 | DEVICE = '0' # which GPU to use 45 | 46 | BATCH_SIZE = 1 # number of images being processed simultaneously, default is 1 47 | NB_IMGS = 1 # number of fingerprinting images generated 48 | 49 | # set to 1 here, as we do not care about how the image looks like 50 | DSSIM_THRESHOLD = 1 51 | CUTOFF_LAYER = 38 # ID of bottleneck layer 52 | 53 | INITIAL_CONST = 1e10 # Penalty coefficient. Controls how tight the bound is 54 | LR = 1 # Learning rate of the optimizer 55 | MAX_ITER = 2000 # maximul number of iteration 56 | 57 | ############################## 58 | # END PARAMETERS # 59 | ############################## 60 | 61 | 62 | def load_and_build_models(student_model_file=STUDENT_MODEL_FILE, 63 | teacher_model_file=TEACHER_MODEL_FILE, 64 | cutoff_layer=CUTOFF_LAYER): 65 | 66 | # load the student model 67 | print('loading student model') 68 | student_model = load_model(student_model_file) 69 | 70 | print('loading teacher model') 71 | teacher_model = load_model(teacher_model_file) 72 | 73 | # load the bottleneck model 74 | print('building bottleneck model') 75 | bottleneck_model = Model(teacher_model.input, 76 | teacher_model.layers[cutoff_layer - 1].output) 77 | bottleneck_model.compile(loss='categorical_crossentropy', 78 | optimizer='adam', 79 | metrics=['accuracy']) 80 | 81 | return bottleneck_model, student_model 82 | 83 | 84 | def pubfig65_fingerprint_vggface(): 85 | 86 | os.environ["CUDA_VISIBLE_DEVICES"] = DEVICE 87 | 88 | sess = utils_translearn.fix_gpu_memory() 89 | 90 | bottleneck_model, student_model = load_and_build_models() 91 | 92 | # form attacker class 93 | # setting mimic_img to False, since we are mimicking a specific vector 94 | print('loading attacker') 95 | attacker = MimicPenaltyDSSIM(sess, 96 | bottleneck_model, 97 | mimic_img=False, 98 | batch_size=BATCH_SIZE, 99 | intensity_range=INTENSITY_RANGE, 100 | initial_const=INITIAL_CONST, 101 | learning_rate=LR, 102 | max_iterations=MAX_ITER, 103 | l_threshold=DSSIM_THRESHOLD, 104 | verbose=1) 105 | 106 | print('building fingerprinting input') 107 | # initializing input with random noise 108 | X_source_raw = np.random.random((NB_IMGS, IMG_ROW, IMG_COL, IMG_COLOR)) 109 | X_source_raw *= 255.0 110 | X_source = utils_translearn.preprocess(X_source_raw, INTENSITY_RANGE) 111 | 112 | # build target bottleneck neuron vector as all-zero vector 113 | bottleneck_shape = ( 114 | [X_source.shape[0]] + 115 | list(bottleneck_model.layers[-1].output_shape[1:])) 116 | X_target_bottleneck = np.zeros(bottleneck_shape) 117 | 118 | # build fingerprinting input 119 | X_adv = attacker.attack(X_source, X_target_bottleneck) 120 | 121 | print('testing fingerprint image on student') 122 | gini_list = [] 123 | max_conf_list = [] 124 | Y_pred = student_model.predict(X_adv) 125 | Y_conf = np.max(Y_pred, axis=1) 126 | for idx in xrange(NB_IMGS): 127 | gini_list.append(utils_translearn.gini(Y_pred[idx])) 128 | max_conf_list.append(Y_conf[idx]) 129 | 130 | # Low gini index means fingerprinting is successful, the correct teacher 131 | # is found. You can also infer this from maximum confidence. If max_conf 132 | # is low (similar to 1 / NB_CLASSES), then fingerprinting is successful. 133 | avg_gini = np.mean(gini_list) 134 | avg_max_conf = np.mean(max_conf_list) 135 | print('INFO: avg_gini: %f, avg_max_conf: %f' % (avg_gini, avg_max_conf)) 136 | 137 | pass 138 | 139 | 140 | if __name__ == '__main__': 141 | 142 | start_time = time.time() 143 | pubfig65_fingerprint_vggface() 144 | elapsed_time = time.time() - start_time 145 | print('elapsed time %f s' % (elapsed_time)) 146 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TransLearn 2 | 3 | ### ABOUT 4 | 5 | This repository contains code implementation of the paper "[With Great Training Comes Great Vulnerability: Practical Attacks against Transfer Learning](http://people.cs.uchicago.edu/~ravenben/publications/pdf/translearn-usenixsec18.pdf)", at *USENIX Security 2018*. 6 | 7 | 8 | ### DEPENDENCIES 9 | 10 | Code is implemented using a mixure of Keras and TensorFlow. Following packages are used to perform the attack and setup the attack evaluation: 11 | 12 | - `keras==2.2.0` 13 | - `numpy==1.14.0` 14 | - `tensorflow-gpu==1.8.0` 15 | - `h5py==2.8.0` 16 | 17 | The code is tested using Python 2.7. 18 | 19 | 20 | ### HOWTO 21 | 22 | #### Attack 23 | 24 | We include a sample [script](pubfig65_vggface_mimic_penalty_dssim.py) that demonstrates how to perform the attack on the Face Recognition example and how to evaluate the attack performance. 25 | 26 | ``` 27 | python pubfig65_vggface_mimic_penalty_dssim.py 28 | ``` 29 | 30 | There are several parameters that need to be modified before running the code, which is included in the "[PARAMETER](pubfig65_vggface_mimic_penalty_dssim.py#L25-L60)" section of the script. 31 | 32 | 1. Model files of the Teacher and Student need to be downloaded using the following link, and placed at the correct path. Model files are specified by [`TEACHER_MODEL_FILE`](pubfig65_vggface_mimic_penalty_dssim.py#L39) and [`STUDENT_MODEL_FILE`](pubfig65_vggface_mimic_penalty_dssim.py#L40). You can download the pre-trained models using links provided in the section below, and place them under the `models` folder. 33 | 2. We included a [sample data file](datasets/pubfig65_imagenet_test.h5), which includes 1 image for each label in the Student model. Download the data file, and place it under the `datasets` folder. 34 | 3. If you are using GPU, you need to specify which GPU you want to use for the attack. This this specified by the [`DEVICE`](pubfig65_vggface_mimic_penalty_dssim.py#L46) variable. If the specified GPU is not found, it will fall back to CPU by default. 35 | 4. Attack configuration is specified by [this section](pubfig65_vggface_mimic_penalty_dssim.py#L48-L56) of parameters. Most important parameters are, [`NB_PAIR`](pubfig65_vggface_mimic_penalty_dssim.py#L49) and [`DSSIM_THRESHOLD`](pubfig65_vggface_mimic_penalty_dssim.py#L51). 36 | 37 | 38 | #### Fingerprinting 39 | 40 | We include two scripts showcasing how to fingerprint the Teacher model given a Student. [`pubfig65_fingerprint_vggface.py`](pubfig65_fingerprint_vggface.py) shows fingerprinting the VGGFace model and test on the Face Recognition model, which uses VGGFace as Teacher. This [`pubfig65_fingerprint_vgg16.py`](pubfig65_fingerprint_vgg16.py) shows fingerprinting the VGG-16 model and test on the Face Recognition model. As described in the paper, the fingerprint image of the correct Teacher should produce an evenly-distributed prediction result, which would have a very low Gini coefficient. For example, [`pubfig65_fingerprint_vggface.py`](pubfig65_fingerprint_vggface.py) produces a Gini coefficient of `0.003539`, and [`pubfig65_fingerprint_vgg16.py`](pubfig65_fingerprint_vgg16.py) produces a Gini coefficient of `0.508905`. 41 | 42 | To run these examples, simply run 43 | 44 | ``` 45 | python pubfig65_fingerprint_vggface.py 46 | ``` 47 | 48 | Similar as the previous attack example, there are several parameters you need to change. And there are several special modifications comparing with the previous attack example. 49 | 50 | 1. You need to specify the GPU used in [`DEVICE`](pubfig65_fingerprint_vggface.py#L44). 51 | 2. Path to model files are specified by [`TEACHER_MODEL_FILE`](pubfig65_fingerprint_vggface.py#L39) and [`STUDENT_MODEL_FILE`](pubfig65_fingerprint_vggface.py#L40). Or you can load Teacher model directly from Keras, inside the `load_and_build_models()` function, similar as [this](pubfig65_fingerprint_vgg16.py#L60-L79). 52 | 3. DSSIM threshold ([`DSSIM_THRESHOLD`](pubfig65_fingerprint_vgg16.py#L48)) is set to `1` in fingerprinting. This is because this is not intended to be an attack, therefore does not have to be stealthy. 53 | 4. When building the attacker, the [`mimic_img`](pubfig65_fingerprint_vggface.py#L97) flag is set to be `False`. This is because we mimic an all-zero vector, instead of internal representation of a target image. 54 | 55 | 56 | #### Patch 57 | 58 | This [script](pubfig65_patch_neuron_distance.py) contains an example of how to patch DNN using the updated loss function. To run this script, simply run 59 | 60 | ``` 61 | python pubfig65_patch_neuron_distance.py 62 | ``` 63 | 64 | Similar as the previous example, there is some setup before running this example, as described below. 65 | 66 | 1. Path to model files are specified by [`TEACHER_MODEL_FILE`](pubfig65_patch_neuron_distance.py#L35) and [`STUDENT_MODEL_FILE`](pubfig65_patch_neuron_distance.py#L36). 67 | 2. [`DATA_FILE`](pubfig65_patch_neuron_distance.py#L37) specifies the patch to the training/testing dataset. We use the `h5` format to store the dataset, but you can change it to any format you prefer. Dataset is loaded by the [`load_dataset()`](pubfig65_patch_neuron_distance.py#L187-L211) function. Be sure to modify the function if you change the dataset format. 68 | 3. Similar as before, you need to specify the GPU used for training. This is specified by `DEVICE`. 69 | 4. Parameters used by the patching is specified [here](pubfig65_patch_neuron_distance.py#L49-L55). We incrementally increase the neuron distance threshold to stablize the training process. More details are included in the documentation of the script. 70 | 71 | 72 | ### DATASETS 73 | 74 | Below is the list of datasets we used in the paper. 75 | 76 | - **PubFig**: This dataset is used to train the Face Recognition model in the paper. The detailed information about this dataset is included in [this](http://vision.seas.harvard.edu/pubfig83/) page. We use a specific [version](http://ic.unicamp.br/~chiachia/resources/pubfig83-aligned/) of the dataset, where images are aligned. 77 | - **CASIA Iris**: This dataset is used to train the Iris Recognition task. Detailed information is included in [this](http://biometrics.idealtest.org/) page. 78 | - **GTSRB**: This dataset is used to train the Traffic Sign Recognition model. Detailed information could be found [here](http://benchmark.ini.rub.de/?section=gtsrb&subsection=dataset). 79 | - **VGG Flower**: This dataset is used to train the Flower Recognition model. Detailed information and download link could be found [here](http://www.robots.ox.ac.uk/~vgg/data/flowers/102/index.html). 80 | 81 | 82 | ### PRE-TRAINED MODELS 83 | 84 | Below is a list of links to pre-trained models we used in the paper. All models are hosted on Dropbox. 85 | 86 | - **Face Recognition**: [link](https://www.dropbox.com/s/xcj8rfpy72ukalz/pubfig65_vggface_trans_nbtrain_90.h5?dl=0) to model. This model uses imagenet mean-centering as preprocessing. 87 | - **Iris Recognition**: [link](https://www.dropbox.com/s/pqeq135rdj9hk3x/iris_vgg16_trans.h5?dl=0) to model. This model uses imagenet mean-centering as preprocessing. 88 | - **Traffic Sign Recognition**: [link](https://www.dropbox.com/s/k6u7vygwt9wrcs0/gtsrb_vgg16_trans.h5?dl=0) to model. This model uses imagenet mean-centering as preprocessing. 89 | - **Flower Recognition**: [link](https://www.dropbox.com/s/fvqlaqljfmkeghi/flower_resnet50_trans.h5?dl=0) to model. This model uses inception preprocessing, which rescales the input to `[-1, 1]`. 90 | 91 | We also converted the pre-trained `VGGFace` model from Caffe to Keras. The architecture is defined in [`utils_translearn.py`](utils_translearn.py#L15-L83), and the pre-trained model weights can be downloaded [here](https://www.dropbox.com/s/94m2w1ofvd9raua/vggface.h5?dl=0). 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | -------------------------------------------------------------------------------- /pubfig65_vggface_mimic_penalty_dssim.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # @Date : 2018-08-03 22:51:25 4 | # @Author : Bolun Wang (bolunwang@cs.ucsb.edu) 5 | # @Link : http://cs.ucsb.edu/~bolunwang 6 | 7 | import os 8 | import time 9 | 10 | import numpy as np 11 | import keras.backend as K 12 | K.set_learning_phase(0) 13 | from keras.models import Model 14 | from keras.models import load_model 15 | 16 | import utils_translearn 17 | from mimic_penalty_dssim import MimicPenaltyDSSIM 18 | 19 | # for reproducing results 20 | import random 21 | random.seed(1234) 22 | import itertools 23 | 24 | 25 | ############################## 26 | # PARAMETERS # 27 | ############################## 28 | 29 | # parameters about the model 30 | 31 | NB_CLASSES = 65 32 | IMG_ROW = 224 33 | IMG_COL = 224 34 | IMG_COLOR = 3 35 | INTENSITY_RANGE = 'imagenet' 36 | 37 | # parameters about dataset/model/result path 38 | 39 | TEACHER_MODEL_FILE = 'models/vggface.h5' 40 | STUDENT_MODEL_FILE = 'models/pubfig65_vggface_trans_nbtrain_90.h5' 41 | DATA_FILE = 'datasets/pubfig65_imagenet_test.h5' 42 | RESULT_DIR = './pubfig65' 43 | 44 | # parameters used for attack 45 | 46 | DEVICE = '0' # which GPU to use 47 | 48 | BATCH_SIZE = 1 # number of images being processed simultaneously, default is 1 49 | NB_PAIR = 2 # number of pairs of source, target image 50 | 51 | DSSIM_THRESHOLD = 0.001 # DSSIM budget of perturbation 52 | CUTOFF_LAYER = 38 # Layer ID of the bottleneck layer (actual ID - 1) 53 | 54 | INITIAL_CONST = 1e10 # Penalty coefficient. Controls how tight the bound is 55 | LR = 1 # Learning rate of the optimizer 56 | MAX_ITER = 2000 # maximul number of iteration 57 | 58 | ############################## 59 | # END PARAMETERS # 60 | ############################## 61 | 62 | 63 | def load_dataset(data_file=DATA_FILE): 64 | 65 | dataset = utils_translearn.load_dataset( 66 | data_file, 67 | keys=['X_test', 'Y_test']) 68 | 69 | X = dataset['X_test'] 70 | Y = dataset['Y_test'] 71 | 72 | X = X.astype(np.float32) 73 | Y = Y.astype(np.float32) 74 | 75 | X = utils_translearn.preprocess(X, INTENSITY_RANGE) 76 | Y = np.argmax(Y, axis=1) 77 | 78 | return X, Y 79 | 80 | 81 | def load_and_build_models(student_model_file=STUDENT_MODEL_FILE, 82 | teacher_model_file=TEACHER_MODEL_FILE, 83 | cutoff_layer=CUTOFF_LAYER): 84 | 85 | # load the student model 86 | print('loading student model') 87 | student_model = load_model(student_model_file) 88 | 89 | print('loading teacher model') 90 | teacher_model = load_model(teacher_model_file) 91 | 92 | # load the bottleneck model 93 | print('building bottleneck model') 94 | bottleneck_model = Model(teacher_model.input, 95 | teacher_model.layers[cutoff_layer - 1].output) 96 | bottleneck_model.compile(loss='categorical_crossentropy', 97 | optimizer='adam', 98 | metrics=['accuracy']) 99 | 100 | return bottleneck_model, student_model 101 | 102 | 103 | def filter_data(X, Y, student_model): 104 | 105 | Y_pred = student_model.predict(X) 106 | accuracy = np.mean(np.argmax(Y_pred, axis=1) == Y) 107 | print('baseline accuracy of student %f' % accuracy) 108 | 109 | correct_indices = np.argmax(Y_pred, axis=1) == Y 110 | X = X[correct_indices] 111 | Y = Y[correct_indices] 112 | 113 | print('X shape: %s' % str(X.shape)) 114 | print('Y shape: %s' % str(Y.shape)) 115 | 116 | return X, Y 117 | 118 | 119 | def select_source_target(X, Y, source, target, sub_sample=(1, 1)): 120 | 121 | # pick source 122 | source_idx = Y == source 123 | X_source = X[source_idx] 124 | Y_source = Y[source_idx] 125 | # pick targets 126 | target_idx = Y == target 127 | X_target = X[target_idx] 128 | Y_target = Y[target_idx] 129 | 130 | source_random_idx = random.sample(range(X_source.shape[0]), sub_sample[0]) 131 | X_source = X_source[source_random_idx] 132 | Y_source = Y_source[source_random_idx] 133 | 134 | target_random_idx = random.sample(range(X_target.shape[0]), sub_sample[1]) 135 | X_target = X_target[target_random_idx] 136 | Y_target = Y_target[target_random_idx] 137 | 138 | return X_source, Y_source, X_target, Y_target 139 | 140 | 141 | def cal_bottleneck_sim(bottleneck_1, bottleneck_2, weights): 142 | 143 | bottleneck_1 = np.array(bottleneck_1) 144 | bottleneck_2 = np.array(bottleneck_2) 145 | bottleneck_1 *= weights 146 | bottleneck_2 *= weights 147 | bottleneck_diff = np.abs(bottleneck_1 - bottleneck_2) 148 | sim = np.sqrt(np.sum(np.square(bottleneck_diff), 149 | axis=tuple(range(1, len(bottleneck_1.shape))))) 150 | 151 | return sim 152 | 153 | 154 | def pubfig65_mimic_penalty_dssim(): 155 | 156 | os.environ["CUDA_VISIBLE_DEVICES"] = DEVICE 157 | 158 | sess = utils_translearn.fix_gpu_memory() 159 | 160 | # load models 161 | (bottleneck_model, student_model) = load_and_build_models() 162 | 163 | # form attacker class 164 | print('loading attacker') 165 | attacker = MimicPenaltyDSSIM(sess, 166 | bottleneck_model, 167 | batch_size=BATCH_SIZE, 168 | intensity_range=INTENSITY_RANGE, 169 | initial_const=INITIAL_CONST, 170 | learning_rate=LR, 171 | max_iterations=MAX_ITER, 172 | l_threshold=DSSIM_THRESHOLD, 173 | verbose=1) 174 | 175 | # load dataset for the student model 176 | print('loading dataset') 177 | X, Y = load_dataset() 178 | 179 | # filter data points, keep only correctly predicted samples 180 | print('filtering data') 181 | X, Y = filter_data(X, Y, student_model) 182 | 183 | # dumping raw images 184 | if not os.path.exists(RESULT_DIR): 185 | os.makedirs(RESULT_DIR) 186 | 187 | Y_label = list(np.unique(Y)) 188 | all_pair_list = list(itertools.permutations(Y_label, 2)) 189 | pair_list = random.sample( 190 | all_pair_list, 191 | min(NB_PAIR, len(all_pair_list))) 192 | 193 | for pair_idx, (source, target) in enumerate(pair_list): 194 | 195 | print('INFO: processing pair #%d source: %d, target: %d' 196 | % (pair_idx, source, target)) 197 | 198 | # sample images 199 | (X_source, Y_source, X_target, Y_target) = select_source_target( 200 | X, Y, source, target) 201 | 202 | # LAUNCH ATTACK 203 | X_adv = attacker.attack(X_source, X_target) 204 | 205 | # test target success 206 | Y_pred = student_model.predict(X_adv) 207 | Y_pred = np.argmax(Y_pred, axis=1) 208 | target_success = np.mean(Y_pred == target) 209 | 210 | # dumping imanges 211 | img_filename = ('%d-%d.png' % (source, target)) 212 | img_fullpath = '%s/%s' % (RESULT_DIR, img_filename) 213 | 214 | X_source_raw = utils_translearn.reverse_preprocess( 215 | X_source, INTENSITY_RANGE) 216 | X_target_raw = utils_translearn.reverse_preprocess( 217 | X_target, INTENSITY_RANGE) 218 | X_adv_raw = utils_translearn.reverse_preprocess( 219 | X_adv, INTENSITY_RANGE) 220 | 221 | grid_img = utils_translearn.generate_grid_img( 222 | [[X_source_raw[0], X_adv_raw[0], X_target_raw[0]]], 223 | gap=0, background=0) 224 | utils_translearn.dump_image(grid_img, img_fullpath, 'png') 225 | 226 | # print final information 227 | print('INFO: source: %d, target: %d, target_success: %d, img: %s' 228 | % (source, target, target_success, img_filename)) 229 | 230 | pass 231 | 232 | 233 | if __name__ == '__main__': 234 | 235 | start_time = time.time() 236 | pubfig65_mimic_penalty_dssim() 237 | elapsed_time = time.time() - start_time 238 | print('elapsed time %f s' % (elapsed_time)) 239 | -------------------------------------------------------------------------------- /msssim_tf.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | from __future__ import division 3 | from __future__ import print_function 4 | from __future__ import unicode_literals 5 | 6 | import numpy as np 7 | import tensorflow as tf 8 | 9 | 10 | def _FSpecialGauss(size, sigma): 11 | 12 | """Function to mimic the 'fspecial' gaussian MATLAB function.""" 13 | radius = size // 2 14 | offset = 0.0 15 | start, stop = -radius, radius + 1 16 | if size % 2 == 0: 17 | offset = 0.5 18 | stop -= 1 19 | x, y = np.mgrid[offset + start:stop, offset + start:stop] 20 | assert len(x) == size 21 | g = np.exp(-((x**2 + y**2) / (2.0 * sigma**2))) 22 | window = g / g.sum() 23 | window = np.reshape(window, (size, size, 1, 1)) 24 | window = tf.constant(window, dtype=tf.float32) 25 | 26 | return window 27 | 28 | 29 | def _Conv2dIndividual(input_tensor, filter_tensor, strides, padding): 30 | 31 | if input_tensor.get_shape().as_list()[3] == 3: 32 | input_split = tf.unstack(input_tensor, 3, axis=3) 33 | result_split = [tf.nn.conv2d(tf.expand_dims(in_t, -1), 34 | filter_tensor, 35 | strides=strides, 36 | padding=padding) 37 | for in_t in input_split] 38 | result = tf.stack(result_split, axis=3) 39 | result = tf.squeeze(result, axis=4) 40 | elif input_tensor.get_shape().as_list()[3] == 1: 41 | result = tf.nn.conv2d(input_tensor, 42 | filter_tensor, 43 | strides=strides, 44 | padding=padding) 45 | else: 46 | raise Exception('nb_channels must be 1 or 3, got %s instead' 47 | % str(input_tensor.get_shape().as_list())) 48 | 49 | return result 50 | 51 | 52 | def _SSIMForMultiScale(img1, img2, max_val=255, filter_size=11, 53 | filter_sigma=1.5, k1=0.01, k2=0.03): 54 | 55 | """Return the Structural Similarity Map between `img1` and `img2`. 56 | This function attempts to match the functionality of ssim_index_new.m by 57 | Zhou Wang: http://www.cns.nyu.edu/~lcv/ssim/msssim.zip 58 | Arguments: 59 | img1: Numpy array holding the first RGB image batch. 60 | img2: Numpy array holding the second RGB image batch. 61 | max_val: the dynamic range of the images (i.e., the difference 62 | between the maximum the and minimum allowed values). 63 | filter_size: Size of blur kernel to use 64 | (will be reduced for small images). 65 | filter_sigma: Standard deviation for Gaussian blur kernel 66 | (will be reduced for small images). 67 | k1: Constant used to maintain stability in the SSIM calculation 68 | (0.01 in the original paper). 69 | k2: Constant used to maintain stability in the SSIM calculation 70 | (0.03 in the original paper). 71 | Returns: 72 | Pair containing the mean SSIM and contrast sensitivity between 73 | `img1` and `img2`. 74 | Raises: 75 | RuntimeError: If input images don't have the same shape or don't 76 | have four dimensions: [batch_size, height, width, depth]. 77 | """ 78 | 79 | if img1.get_shape().as_list() != img2.get_shape().as_list(): 80 | raise RuntimeError( 81 | 'Input images must have the same shape (%s vs. %s).', 82 | img1.get_shape().as_list(), img2.get_shape().as_list()) 83 | if len(img1.get_shape().as_list()) != 4: 84 | raise RuntimeError('Input images must have four dimensions, not %d', 85 | len(img1.get_shape().as_list())) 86 | 87 | _, height, width, _ = img1.get_shape().as_list() 88 | 89 | # Filter size can't be larger than height or width of images. 90 | size = min(filter_size, height, width) 91 | 92 | # Scale down sigma if a smaller filter size is used. 93 | sigma = size * filter_sigma / filter_size if filter_size else 0 94 | 95 | if filter_size: 96 | window = _FSpecialGauss(size, sigma) 97 | mu1 = _Conv2dIndividual(img1, 98 | window, 99 | strides=[1, 1, 1, 1], 100 | padding='VALID') 101 | mu2 = _Conv2dIndividual(img2, 102 | window, 103 | strides=[1, 1, 1, 1], 104 | padding='VALID') 105 | sigma11 = _Conv2dIndividual(img1 * img1, 106 | window, 107 | strides=[1, 1, 1, 1], 108 | padding='VALID') 109 | sigma22 = _Conv2dIndividual(img2 * img2, 110 | window, 111 | strides=[1, 1, 1, 1], 112 | padding='VALID') 113 | sigma12 = _Conv2dIndividual(img1 * img2, 114 | window, 115 | strides=[1, 1, 1, 1], 116 | padding='VALID') 117 | else: 118 | # Empty blur kernel so no need to convolve. 119 | mu1, mu2 = img1, img2 120 | sigma11 = img1 * img1 121 | sigma22 = img2 * img2 122 | sigma12 = img1 * img2 123 | 124 | # return mu1[0, 0, 0, 0], sigma11[0, 0, 0, 0] 125 | 126 | mu11 = mu1 * mu1 127 | mu22 = mu2 * mu2 128 | mu12 = mu1 * mu2 129 | sigma11 -= mu11 130 | sigma22 -= mu22 131 | sigma12 -= mu12 132 | 133 | # Calculate intermediate values used by both ssim and cs_map. 134 | c1 = (k1 * max_val) ** 2 135 | c2 = (k2 * max_val) ** 2 136 | v1 = 2.0 * sigma12 + c2 137 | v2 = sigma11 + sigma22 + c2 138 | ssim = tf.reduce_mean(((2.0 * mu12 + c1) * v1) / ((mu11 + mu22 + c1) * v2)) 139 | cs = tf.reduce_mean(v1 / v2) 140 | 141 | return ssim, cs 142 | 143 | 144 | def MultiScaleSSIM(img1, img2, max_val=255, filter_size=11, filter_sigma=1.5, 145 | k1=0.01, k2=0.03, weights=None): 146 | 147 | """Return the MS-SSIM score between `img1` and `img2`. 148 | This function implements Multi-Scale Structural Similarity (MS-SSIM) Image 149 | Quality Assessment according to Zhou Wang's paper, "Multi-scale structural 150 | similarity for image quality assessment" (2003). 151 | Link: https://ece.uwaterloo.ca/~z70wang/publications/msssim.pdf 152 | Author's MATLAB implementation: 153 | http://www.cns.nyu.edu/~lcv/ssim/msssim.zip 154 | Arguments: 155 | img1: Numpy array holding the first RGB image batch. 156 | img2: Numpy array holding the second RGB image batch. 157 | max_val: the dynamic range of the images (i.e., the difference 158 | between the maximum the and minimum allowed values). 159 | filter_size: Size of blur kernel to use 160 | (will be reduced for small images). 161 | filter_sigma: Standard deviation for Gaussian blur kernel 162 | (will be reduced for small images). 163 | k1: Constant used to maintain stability in the SSIM calculation 164 | (0.01 in the original paper). 165 | k2: Constant used to maintain stability in the SSIM calculation 166 | (0.03 in the original paper). 167 | weights: List of weights for each level; if none, 168 | use five levels and the weights from the original paper. 169 | Returns: 170 | MS-SSIM score between `img1` and `img2`. 171 | Raises: 172 | RuntimeError: If input images don't have the same shape or 173 | don't have four dimensions: [batch_size, height, width, depth]. 174 | """ 175 | 176 | if img1.get_shape().as_list() != img2.get_shape().as_list(): 177 | raise RuntimeError( 178 | 'Input images must have the same shape (%s vs. %s).', 179 | img1.get_shape().as_list(), img2.get_shape().as_list()) 180 | if len(img1.get_shape().as_list()) != 4: 181 | raise RuntimeError('Input images must have four dimensions, not %d', 182 | len(img1.get_shape().as_list())) 183 | 184 | # Note: default weights don't sum to 1.0 but do match 185 | # the paper / matlab code. 186 | weights = tf.constant(np.array(weights if weights else 187 | [0.0448, 0.2856, 0.3001, 0.2363, 0.1333]), 188 | dtype=tf.float32) 189 | levels = weights.get_shape().as_list()[0] 190 | downsample_filter = [1, 2, 2, 1] 191 | mssim = [] 192 | mcs = [] 193 | for _ in range(levels): 194 | ssim, cs = _SSIMForMultiScale( 195 | img1, img2, max_val=max_val, filter_size=filter_size, 196 | filter_sigma=filter_sigma, k1=k1, k2=k2) 197 | mssim.append(ssim) 198 | mcs.append(cs) 199 | filtered = [tf.nn.avg_pool(im, 200 | downsample_filter, 201 | strides=[1, 2, 2, 1], 202 | padding='SAME') 203 | for im in [img1, img2]] 204 | img1, img2 = filtered 205 | 206 | return (tf.reduce_prod(tf.pow(mcs[0:levels - 1], weights[0:levels - 1])) * 207 | (mssim[levels - 1]**weights[levels - 1])) 208 | -------------------------------------------------------------------------------- /utils_translearn.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # @Date : 2018-08-03 11:42:59 4 | # @Author : Bolun Wang (bolunwang@cs.ucsb.edu) 5 | # @Link : http://cs.ucsb.edu/~bolunwang 6 | 7 | 8 | import tensorflow as tf 9 | import numpy as np 10 | from keras import backend as K 11 | from keras.preprocessing import image 12 | import h5py 13 | 14 | 15 | def vgg_face(): 16 | 17 | from keras.models import Model 18 | from keras.layers import Dense, Dropout, Flatten, Input, Activation 19 | from keras.layers import Conv2D, MaxPooling2D 20 | 21 | input_shape = (224, 224, 3) 22 | 23 | img_input = Input(shape=input_shape) 24 | 25 | # Block 1 26 | x = Conv2D(64, (3, 3), padding='same', name='conv1_1')(img_input) 27 | x = Activation('relu')(x) 28 | x = Conv2D(64, (3, 3), padding='same', name='conv1_2')(x) 29 | x = Activation('relu')(x) 30 | x = MaxPooling2D((2, 2), strides=(2, 2), name='pool1')(x) 31 | 32 | # Block 2 33 | x = Conv2D(128, (3, 3), padding='same', name='conv2_1')(x) 34 | x = Activation('relu')(x) 35 | x = Conv2D(128, (3, 3), padding='same', name='conv2_2')(x) 36 | x = Activation('relu')(x) 37 | x = MaxPooling2D((2, 2), strides=(2, 2), name='pool2')(x) 38 | 39 | # Block 3 40 | x = Conv2D(256, (3, 3), padding='same', name='conv3_1')(x) 41 | x = Activation('relu')(x) 42 | x = Conv2D(256, (3, 3), padding='same', name='conv3_2')(x) 43 | x = Activation('relu')(x) 44 | x = Conv2D(256, (3, 3), padding='same', name='conv3_3')(x) 45 | x = Activation('relu')(x) 46 | x = MaxPooling2D((2, 2), strides=(2, 2), name='pool3')(x) 47 | 48 | # Block 4 49 | x = Conv2D(512, (3, 3), padding='same', name='conv4_1')(x) 50 | x = Activation('relu')(x) 51 | x = Conv2D(512, (3, 3), padding='same', name='conv4_2')(x) 52 | x = Activation('relu')(x) 53 | x = Conv2D(512, (3, 3), padding='same', name='conv4_3')(x) 54 | x = Activation('relu')(x) 55 | x = MaxPooling2D((2, 2), strides=(2, 2), name='pool4')(x) 56 | 57 | # Block 5 58 | x = Conv2D(512, (3, 3), padding='same', name='conv5_1')(x) 59 | x = Activation('relu')(x) 60 | x = Conv2D(512, (3, 3), padding='same', name='conv5_2')(x) 61 | x = Activation('relu')(x) 62 | x = Conv2D(512, (3, 3), padding='same', name='conv5_3')(x) 63 | x = Activation('relu')(x) 64 | x = MaxPooling2D((2, 2), strides=(2, 2), name='pool5')(x) 65 | 66 | # Classification block 67 | x = Flatten(name='flatten')(x) 68 | x = Dense(4096, name='fc6')(x) 69 | x = Activation('relu')(x) 70 | x = Dropout(0.5)(x) 71 | x = Dense(4096, name='fc7')(x) 72 | x = Activation('relu')(x) 73 | x = Dropout(0.5)(x) 74 | x = Dense(2622, name='fc8')(x) 75 | x = Activation('softmax')(x) 76 | 77 | # Ensure that the model takes into account 78 | # any potential predecessors of `input_tensor`. 79 | inputs = img_input 80 | # Create model. 81 | model = Model(inputs, x, name='vgg_face') 82 | 83 | return model 84 | 85 | 86 | def gini(array): 87 | """Calculate the Gini coefficient of a numpy array.""" 88 | # based on bottom eq: http://www.statsdirect.com/help/content/image/stat0206_wmf.gif 89 | # from: http://www.statsdirect.com/help/default.htm#nonparametric_methods/gini.htm 90 | array = np.array(array, dtype=np.float32) 91 | array = array.flatten() # all values are treated equally, arrays must be 1d 92 | if np.amin(array) < 0: 93 | array -= np.amin(array) # values cannot be negative 94 | array += 1e-10 # values cannot be 0 95 | array = np.sort(array) # values must be sorted 96 | index = np.arange(1, array.shape[0] + 1) # index per array element 97 | n = array.shape[0] # number of array elements 98 | return ((np.sum((2 * index - n - 1) * array)) / (n * np.sum(array))) # Gini coefficient 99 | 100 | 101 | def generate_grid_img(img_array, gap=0.1, background=1): 102 | 103 | """ 104 | Assemble a matrix of images into a grid image 105 | :param img_array: input image matrix, use Python native list, each element 106 | in the list is a Numpy array. 107 | :param gap: portion of gap between images, relative ratio to image 108 | dimension 109 | :return: a single Numpy array 110 | """ 111 | 112 | gap = float(gap) 113 | row_num = len(img_array) 114 | col_num = len(img_array[0]) 115 | img_row_num, img_col_num, img_color_num = img_array[0][0].shape 116 | row_pixel_gap = int(img_row_num * gap) 117 | col_pixel_gap = int(img_col_num * gap) 118 | total_row_num = row_num * img_row_num + (row_num - 1) * row_pixel_gap 119 | total_col_num = col_num * img_col_num + (col_num - 1) * col_pixel_gap 120 | grid_img = (np.ones((total_row_num, total_col_num, img_color_num)) * 121 | background) 122 | 123 | for row_id in xrange(len(img_array)): 124 | for col_id in xrange(len(img_array[0])): 125 | row_index_start = row_id * (img_row_num + row_pixel_gap) 126 | row_index_end = row_index_start + img_row_num 127 | col_index_start = col_id * (img_col_num + col_pixel_gap) 128 | col_index_end = col_index_start + img_col_num 129 | grid_img[row_index_start:row_index_end, 130 | col_index_start:col_index_end, ] = img_array[row_id][col_id] 131 | 132 | return grid_img 133 | 134 | 135 | def dump_image(x, filename, format): 136 | 137 | img = image.array_to_img(x, scale=False) 138 | img.save(filename, format) 139 | 140 | return 141 | 142 | 143 | def load_dataset(data_filename, keys=None): 144 | 145 | ''' assume all datasets are numpy arrays ''' 146 | dataset = {} 147 | with h5py.File(data_filename) as hf: 148 | if keys is None: 149 | for name in hf: 150 | dataset[name] = np.array(hf.get(name)) 151 | else: 152 | for name in keys: 153 | dataset[name] = np.array(hf.get(name)) 154 | return dataset 155 | 156 | 157 | def fix_gpu_memory(mem_fraction=1): 158 | 159 | gpu_options = tf.GPUOptions(per_process_gpu_memory_fraction=mem_fraction) 160 | tf_config = tf.ConfigProto(gpu_options=gpu_options) 161 | tf_config.gpu_options.allow_growth = True 162 | tf_config.log_device_placement = False 163 | tf_config.allow_soft_placement = True 164 | init_op = tf.global_variables_initializer() 165 | sess = tf.Session(config=tf_config) 166 | sess.run(init_op) 167 | K.set_session(sess) 168 | 169 | return sess 170 | 171 | 172 | def cal_rmsd(X, X_adv): 173 | 174 | rmsd = np.mean(np.square(X - X_adv), axis=tuple(range(1, X.ndim))) 175 | rmsd = np.sqrt(rmsd) 176 | avg_rmsd = np.mean(rmsd) 177 | std_rmsd = np.std(rmsd) 178 | 179 | return avg_rmsd, std_rmsd 180 | 181 | 182 | def preprocess(X, method): 183 | 184 | # assume color last 185 | assert method in {'raw', 'imagenet', 'inception', 'mnist'} 186 | 187 | if method is 'raw': 188 | pass 189 | elif method is 'imagenet': 190 | X = imagenet_preprocessing(X) 191 | elif method is 'inception': 192 | X = inception_preprocessing(X) 193 | elif method is 'mnist': 194 | X = mnist_preprocessing(X) 195 | else: 196 | raise Exception('unknown method %s' % method) 197 | 198 | return X 199 | 200 | 201 | def reverse_preprocess(X, method): 202 | 203 | # assume color last 204 | assert method in {'raw', 'imagenet', 'inception', 'mnist'} 205 | 206 | if method is 'raw': 207 | pass 208 | elif method is 'imagenet': 209 | X = imagenet_reverse_preprocessing(X) 210 | elif method is 'inception': 211 | X = inception_reverse_preprocessing(X) 212 | elif method is 'mnist': 213 | X = mnist_reverse_preprocessing(X) 214 | else: 215 | raise Exception('unknown method %s' % method) 216 | 217 | return X 218 | 219 | 220 | def inception_reverse_preprocessing(x): 221 | 222 | x = np.array(x) 223 | 224 | x /= 2.0 225 | x += 0.5 226 | x *= 255.0 227 | 228 | return x 229 | 230 | 231 | def inception_preprocessing(x): 232 | 233 | x = np.array(x) 234 | 235 | x /= 255.0 236 | x -= 0.5 237 | x *= 2.0 238 | 239 | return x 240 | 241 | 242 | def mnist_preprocessing(x): 243 | 244 | x = np.array(x) 245 | x /= 255.0 246 | 247 | return x 248 | 249 | 250 | def mnist_reverse_preprocessing(x): 251 | 252 | x = np.array(x) 253 | x *= 255.0 254 | 255 | return x 256 | 257 | 258 | def imagenet_preprocessing(x, data_format=None): 259 | 260 | if data_format is None: 261 | data_format = K.image_data_format() 262 | assert data_format in ('channels_last', 'channels_first') 263 | 264 | x = np.array(x) 265 | if data_format == 'channels_first': 266 | # 'RGB'->'BGR' 267 | if x.ndim == 3: 268 | x = x[::-1, ...] 269 | else: 270 | x = x[:, ::-1, ...] 271 | else: 272 | # 'RGB'->'BGR' 273 | x = x[..., ::-1] 274 | 275 | mean = [103.939, 116.779, 123.68] 276 | std = None 277 | 278 | # Zero-center by mean pixel 279 | if data_format == 'channels_first': 280 | if x.ndim == 3: 281 | x[0, :, :] -= mean[0] 282 | x[1, :, :] -= mean[1] 283 | x[2, :, :] -= mean[2] 284 | if std is not None: 285 | x[0, :, :] /= std[0] 286 | x[1, :, :] /= std[1] 287 | x[2, :, :] /= std[2] 288 | else: 289 | x[:, 0, :, :] -= mean[0] 290 | x[:, 1, :, :] -= mean[1] 291 | x[:, 2, :, :] -= mean[2] 292 | if std is not None: 293 | x[:, 0, :, :] /= std[0] 294 | x[:, 1, :, :] /= std[1] 295 | x[:, 2, :, :] /= std[2] 296 | else: 297 | x[..., 0] -= mean[0] 298 | x[..., 1] -= mean[1] 299 | x[..., 2] -= mean[2] 300 | if std is not None: 301 | x[..., 0] /= std[0] 302 | x[..., 1] /= std[1] 303 | x[..., 2] /= std[2] 304 | 305 | return x 306 | 307 | 308 | def imagenet_reverse_preprocessing(x, data_format=None): 309 | 310 | """ Reverse preprocesses a tensor encoding a batch of images. 311 | # Arguments 312 | x: input Numpy tensor, 4D. 313 | data_format: data format of the image tensor. 314 | # Returns 315 | Preprocessed tensor. 316 | """ 317 | 318 | x = np.array(x) 319 | if data_format is None: 320 | data_format = K.image_data_format() 321 | assert data_format in ('channels_last', 'channels_first') 322 | 323 | if data_format == 'channels_first': 324 | if x.ndim == 3: 325 | # Zero-center by mean pixel 326 | x[0, :, :] += 103.939 327 | x[1, :, :] += 116.779 328 | x[2, :, :] += 123.68 329 | # 'BGR'->'RGB' 330 | x = x[::-1, :, :] 331 | else: 332 | x[:, 0, :, :] += 103.939 333 | x[:, 1, :, :] += 116.779 334 | x[:, 2, :, :] += 123.68 335 | x = x[:, ::-1, :, :] 336 | else: 337 | # Zero-center by mean pixel 338 | x[..., 0] += 103.939 339 | x[..., 1] += 116.779 340 | x[..., 2] += 123.68 341 | # 'BGR'->'RGB' 342 | x = x[..., ::-1] 343 | return x 344 | -------------------------------------------------------------------------------- /pubfig65_patch_neuron_distance.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # @Date : 2018-08-04 16:35:19 4 | # @Author : Bolun Wang (bolunwang@cs.ucsb.edu) 5 | # @Link : http://cs.ucsb.edu/~bolunwang 6 | 7 | import os 8 | import time 9 | from decimal import Decimal 10 | 11 | import numpy as np 12 | import keras.backend as K 13 | from keras.models import Model, load_model 14 | from keras.optimizers import SGD 15 | from keras.engine.topology import Layer 16 | from keras.layers import Input 17 | from keras.utils import to_categorical 18 | 19 | import utils_translearn 20 | 21 | ############################## 22 | # PARAMETERS # 23 | ############################## 24 | 25 | # parameters about the model 26 | 27 | NB_CLASSES = 65 28 | IMG_ROW = 224 29 | IMG_COL = 224 30 | IMG_COLOR = 3 31 | INTENSITY_RANGE = 'imagenet' 32 | 33 | # parameters about dataset/model/result path 34 | 35 | TEACHER_MODEL_FILE = 'models/vggface.h5' 36 | STUDENT_MODEL_FILE = 'models/pubfig65_vggface_trans_nbtrain_90.h5' 37 | DATA_FILE = 'datasets/pubfig65_imagenet.h5' 38 | ADV_DATA_FILE = 'datasets/pubfig65_mimic_samples_penalty_dssim_0.003.h5' 39 | RESULT_DIR = './pubfig65' 40 | 41 | # parameters used for attack 42 | 43 | DEVICE = '0' # which GPU to use 44 | 45 | BATCH_SIZE = 32 46 | 47 | CUTOFF_LAYER = 38 48 | 49 | LR = 0.001 # learning rate of the optimizer 50 | NB_EPOCHS = 50 # total number of epochs of the training 51 | NB_EPOCHS_SUB = 1 # sub steps of epochs for increasing the neuron distance 52 | LOSS_COEFF = 1e-6 # coefficient to balance 53 | 54 | CAP = 5 * 1000 # neuron distance threshold 55 | CAP_STEP = 200 # sub steps for increasing neuron distance threshold 56 | 57 | ############################## 58 | # END PARAMETERS # 59 | ############################## 60 | 61 | 62 | class DenseWeightDiff(Layer): 63 | 64 | ''' 65 | This layer calculates the difference between two input vectors. 66 | This is calculated as part of the objective to measure the distance 67 | between neuron vectors. 68 | Thie version weighs neurons using the sum of weights to the next layer 69 | that connect to each neuron. 70 | ''' 71 | 72 | def __init__(self, other_layer, **kwargs): 73 | 74 | self.other_layer = other_layer 75 | bottleneck_units = K.int_shape(self.other_layer.kernel)[0] 76 | self.scale_kernel = K.variable( 77 | [float(bottleneck_units)] * bottleneck_units, 78 | dtype=K.floatx()) 79 | super(DenseWeightDiff, self).__init__(**kwargs) 80 | 81 | return 82 | 83 | def call(self, x): 84 | 85 | x1, x2 = x 86 | sum_kernel = K.sum(K.abs(self.other_layer.kernel), axis=1) 87 | normalized_kernel = K.l2_normalize(sum_kernel) 88 | normalized_kernel = normalized_kernel * self.scale_kernel 89 | output = K.sqrt(K.sum( 90 | K.square(x1 - x2) * normalized_kernel, 91 | axis=1)) 92 | 93 | return output 94 | 95 | 96 | # two loss functions used in the patching 97 | 98 | def crossentropy(y_true, y_pred): 99 | 100 | from keras.losses import categorical_crossentropy 101 | entropy = categorical_crossentropy(y_true, y_pred) 102 | 103 | return entropy 104 | 105 | 106 | def cap_max(y_true, y_pred): 107 | 108 | return K.square(K.maximum(y_true - y_pred, 0)) 109 | 110 | 111 | def recompile_student_model(student_model): 112 | 113 | ''' 114 | Recompile the student model to incorporate the new loss function. 115 | ''' 116 | 117 | # initialize neuron distance layer with weights in the next dense 118 | dense_weight_diff = DenseWeightDiff(student_model.layers[CUTOFF_LAYER]) 119 | 120 | # second input is the original bottleneck neuron vector 121 | # this value is kept as a fixed input, and used to calculate distance 122 | bottleneck_shape = student_model.layers[CUTOFF_LAYER - 1].output_shape 123 | bottleneck_input = Input(bottleneck_shape[1:], name='input_2') 124 | 125 | # initialize the neuron distance layer 126 | # first input is the fixed neuron vector as the reference point 127 | # second input is the patched student output at the bottleneck layer 128 | weighted_diff = dense_weight_diff([ 129 | bottleneck_input, 130 | student_model.layers[CUTOFF_LAYER - 1].output]) 131 | 132 | # compile a new model with multiple inputs and multiple outputs 133 | model = Model( 134 | inputs=[student_model.input, 135 | bottleneck_input], 136 | outputs=[student_model.output, 137 | weighted_diff]) 138 | 139 | # compile with optimizer and a coefficient to balance two loss terms 140 | # first loss is a simple cross entropy 141 | # second loss is a capped max loss, which only becomes positive when 142 | # the predict value (actual neuron distance) is smaller than the 143 | # true value desired (pre-determined neuron distance threshold). 144 | # The original formulation is a constrained optimization problem. 145 | # We use penalty method to convert this into a multi objective 146 | # optimization problem. 147 | # COEFF is the penalty cost. Setting it to the appropriate value can 148 | # make sure the opmization will converge to a point where the 149 | # second term (constraint) will be met, while the first term can be 150 | # optimized. 151 | optimizer = SGD(lr=LR) 152 | model.compile(loss=[crossentropy, cap_max], 153 | loss_weights=[1, LOSS_COEFF], 154 | optimizer=optimizer, 155 | metrics=['accuracy']) 156 | 157 | return model 158 | 159 | 160 | def load_and_build_models(student_model_file=STUDENT_MODEL_FILE, 161 | teacher_model_file=TEACHER_MODEL_FILE, 162 | cutoff_layer=CUTOFF_LAYER): 163 | 164 | # load the student model 165 | print('loading student model') 166 | student_model = load_model(student_model_file) 167 | 168 | for idx, layer in enumerate(student_model.layers): 169 | layer.trainable = True 170 | 171 | print('loading teacher model') 172 | teacher_model = load_model(teacher_model_file) 173 | 174 | # load the bottleneck model 175 | print('building bottleneck model') 176 | bottleneck_model = Model(teacher_model.input, 177 | teacher_model.layers[cutoff_layer - 1].output) 178 | bottleneck_model.compile(loss='categorical_crossentropy', 179 | optimizer='adam', 180 | metrics=['accuracy']) 181 | 182 | student_model = recompile_student_model(student_model) 183 | 184 | return bottleneck_model, student_model 185 | 186 | 187 | def load_dataset(data_file=DATA_FILE): 188 | 189 | dataset = utils_translearn.load_dataset( 190 | data_file, 191 | keys=['X_train', 'Y_train', 'X_test', 'Y_test']) 192 | 193 | X_train = dataset['X_train'] 194 | Y_train = dataset['Y_train'] 195 | X_test = dataset['X_test'] 196 | Y_test = dataset['Y_test'] 197 | 198 | X_train = X_train.astype(np.float32) 199 | Y_train = Y_train.astype(np.float32) 200 | X_test = X_test.astype(np.float32) 201 | Y_test = Y_test.astype(np.float32) 202 | 203 | X_train = utils_translearn.preprocess(X_train, INTENSITY_RANGE) 204 | X_test = utils_translearn.preprocess(X_test, INTENSITY_RANGE) 205 | 206 | print('X_train shape: %s' % str(X_train.shape)) 207 | print('Y_train shape: %s' % str(Y_train.shape)) 208 | print('X_test shape: %s' % str(X_test.shape)) 209 | print('Y_test shape: %s' % str(Y_test.shape)) 210 | 211 | return X_train, Y_train, X_test, Y_test 212 | 213 | 214 | def load_adv_image(adv_data_file=ADV_DATA_FILE): 215 | 216 | dataset = utils_translearn.load_dataset( 217 | adv_data_file, 218 | keys=['X_source', 'X_adv', 'Y_source', 'Y_target']) 219 | X_source = dataset['X_source'] 220 | X_adv = dataset['X_adv'] 221 | Y_source = dataset['Y_source'] 222 | Y_target = dataset['Y_target'] 223 | 224 | Y_source = to_categorical(Y_source, NB_CLASSES) 225 | Y_target = to_categorical(Y_target, NB_CLASSES) 226 | 227 | print('X_adv shape: %s' % str(X_adv.shape)) 228 | print('Y_source shape: %s' % str(Y_source.shape)) 229 | 230 | return X_source, X_adv, Y_source, Y_target 231 | 232 | 233 | def train_model(model, X_train, Y_train, 234 | cap=CAP, cap_step=CAP_STEP, 235 | nb_epochs_sub=NB_EPOCHS_SUB, nb_epochs=NB_EPOCHS): 236 | 237 | ''' 238 | We use an incremental approach to stablize the training process.training 239 | After each nb_epochs_sub, we increase the neuron distance threshold 240 | (current_cap) by cap_step, until we reach the final threshold (cap). 241 | This function prints out the intermediate log of the training result. 242 | cap shows the current neuron distance threshold used in this epoch. 243 | loss_total is the total loss, loss_ce is the cross entropy, loss_dis 244 | is the current distance away from the desired neuron distance threshold 245 | (temporary threshold). raw shows the raw distance. acc shows the 246 | classification accuracy on the training dataset. 247 | ''' 248 | 249 | callbacks = [] 250 | 251 | current_cap = 0 252 | 253 | for epoch in range(nb_epochs): 254 | 255 | if epoch % nb_epochs_sub == 0: 256 | if current_cap < cap: 257 | current_cap += cap_step 258 | Y_train = reset_cap(Y_train, current_cap) 259 | 260 | history = model.fit(X_train, Y_train, 261 | epochs=1, 262 | verbose=0, 263 | callbacks=callbacks) 264 | logs = history.history 265 | 266 | loss_total = logs['loss'][0] 267 | loss_ce = logs['activation_17_loss'][0] 268 | loss_dis = logs['dense_weight_diff_1_loss'][0], 269 | loss_dis = np.sqrt(loss_dis) 270 | raw_dis = current_cap - loss_dis 271 | dis_over = loss_dis / current_cap * 100 272 | acc = logs['activation_17_acc'][0] 273 | print('epoch %04d/%04d; cap: %.2E, loss_total: %f, loss_ce: %f, loss_dis: %.2f (raw: %.2f, %.1f%% less), acc: %.4f' % 274 | (epoch, NB_EPOCHS, Decimal(current_cap), 275 | loss_total, loss_ce, loss_dis, raw_dis, dis_over, acc)) 276 | 277 | return model 278 | 279 | 280 | def transform_dataset(bottleneck_model, X, Y, cap): 281 | 282 | ''' 283 | Transform the original training data to add bottleneck neuron vector 284 | into X, and adding the neuron distance threshold (cap) to Y. 285 | ''' 286 | 287 | Y_pred = bottleneck_model.predict(X) 288 | 289 | X_bottleneck = Y_pred 290 | 291 | X = [X, X_bottleneck] 292 | Y = [Y, np.array([cap] * Y.shape[0])] 293 | 294 | return X, Y 295 | 296 | 297 | def reset_cap(Y, cap): 298 | 299 | # resetting neuron distance threshold 300 | Y[1] = np.array([cap] * Y[0].shape[0]) 301 | 302 | return Y 303 | 304 | 305 | def eval_model(student_model, X_test, Y_test, X_adv, Y_adv): 306 | 307 | # evaluate model classification accuracy and attack success rate 308 | Y_pred = student_model.predict(X_test) 309 | correct_indices = ( 310 | np.argmax(Y_pred[0], axis=1) == 311 | np.argmax(Y_test[0], axis=1)) 312 | test_acc = np.mean(correct_indices) 313 | test_neuron_sim = np.mean(Y_pred[1]) 314 | 315 | Y_pred = student_model.predict(X_adv) 316 | correct_indices = ( 317 | np.argmax(Y_pred[0], axis=1) == 318 | np.argmax(Y_adv[0], axis=1)) 319 | attack_success = np.mean(correct_indices) 320 | print('INFO: acc: %f, attack: %f, sim: %f' % 321 | (test_acc, attack_success, test_neuron_sim)) 322 | 323 | return 324 | 325 | 326 | def pubfig65_patch_neuron_distance(): 327 | 328 | # specify which GPU to use for training 329 | os.environ["CUDA_VISIBLE_DEVICES"] = DEVICE 330 | utils_translearn.fix_gpu_memory() 331 | 332 | print('loading models') 333 | bottleneck_model, student_model = load_and_build_models() 334 | 335 | print('loading dataset') 336 | X_train, Y_train, X_test, Y_test = load_dataset() 337 | X_source, X_adv, Y_source, Y_target = load_adv_image() 338 | 339 | # modify data to include bottleneck neuron values in Teacher (into X) and 340 | # add neuron distance threshold (into Y) 341 | print('transforming datasets') 342 | X_train, Y_train = transform_dataset(bottleneck_model, X_train, Y_train, 0) 343 | X_test, Y_test = transform_dataset(bottleneck_model, X_test, Y_test, 0) 344 | X_adv, Y_target = transform_dataset(bottleneck_model, X_adv, Y_target, 0) 345 | 346 | # evaluate model performance before training 347 | eval_model(student_model, X_test, Y_test, X_adv, Y_target) 348 | 349 | # model training 350 | student_model = train_model(student_model, X_train, Y_train, 351 | cap=CAP, cap_step=CAP_STEP) 352 | 353 | # evaluate model performance after training 354 | eval_model(student_model, X_test, Y_test, X_adv, Y_target) 355 | 356 | return 357 | 358 | 359 | if __name__ == '__main__': 360 | 361 | start_time = time.time() 362 | pubfig65_patch_neuron_distance() 363 | elapsed_time = time.time() - start_time 364 | print('elapsed time %f s' % (elapsed_time)) 365 | -------------------------------------------------------------------------------- /mimic_penalty_dssim.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # @Date : 2018-08-03 22:50:49 4 | # @Author : Bolun Wang (bolunwang@cs.ucsb.edu) 5 | # @Link : http://cs.ucsb.edu/~bolunwang 6 | 7 | import numpy as np 8 | import tensorflow as tf 9 | from six.moves import xrange 10 | 11 | import datetime 12 | import time 13 | from decimal import Decimal 14 | 15 | from utils_translearn import preprocess, reverse_preprocess, cal_rmsd 16 | from msssim_tf import MultiScaleSSIM 17 | 18 | 19 | class MimicPenaltyDSSIM: 20 | 21 | # if the attack is trying to mimic a target image or a neuron vector 22 | MIMIC_IMG = True 23 | # number of iterations to perform gradient descent 24 | MAX_ITERATIONS = 10000 25 | # larger values converge faster to less accurate results 26 | LEARNING_RATE = 1e-2 27 | # the initial constant c to pick as a first guess 28 | INITIAL_CONST = 1 29 | # pixel intensity range 30 | INTENSITY_RANGE = 'imagenet' 31 | # threshold for distance 32 | L_THRESHOLD = 0.03 33 | # whether keep the final result or the best result 34 | KEEP_FINAL = False 35 | # max_val of image 36 | MAX_VAL = 255 37 | # The following variables are used by DSSIM, should keep as default 38 | # filter size in SSIM 39 | FILTER_SIZE = 11 40 | # filter sigma in SSIM 41 | FILTER_SIGMA = 1.5 42 | # weights used in MS-SSIM 43 | SCALE_WEIGHTS = None 44 | 45 | def __init__(self, sess, bottleneck_model, mimic_img=MIMIC_IMG, 46 | batch_size=1, learning_rate=LEARNING_RATE, 47 | max_iterations=MAX_ITERATIONS, initial_const=INITIAL_CONST, 48 | intensity_range=INTENSITY_RANGE, l_threshold=L_THRESHOLD, 49 | max_val=MAX_VAL, keep_final=KEEP_FINAL, 50 | filter_size=FILTER_SIZE, filter_sigma=FILTER_SIGMA, 51 | scale_weights=SCALE_WEIGHTS, 52 | verbose=0): 53 | """ 54 | The L_2 optimized attack. 55 | Returns adversarial examples for the supplied bottleneck model. 56 | sess: A TensorFlow session 57 | bottleneck_model: The teacher model cut off at the layer attacker 58 | tries to launch the mimicry attack 59 | mimic_img: Whether the attack is trying to mimic a target image 60 | or a bottleneck neuron vector directly. 61 | batch_size: Number of attacks to run simultaneously. 62 | learning_rate: The learning rate for the attack algorithm. 63 | Smaller values produce better results but are slower to converge. 64 | max_iterations: The maximum number of iterations. 65 | Larger values are more accurate; setting too small will require 66 | a large learning rate and will produce poor results. 67 | initial_const: The initial tradeoff-constant to use to tune the 68 | relative importance of DSSIM and bottleneck similarity. 69 | intensity_range: The preprocessing method used by the model. 70 | 'raw': no preprocessing 71 | 'imagenet': default imagenet mean centering 72 | 'inception': inception preprocessing, scaling to [-1, 1] 73 | 'mnist': scaling to [0, 1] 74 | l_threshold: The DSSIM budget used for crafting adversarial samples. 75 | max_val: Maximum pixel intensity. Default set to 255. 76 | keep_final: Whether to keep the final optimization result or keep the 77 | best result throughout the process. 78 | verbose: Whether to output the intermediate log or not. 79 | """ 80 | 81 | assert intensity_range in {'raw', 'imagenet', 'inception', 'mnist'} 82 | 83 | # constant used for tanh transformation to avoid corner cases 84 | self.tanh_constant = 2 - 1e-6 85 | self.sess = sess 86 | self.MIMIC_IMG = mimic_img 87 | self.LEARNING_RATE = learning_rate 88 | self.MAX_ITERATIONS = max_iterations 89 | self.initial_const = initial_const 90 | self.batch_size = batch_size 91 | self.intensity_range = intensity_range 92 | self.l_threshold = l_threshold 93 | self.max_val = max_val 94 | self.keep_final = keep_final 95 | self.verbose = verbose 96 | 97 | self.input_shape = tuple( 98 | [self.batch_size] + 99 | list(bottleneck_model.input_shape[1:])) 100 | 101 | self.bottleneck_shape = tuple( 102 | [self.batch_size] + 103 | list(bottleneck_model.output_shape[1:])) 104 | 105 | ''' 106 | VARIABLE ASSIGNMENT 107 | ''' 108 | 109 | # the variable we're going to optimize over 110 | self.modifier = tf.Variable( 111 | np.zeros(self.input_shape, dtype=np.float32)) 112 | 113 | # target image in tanh space 114 | if self.MIMIC_IMG: 115 | self.timg_tanh = tf.Variable( 116 | np.zeros(self.input_shape), dtype=np.float32) 117 | else: 118 | self.bottleneck_t_raw = tf.Variable( 119 | np.zeros(self.bottleneck_shape), dtype=np.float32) 120 | # source image in tanh space 121 | self.simg_tanh = tf.Variable( 122 | np.zeros(self.input_shape), dtype=np.float32) 123 | self.const = tf.Variable(np.ones(batch_size), dtype=np.float32) 124 | self.mask = tf.Variable(np.ones((batch_size), dtype=np.bool)) 125 | self.weights = tf.Variable(np.ones(self.bottleneck_shape, 126 | dtype=np.float32)) 127 | 128 | # and here's what we use to assign them 129 | self.assign_modifier = tf.placeholder(tf.float32, self.input_shape) 130 | if self.MIMIC_IMG: 131 | self.assign_timg_tanh = tf.placeholder( 132 | tf.float32, self.input_shape) 133 | else: 134 | self.assign_bottleneck_t_raw = tf.placeholder( 135 | tf.float32, self.bottleneck_shape) 136 | self.assign_simg_tanh = tf.placeholder(tf.float32, self.input_shape) 137 | self.assign_const = tf.placeholder(tf.float32, (batch_size)) 138 | self.assign_mask = tf.placeholder(tf.bool, (batch_size)) 139 | self.assign_weights = tf.placeholder(tf.float32, self.bottleneck_shape) 140 | 141 | ''' 142 | PREPROCESSING 143 | ''' 144 | 145 | # the resulting image, tanh'd to keep bounded from -0.5 to 0.5 146 | # adversarial image in raw space 147 | self.aimg_raw = (tf.tanh(self.modifier + self.simg_tanh) / 148 | self.tanh_constant + 149 | 0.5) * 255.0 150 | # source image in raw space 151 | self.simg_raw = (tf.tanh(self.simg_tanh) / 152 | self.tanh_constant + 153 | 0.5) * 255.0 154 | if self.MIMIC_IMG: 155 | # target image in raw space 156 | self.timg_raw = (tf.tanh(self.timg_tanh) / 157 | self.tanh_constant + 158 | 0.5) * 255.0 159 | 160 | # convert source and adversarial image into input space 161 | if self.intensity_range == 'imagenet': 162 | mean = tf.constant(np.repeat([[[[103.939, 116.779, 123.68]]]], 163 | self.batch_size, 164 | axis=0), 165 | dtype=tf.float32, 166 | name='img_mean') 167 | self.aimg_input = (self.aimg_raw[..., ::-1] - mean) 168 | self.simg_input = (self.simg_raw[..., ::-1] - mean) 169 | if self.MIMIC_IMG: 170 | self.timg_input = (self.timg_raw[..., ::-1] - mean) 171 | 172 | elif self.intensity_range == 'inception': 173 | self.aimg_input = (self.aimg_raw / 255.0 - 0.5) * 2.0 174 | self.simg_input = (self.simg_raw / 255.0 - 0.5) * 2.0 175 | if self.MIMIC_IMG: 176 | self.timg_input = (self.timg_raw / 255.0 - 0.5) * 2.0 177 | 178 | elif self.intensity_range == 'mnist': 179 | self.aimg_input = self.aimg_raw / 255.0 180 | self.simg_input = self.simg_raw / 255.0 181 | if self.MIMIC_IMG: 182 | self.timg_input = self.timg_raw / 255.0 183 | 184 | elif self.intensity_range == 'raw': 185 | self.aimg_input = self.aimg_raw 186 | self.simg_input = self.simg_raw 187 | if self.MIMIC_IMG: 188 | self.timg_input = self.timg_raw 189 | 190 | ''' 191 | CONSTRAINTS: perturbation 192 | DSSIM: structural dis-similarity between two images. 193 | ''' 194 | 195 | def batch_gen_DSSIM(aimg_raw_split, simg_raw_split): 196 | 197 | msssim_split = [ 198 | MultiScaleSSIM( 199 | tf.expand_dims(aimg_raw_split[idx], 0), 200 | tf.expand_dims(simg_raw_split[idx], 0), 201 | max_val=max_val, filter_size=filter_size, 202 | filter_sigma=filter_sigma, weights=scale_weights) 203 | for idx in xrange(batch_size)] 204 | 205 | dssim = (1.0 - tf.stack(msssim_split)) / 2.0 206 | 207 | return dssim 208 | 209 | # unstack tensor into list to calculate DSSIM 210 | aimg_raw_split = tf.unstack(self.aimg_raw, batch_size, axis=0) 211 | simg_raw_split = tf.unstack(self.simg_raw, batch_size, axis=0) 212 | 213 | # raw value of DSSIM distance 214 | self.dist_raw = batch_gen_DSSIM(aimg_raw_split, simg_raw_split) 215 | # distance value after applying threshold 216 | self.dist = tf.maximum(self.dist_raw - self.l_threshold, 0.0) 217 | 218 | self.dist_raw_sum = tf.reduce_sum( 219 | tf.where(self.mask, 220 | self.dist_raw, 221 | tf.zeros_like(self.dist_raw))) 222 | self.dist_sum = tf.reduce_sum(tf.where(self.mask, 223 | self.dist, 224 | tf.zeros_like(self.dist))) 225 | 226 | ''' 227 | BOTTLESIM 228 | similarity between neuron values between new images and original images 229 | ''' 230 | 231 | self.bottleneck_a = bottleneck_model(self.aimg_input) 232 | self.bottleneck_a *= self.weights 233 | if self.MIMIC_IMG: 234 | self.bottleneck_t = bottleneck_model(self.timg_input) 235 | self.bottleneck_t *= self.weights 236 | else: 237 | self.bottleneck_t = self.weights * self.bottleneck_t_raw 238 | 239 | # L2 diff between two sets of non-normalized bottleneck neurons 240 | # L2 diff = sqrt(sum(square(X - Y))) 241 | # Careful about the dimensions when applying math operators 242 | bottleneck_diff = self.bottleneck_t - self.bottleneck_a 243 | self.bottlesim = tf.sqrt( 244 | tf.reduce_sum(tf.square(bottleneck_diff), 245 | axis=range(1, len(self.bottleneck_shape)))) 246 | 247 | self.bottlesim_sum = tf.reduce_sum( 248 | tf.where(self.mask, 249 | self.bottlesim, 250 | tf.zeros_like(self.bottlesim))) 251 | 252 | ''' 253 | Sum up two losses 254 | ''' 255 | 256 | # sum up the losses 257 | self.loss = self.const * tf.square(self.dist) + self.bottlesim 258 | 259 | self.loss_sum = tf.reduce_sum(tf.where(self.mask, 260 | self.loss, 261 | tf.zeros_like(self.loss))) 262 | 263 | ''' 264 | Setup phase 265 | ''' 266 | 267 | # Setup the Adadelta optimizer and keep track of variables 268 | # we're creating 269 | start_vars = set(x.name for x in tf.global_variables()) 270 | optimizer = tf.train.AdadeltaOptimizer(self.LEARNING_RATE) 271 | self.train = optimizer.minimize(self.loss_sum, 272 | var_list=[self.modifier]) 273 | end_vars = tf.global_variables() 274 | new_vars = [x for x in end_vars if x.name not in start_vars] 275 | 276 | # these are the variables to initialize when we run 277 | self.setup = [] 278 | self.setup.append(self.modifier.assign(self.assign_modifier)) 279 | if self.MIMIC_IMG: 280 | self.setup.append(self.timg_tanh.assign(self.assign_timg_tanh)) 281 | else: 282 | self.setup.append(self.bottleneck_t_raw.assign( 283 | self.assign_bottleneck_t_raw)) 284 | self.setup.append(self.simg_tanh.assign(self.assign_simg_tanh)) 285 | self.setup.append(self.const.assign(self.assign_const)) 286 | self.setup.append(self.mask.assign(self.assign_mask)) 287 | self.setup.append(self.weights.assign(self.assign_weights)) 288 | 289 | self.init = tf.variables_initializer( 290 | var_list=[self.modifier] + new_vars) 291 | 292 | print('Attacker loaded') 293 | 294 | def preprocess_arctanh(self, imgs): 295 | 296 | imgs = reverse_preprocess(imgs, self.intensity_range) 297 | imgs /= 255.0 298 | imgs -= 0.5 299 | imgs *= self.tanh_constant 300 | tanh_imgs = np.arctanh(imgs) 301 | 302 | return tanh_imgs 303 | 304 | def clipping(self, imgs): 305 | 306 | imgs = reverse_preprocess(imgs, self.intensity_range) 307 | imgs = np.clip(imgs, 0, self.max_val) 308 | imgs = np.rint(imgs) 309 | 310 | imgs = preprocess(imgs, self.intensity_range) 311 | 312 | return imgs 313 | 314 | def print_stat(self, source_imgs, best_adv): 315 | 316 | avg_rmsd, std_rmsd = cal_rmsd(source_imgs, best_adv) 317 | print('Avg RMSD: %.4f, STD RMSD: %.4f' % (avg_rmsd, std_rmsd)) 318 | 319 | return 320 | 321 | def attack(self, source_imgs, target_imgs, weights=None): 322 | 323 | # weights defines 324 | if weights is None: 325 | weights = np.ones([source_imgs.shape[0]] + 326 | list(self.bottleneck_shape[1:])) 327 | 328 | assert weights.shape[1:] == self.bottleneck_shape[1:] 329 | assert source_imgs.shape[1:] == self.input_shape[1:] 330 | assert source_imgs.shape[0] == weights.shape[0] 331 | if self.MIMIC_IMG: 332 | assert target_imgs.shape[1:] == self.input_shape[1:] 333 | assert source_imgs.shape[0] == target_imgs.shape[0] 334 | else: 335 | # target_imgs should be bottleneck values 336 | # we do not rename the variable here 337 | assert target_imgs.shape[1:] == self.bottleneck_shape[1:] 338 | assert source_imgs.shape[0] == target_imgs.shape[0] 339 | 340 | start_time = time.time() 341 | 342 | adv_imgs = [] 343 | print('%d batches in total' 344 | % int(np.ceil(len(source_imgs) / self.batch_size))) 345 | 346 | for idx in range(0, len(source_imgs), self.batch_size): 347 | print('processing batch %d at %s' % (idx, datetime.datetime.now())) 348 | adv_img = self.attack_batch(source_imgs[idx:idx + self.batch_size], 349 | target_imgs[idx:idx + self.batch_size], 350 | weights[idx:idx + self.batch_size]) 351 | adv_imgs.extend(adv_img) 352 | 353 | elapsed_time = time.time() - start_time 354 | print('attack cost %f s' % (elapsed_time)) 355 | 356 | return np.array(adv_imgs) 357 | 358 | def attack_batch(self, source_imgs, target_imgs, weights): 359 | 360 | """ 361 | Run the attack on a batch of images and labels. 362 | """ 363 | 364 | # if self.verbose == 1: 365 | # print('imgs shape %s' % str(imgs.shape)) 366 | # print('target_imgs shape %s' % str(target_imgs.shape)) 367 | # print('weights shape %s' % str(weights.shape)) 368 | 369 | nb_imgs = source_imgs.shape[0] 370 | mask = [True] * nb_imgs + [False] * (self.batch_size - nb_imgs) 371 | mask = np.array(mask, dtype=np.bool) 372 | 373 | source_imgs = np.array(source_imgs) 374 | target_imgs = np.array(target_imgs) 375 | 376 | # convert to tanh-space 377 | simg_tanh = self.preprocess_arctanh(source_imgs) 378 | if self.MIMIC_IMG: 379 | timg_tanh = self.preprocess_arctanh(target_imgs) 380 | else: 381 | timg_tanh = target_imgs 382 | 383 | CONST = np.ones(self.batch_size) * self.initial_const 384 | 385 | self.sess.run(self.init) 386 | simg_tanh_batch = np.zeros(self.input_shape) 387 | if self.MIMIC_IMG: 388 | timg_tanh_batch = np.zeros(self.input_shape) 389 | else: 390 | timg_tanh_batch = np.zeros(self.bottleneck_shape) 391 | weights_batch = np.zeros(self.bottleneck_shape) 392 | simg_tanh_batch[:nb_imgs] = simg_tanh[:nb_imgs] 393 | timg_tanh_batch[:nb_imgs] = timg_tanh[:nb_imgs] 394 | weights_batch[:nb_imgs] = weights[:nb_imgs] 395 | modifier_batch = np.ones(self.input_shape) * 1e-6 396 | 397 | # set the variables so that we don't have to send them over again 398 | if self.MIMIC_IMG: 399 | self.sess.run(self.setup, 400 | {self.assign_timg_tanh: timg_tanh_batch, 401 | self.assign_simg_tanh: simg_tanh_batch, 402 | self.assign_const: CONST, 403 | self.assign_mask: mask, 404 | self.assign_weights: weights_batch, 405 | self.assign_modifier: modifier_batch}) 406 | else: 407 | # if directly mimicking a vector, use assign_bottleneck_t_raw 408 | # in setup 409 | self.sess.run(self.setup, 410 | {self.assign_bottleneck_t_raw: timg_tanh_batch, 411 | self.assign_simg_tanh: simg_tanh_batch, 412 | self.assign_const: CONST, 413 | self.assign_mask: mask, 414 | self.assign_weights: weights_batch, 415 | self.assign_modifier: modifier_batch}) 416 | 417 | # if self.verbose == 1: 418 | # print('************************************************') 419 | # print('CONST: %s' % str(CONST)) 420 | 421 | # the best bottlesim and adv img 422 | best_bottlesim = [np.inf] * nb_imgs 423 | best_adv = np.zeros_like(source_imgs) 424 | 425 | if self.verbose == 1: 426 | loss_sum = float(self.sess.run(self.loss_sum)) 427 | dist_sum = float(self.sess.run(self.dist_sum)) 428 | thresh_over = (dist_sum / self.batch_size / self.l_threshold * 100) 429 | dist_raw_sum = float(self.sess.run(self.dist_raw_sum)) 430 | bottlesim_sum = self.sess.run(self.bottlesim_sum) 431 | print('START: Total loss: %.4E; perturb: %.6f (%.2f%% over, raw: %.6f); sim: %f' 432 | % (Decimal(loss_sum), 433 | dist_sum, 434 | thresh_over, 435 | dist_raw_sum, 436 | bottlesim_sum)) 437 | 438 | for iteration in xrange(self.MAX_ITERATIONS): 439 | 440 | self.sess.run([self.train]) 441 | 442 | dist_raw_list, bottlesim_list, aimg_input_list = self.sess.run( 443 | [self.dist_raw, 444 | self.bottlesim, 445 | self.aimg_input]) 446 | for e, (dist_raw, bottlesim, aimg_input) in enumerate( 447 | zip(dist_raw_list, bottlesim_list, aimg_input_list)): 448 | if e >= nb_imgs: 449 | break 450 | if (dist_raw < self.l_threshold and 451 | bottlesim < best_bottlesim[e]): 452 | best_bottlesim[e] = bottlesim 453 | best_adv[e] = aimg_input 454 | 455 | # print out the losses every 10% 456 | if iteration % (self.MAX_ITERATIONS // 10) == 0: 457 | if self.verbose == 1: 458 | loss_sum = float(self.sess.run(self.loss_sum)) 459 | dist_sum = float(self.sess.run(self.dist_sum)) 460 | thresh_over = (dist_sum / 461 | self.batch_size / 462 | self.l_threshold * 463 | 100) 464 | dist_raw_sum = float(self.sess.run(self.dist_raw_sum)) 465 | bottlesim_sum = self.sess.run(self.bottlesim_sum) 466 | print('ITER %4d: Total loss: %.4E; perturb: %.6f (%.2f%% over, raw: %.6f); sim: %f' 467 | % (iteration, 468 | Decimal(loss_sum), 469 | dist_sum, 470 | thresh_over, 471 | dist_raw_sum, 472 | bottlesim_sum)) 473 | 474 | if self.verbose == 1: 475 | loss_sum = float(self.sess.run(self.loss_sum)) 476 | dist_sum = float(self.sess.run(self.dist_sum)) 477 | thresh_over = (dist_sum / self.batch_size / self.l_threshold * 100) 478 | dist_raw_sum = float(self.sess.run(self.dist_raw_sum)) 479 | bottlesim_sum = float(self.sess.run(self.bottlesim_sum)) 480 | print('END: Total loss: %.4E; perturb: %.6f (%.2f%% over, raw: %.6f); sim: %f' 481 | % (Decimal(loss_sum), 482 | dist_sum, 483 | thresh_over, 484 | dist_raw_sum, 485 | bottlesim_sum)) 486 | 487 | # keep the final round result 488 | if self.keep_final: 489 | dist_raw_list, bottlesim_list, aimg_input_list = self.sess.run( 490 | [self.dist_raw, 491 | self.bottlesim, 492 | self.aimg_input]) 493 | for e, (dist_raw, bottlesim, aimg_input) in enumerate( 494 | zip(dist_raw_list, bottlesim_list, aimg_input_list)): 495 | if e >= nb_imgs: 496 | break 497 | if (dist_raw < self.l_threshold and 498 | bottlesim < best_bottlesim[e]): 499 | best_bottlesim[e] = bottlesim 500 | best_adv[e] = aimg_input 501 | 502 | if self.verbose == 1: 503 | self.print_stat(source_imgs[:nb_imgs], best_adv[:nb_imgs]) 504 | 505 | # return the best solution found 506 | best_adv = self.clipping(best_adv[:nb_imgs]) 507 | 508 | return best_adv 509 | --------------------------------------------------------------------------------