diff --git a/tensorflow_privacy/privacy/membership_inference_attack/codelabs/privacy_report_codelab.ipynb b/tensorflow_privacy/privacy/membership_inference_attack/codelabs/privacy_report_codelab.ipynb new file mode 100644 index 0000000..8eacc2e --- /dev/null +++ b/tensorflow_privacy/privacy/membership_inference_attack/codelabs/privacy_report_codelab.ipynb @@ -0,0 +1,401 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "1eiwVljWpzM7" + }, + "source": [ + "Copyright 2020 The TensorFlow Authors.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "cellView": "both", + "id": "4rmwPgXeptiS" + }, + "outputs": [], + "source": [ + "#@title Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# https://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "YM2gRaJMqvMi" + }, + "source": [ + "# Assess privacy risks with the TensorFlow Privacy Report" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "7oUAMMc6isck" + }, + "source": [ + "\u003ctable class=\"tfo-notebook-buttons\" align=\"left\"\u003e\n", + " \u003ctd\u003e\n", + " \u003ca target=\"_blank\" href=\"https://colab.research.google.com/github/tensorflow/privacy/blob/master/tensorflow_privacy/privacy/membership_inference_attack/codelabs/privacy_report_codelab.ipynb\"\u003e\u003cimg src=\"https://www.tensorflow.org/images/colab_logo_32px.png\" /\u003eRun in Google Colab\u003c/a\u003e\n", + " \u003c/td\u003e\n", + " \u003ctd\u003e\n", + " \u003ca target=\"_blank\" href=\"https://github.com/tensorflow/privacy/blob/master/tensorflow_privacy/privacy/membership_inference_attack/codelabs/privacy_report_codelab.ipynb\"\u003e\u003cimg src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\" /\u003eView source on GitHub\u003c/a\u003e\n", + " \u003c/td\u003e\n", + "\u003c/table\u003e" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "9rMuytY7Nn8P" + }, + "source": [ + "##Overview\n", + "In this codelab we'll train a simple image classification model on the CIFAR10 dataset, and then use the \"membership inference attack\" against this model to assess if the attacker is able to \"guess\" whether a particular sample was present in the training set. We will use the TF Privacy Report to visualize results from multiple models and model checkpoints." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "FUWqArj_q8vs" + }, + "source": [ + "## Setup\n", + "First, set this notebook's runtime to use a GPU, under Runtime \u003e Change runtime type \u003e Hardware accelerator. Then, begin importing the necessary libraries." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "cellView": "form", + "id": "Lr1pwHcbralz" + }, + "outputs": [], + "source": [ + "#@title Import statements.\n", + "import numpy as np\n", + "from typing import Tuple, Text\n", + "from scipy import special\n", + "from sklearn import metrics\n", + "\n", + "import tensorflow as tf\n", + "import tensorflow_datasets as tfds\n", + "\n", + "# Set verbosity.\n", + "tf.compat.v1.logging.set_verbosity(tf.compat.v1.logging.ERROR)\n", + "from warnings import simplefilter\n", + "from sklearn.exceptions import ConvergenceWarning\n", + "simplefilter(action=\"ignore\", category=ConvergenceWarning)\n", + "simplefilter(action=\"ignore\", category=FutureWarning)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "ucw81ar6ru-6" + }, + "source": [ + "### Install TensorFlow Privacy." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "cellView": "both", + "id": "zcqAmiGH90kl" + }, + "outputs": [], + "source": [ + "!pip3 install git+https://github.com/tensorflow/privacy\n", + "\n", + "from tensorflow_privacy.privacy.membership_inference_attack import membership_inference_attack as mia\n", + "from tensorflow_privacy.privacy.membership_inference_attack.data_structures import AttackInputData\n", + "from tensorflow_privacy.privacy.membership_inference_attack.data_structures import AttackResultsCollection\n", + "from tensorflow_privacy.privacy.membership_inference_attack.data_structures import AttackType\n", + "from tensorflow_privacy.privacy.membership_inference_attack.data_structures import PrivacyMetric\n", + "from tensorflow_privacy.privacy.membership_inference_attack.data_structures import PrivacyReportMetadata\n", + "from tensorflow_privacy.privacy.membership_inference_attack.data_structures import SlicingSpec\n", + "from tensorflow_privacy.privacy.membership_inference_attack import privacy_report" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "pBbcG86th_sW" + }, + "source": [ + "## Train a model" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "cellView": "both", + "id": "vCyOWyyhXLib" + }, + "outputs": [], + "source": [ + "#@markdown Train a simple model on CIFAR10 with Keras.\n", + "\n", + "dataset = 'cifar10'\n", + "num_classes = 10\n", + "activation = 'relu'\n", + "lr = 0.02\n", + "momentum = 0.9\n", + "batch_size = 250\n", + "epochs = 50 # Privacy risks are especially visible with lots of epochs.\n", + "\n", + "\n", + "def small_cnn(input_shape: Tuple[int],\n", + " num_classes: int,\n", + " num_conv: int,\n", + " activation: Text = 'relu') -\u003e tf.keras.models.Sequential:\n", + " \"\"\"Setup a small CNN for image classification.\n", + "\n", + " Args:\n", + " input_shape: Integer tuple for the shape of the images.\n", + " num_classes: Number of prediction classes.\n", + " num_conv: Number of convolutional layers.\n", + " activation: The activation function to use for conv and dense layers.\n", + "\n", + " Returns:\n", + " The Keras model.\n", + " \"\"\"\n", + " model = tf.keras.models.Sequential()\n", + " model.add(tf.keras.layers.Input(shape=input_shape))\n", + "\n", + " # Conv layers\n", + " for _ in range(num_conv):\n", + " model.add(tf.keras.layers.Conv2D(32, (3, 3), activation=activation))\n", + " model.add(tf.keras.layers.MaxPooling2D())\n", + "\n", + " model.add(tf.keras.layers.Flatten())\n", + " model.add(tf.keras.layers.Dense(64, activation=activation))\n", + " model.add(tf.keras.layers.Dense(num_classes))\n", + " return model\n", + "\n", + "\n", + "print('Loading the dataset.')\n", + "train_ds = tfds.as_numpy(\n", + " tfds.load(dataset, split=tfds.Split.TRAIN, batch_size=-1))\n", + "test_ds = tfds.as_numpy(\n", + " tfds.load(dataset, split=tfds.Split.TEST, batch_size=-1))\n", + "x_train = train_ds['image'].astype('float32') / 255.\n", + "y_train_indices = train_ds['label'][:, np.newaxis]\n", + "x_test = test_ds['image'].astype('float32') / 255.\n", + "y_test_indices = test_ds['label'][:, np.newaxis]\n", + "\n", + "# Convert class vectors to binary class matrices.\n", + "y_train = tf.keras.utils.to_categorical(y_train_indices, num_classes)\n", + "y_test = tf.keras.utils.to_categorical(y_test_indices, num_classes)\n", + "\n", + "input_shape = x_train.shape[1:]\n", + "\n", + "three_layer_model = small_cnn(\n", + " input_shape, num_classes, num_conv=3, activation=activation)\n", + "optimizer = tf.keras.optimizers.SGD(lr=lr, momentum=momentum)\n", + "loss = tf.keras.losses.CategoricalCrossentropy(from_logits=True)\n", + "three_layer_model.compile(loss=loss, optimizer=optimizer, metrics=['accuracy'])\n", + "\n", + "two_layer_model = small_cnn(\n", + " input_shape, num_classes, num_conv=2, activation=activation)\n", + "two_layer_model.compile(loss=loss, optimizer=optimizer, metrics=['accuracy'])\n", + "\n", + "epoch_results = AttackResultsCollection([])\n", + "num_epochs = 5\n", + "models = {\n", + " 'two layer model': two_layer_model,\n", + " 'three layer model': three_layer_model,\n", + "}\n", + "for model_name, model in models.items():\n", + " # Incrementally train the model and store privacy metrics every num_epochs.\n", + " for i in range(10):\n", + " model.fit(\n", + " x_train,\n", + " y_train,\n", + " batch_size=batch_size,\n", + " epochs=num_epochs,\n", + " validation_data=(x_test, y_test),\n", + " shuffle=True)\n", + "\n", + " logits_train = model.predict(x_train, batch_size=batch_size)\n", + " logits_test = model.predict(x_test, batch_size=batch_size)\n", + "\n", + " prob_train = special.softmax(logits_train, axis=1)\n", + " prob_test = special.softmax(logits_test, axis=1)\n", + "\n", + " # Add metadata to generate a privacy report.\n", + " privacy_report_metadata = PrivacyReportMetadata(\n", + " accuracy_train=metrics.accuracy_score(y_train_indices,\n", + " np.argmax(prob_train, axis=1)),\n", + " accuracy_test=metrics.accuracy_score(y_test_indices,\n", + " np.argmax(prob_test, axis=1)),\n", + " epoch_num=num_epochs * i,\n", + " model_variant_label=model_name)\n", + "\n", + " attack_results = mia.run_attacks(\n", + " AttackInputData(\n", + " labels_train=np.asarray([x[0] for x in y_train_indices]),\n", + " labels_test=np.asarray([x[0] for x in y_test_indices]),\n", + " probs_train=prob_train,\n", + " probs_test=prob_test),\n", + " SlicingSpec(entire_dataset=True, by_class=True),\n", + " attack_types=(AttackType.THRESHOLD_ATTACK,\n", + " AttackType.LOGISTIC_REGRESSION),\n", + " privacy_report_metadata=privacy_report_metadata)\n", + " epoch_results.append(attack_results)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "0snqR0Gbv3qk" + }, + "source": [ + "## Load attack results\n", + "We can load attack results from the model above or replace the filepath with our own results." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "UTZwiCAJt0R6" + }, + "outputs": [], + "source": [ + "loaded_results = epoch_results\n", + "# Or load your own via\n", + "#loaded_results = AttackResultsCollection.load(filepath)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "6mBEYh4utxiR" + }, + "source": [ + "## Epoch Plots\n", + "\n", + "We can visualize how privacy risks happen as we train models. By probing the model periodically (e.g. every 10 epochs), we can pick the point in time with the best performance / privacy trade-off.\n", + "\n", + "We use the TF Privacy Membership Inference Attack module to generate AttackResults. These AttackResults get combined into an AttackResultsCollection. The TF Privacy Report is designed to analyze the provided AttackResultsCollection." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "cellView": "both", + "id": "o7T8n0ffv3qo" + }, + "outputs": [], + "source": [ + "privacy_metrics = (PrivacyMetric.AUC, PrivacyMetric.ATTACKER_ADVANTAGE)\n", + "epoch_plot = privacy_report.plot_by_epochs(\n", + " loaded_results, privacy_metrics=privacy_metrics)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "ijjwGgyixsFg" + }, + "source": [ + "We see that as a rule, privacy vulnerability tends to increase as the number of epochs goes up. This is true across model variants as well as different attacker types.\n", + "\n", + "Two layer models (with fewer convolutional layers) are generally more vulnerable than their three layer model counterparts.\n", + "\n", + "Now let's see how model performance changes with respect to privacy risk." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "GbtlV-2Xu8s-" + }, + "source": [ + "## Privacy vs Utility" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "Lt6fXGoivLH1" + }, + "outputs": [], + "source": [ + "privacy_metrics = (PrivacyMetric.AUC, PrivacyMetric.ATTACKER_ADVANTAGE)\n", + "utility_privacy_plot = privacy_report.plot_privacy_vs_accuracy(\n", + " loaded_results, privacy_metrics=privacy_metrics)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "m_6vg3pBPoyy" + }, + "source": [ + "Three layer models (perhaps due to too many parameters) only achieve a train accuracy of 0.85. The two layer models achieve roughly equal performance for that level of privacy risk but they continue to get better accuracy.\n", + "\n", + "We can also see how the line for two layer models gets steeper. This means that additional marginal gains in train accuracy come at an expense of vast privacy vulnerabilities." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "7u3BAg87v3qv" + }, + "source": [ + "This is the end of the colab!\n", + "Feel free to analyze your own results." + ] + } + ], + "metadata": { + "colab": { + "collapsed_sections": [], + "last_runtime": { + "build_target": "//learning/deepmind/public/tools/ml_python:ml_notebook", + "kind": "private" + }, + "name": "TF Privacy Report codelab", + "provenance": [] + }, + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.10" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} diff --git a/tensorflow_privacy/privacy/membership_inference_attack/privacy_report.py b/tensorflow_privacy/privacy/membership_inference_attack/privacy_report.py index c81f714..e05774a 100644 --- a/tensorflow_privacy/privacy/membership_inference_attack/privacy_report.py +++ b/tensorflow_privacy/privacy/membership_inference_attack/privacy_report.py @@ -109,7 +109,7 @@ def _generate_subplots(all_results_df: pd.DataFrame, x_axis_metric: str, privacy_metrics: Iterable[PrivacyMetric]): """Create one subplot per privacy metric for a specified x_axis_metric.""" fig, axes = plt.subplots( - 1, len(privacy_metrics), figsize=(5 * len(privacy_metrics), 5)) + 1, len(privacy_metrics), figsize=(5 * len(privacy_metrics) + 3, 5)) # Set a title for the entire group of subplots. fig.suptitle(figure_title) if len(privacy_metrics) == 1: @@ -122,9 +122,10 @@ def _generate_subplots(all_results_df: pd.DataFrame, x_axis_metric: str, sorted_label_results = single_label_results.sort_values(x_axis_metric) axes[i].plot(sorted_label_results[x_axis_metric], sorted_label_results[str(privacy_metric)]) - axes[i].legend(legend_labels, loc='lower right') axes[i].set_xlabel(x_axis_metric) axes[i].set_title('%s for %s' % (privacy_metric, ENTIRE_DATASET_SLICE_STR)) + plt.legend(legend_labels, loc='upper left', bbox_to_anchor=(1.02, 1)) + fig.tight_layout(rect=[0, 0, 1, 0.93]) # Leave space for suptitle. return fig