Merge pull request #119 from lwsong:master

PiperOrigin-RevId: 330658958
This commit is contained in:
A. Unique TensorFlower 2020-09-08 22:44:06 -07:00
commit 6312a853d8
3 changed files with 492 additions and 381 deletions

View file

@ -51,14 +51,14 @@
"id": "-B5ZvlSqqLaR" "id": "-B5ZvlSqqLaR"
}, },
"source": [ "source": [
"\u003ctable class=\"tfo-notebook-buttons\" align=\"left\"\u003e\n", "<table class=\"tfo-notebook-buttons\" align=\"left\">\n",
" \u003ctd\u003e\n", " <td>\n",
" \u003ca target=\"_blank\" href=\"https://colab.research.google.com/github/tensorflow/privacy/blob/master/tensorflow_privacy/privacy/membership_inference_attack/codelab.ipynb\"\u003e\u003cimg src=\"https://www.tensorflow.org/images/colab_logo_32px.png\" /\u003eRun in Google Colab\u003c/a\u003e\n", " <a target=\"_blank\" href=\"https://colab.research.google.com/github/tensorflow/privacy/blob/master/tensorflow_privacy/privacy/membership_inference_attack/codelab.ipynb\"><img src=\"https://www.tensorflow.org/images/colab_logo_32px.png\" />Run in Google Colab</a>\n",
" \u003c/td\u003e\n", " </td>\n",
" \u003ctd\u003e\n", " <td>\n",
" \u003ca target=\"_blank\" href=\"https://github.com/tensorflow/privacy/blob/master/tensorflow_privacy/privacy/membership_inference_attack/codelab.ipynb\"\u003e\u003cimg src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\" /\u003eView source on GitHub\u003c/a\u003e\n", " <a target=\"_blank\" href=\"https://github.com/tensorflow/privacy/blob/master/tensorflow_privacy/privacy/membership_inference_attack/codelab.ipynb\"><img src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\" />View source on GitHub</a>\n",
" \u003c/td\u003e\n", " </td>\n",
"\u003c/table\u003e" "</table>"
] ]
}, },
{ {
@ -80,7 +80,7 @@
}, },
"source": [ "source": [
"## Setup\n", "## 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." "First, set this notebook's runtime to use a GPU, under Runtime > Change runtime type > Hardware accelerator. Then, begin importing the necessary libraries."
] ]
}, },
{ {
@ -173,7 +173,7 @@
"def small_cnn(input_shape: Tuple[int],\n", "def small_cnn(input_shape: Tuple[int],\n",
" num_classes: int,\n", " num_classes: int,\n",
" num_conv: int,\n", " num_conv: int,\n",
" activation: Text = 'relu') -\u003e tf.keras.models.Sequential:\n", " activation: Text = 'relu') -> tf.keras.models.Sequential:\n",
" \"\"\"Setup a small CNN for image classification.\n", " \"\"\"Setup a small CNN for image classification.\n",
"\n", "\n",
" Args:\n", " Args:\n",
@ -265,8 +265,8 @@
"logits_test = model.predict(x_test, batch_size=batch_size)\n", "logits_test = model.predict(x_test, batch_size=batch_size)\n",
"\n", "\n",
"print('Apply softmax to get probabilities from logits...')\n", "print('Apply softmax to get probabilities from logits...')\n",
"prob_train = special.softmax(logits_train)\n", "prob_train = special.softmax(logits_train, axis=1)\n",
"prob_test = special.softmax(logits_test)\n", "prob_test = special.softmax(logits_test, axis=1)\n",
"\n", "\n",
"print('Compute losses...')\n", "print('Compute losses...')\n",
"cce = tf.keras.backend.categorical_crossentropy\n", "cce = tf.keras.backend.categorical_crossentropy\n",
@ -365,8 +365,21 @@
}, },
"kernelspec": { "kernelspec": {
"display_name": "Python 3", "display_name": "Python 3",
"language": "python",
"name": "python3" "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"
},
"pycharm": { "pycharm": {
"stem_cell": { "stem_cell": {
"cell_type": "raw", "cell_type": "raw",
@ -378,5 +391,5 @@
} }
}, },
"nbformat": 4, "nbformat": 4,
"nbformat_minor": 0 "nbformat_minor": 1
} }

View file

@ -21,6 +21,7 @@ from typing import Any, Iterable, Union
from dataclasses import dataclass from dataclasses import dataclass
import numpy as np import numpy as np
import pandas as pd import pandas as pd
from scipy import special
from sklearn import metrics from sklearn import metrics
import tensorflow_privacy.privacy.membership_inference_attack.utils as utils import tensorflow_privacy.privacy.membership_inference_attack.utils as utils
@ -145,6 +146,11 @@ def _is_np_array(arr, arr_name):
raise ValueError('%s should be a numpy array.' % arr_name) raise ValueError('%s should be a numpy array.' % arr_name)
def _log_value(probs, small_value=1e-30):
"""Compute the log value on the probability. Clip probabilities close to 0."""
return -np.log(np.maximum(probs, small_value))
@dataclass @dataclass
class AttackInputData: class AttackInputData:
"""Input data for running an attack. """Input data for running an attack.
@ -165,6 +171,12 @@ class AttackInputData:
loss_train: np.ndarray = None loss_train: np.ndarray = None
loss_test: np.ndarray = None loss_test: np.ndarray = None
# Explicitly specified prediction entropy. If provided, this is used instead
# of deriving entropy from logits and labels
# (https://arxiv.org/pdf/2003.10595.pdf by Song and Mittal).
entropy_train: np.ndarray = None
entropy_test: np.ndarray = None
@property @property
def num_classes(self): def num_classes(self):
if self.labels_train is None or self.labels_test is None: if self.labels_train is None or self.labels_test is None:
@ -173,6 +185,36 @@ class AttackInputData:
'Please set labels_train and labels_test') 'Please set labels_train and labels_test')
return int(max(np.max(self.labels_train), np.max(self.labels_test))) + 1 return int(max(np.max(self.labels_train), np.max(self.labels_test))) + 1
@staticmethod
def _get_entropy(logits: np.ndarray, true_labels: np.ndarray):
"""Computes the prediction entropy (by Song and Mittal)."""
if (np.absolute(np.sum(logits, axis=1) - 1) <= 1e-3).all():
probs = logits
else:
# Using softmax to compute probability from logits.
probs = special.softmax(logits, axis=1)
if true_labels is None:
# When not given ground truth label, we compute the
# normal prediction entropy.
# See the Equation (7) in https://arxiv.org/pdf/2003.10595.pdf
return np.sum(np.multiply(probs, _log_value(probs)), axis=1)
else:
# When given the groud truth label, we compute the
# modified prediction entropy.
# See the Equation (8) in https://arxiv.org/pdf/2003.10595.pdf
log_probs = _log_value(probs)
reverse_probs = 1 - probs
log_reverse_probs = _log_value(reverse_probs)
modified_probs = np.copy(probs)
modified_probs[range(true_labels.size),
true_labels] = reverse_probs[range(true_labels.size),
true_labels]
modified_log_probs = np.copy(log_reverse_probs)
modified_log_probs[range(true_labels.size),
true_labels] = log_probs[range(true_labels.size),
true_labels]
return np.sum(np.multiply(modified_probs, modified_log_probs), axis=1)
def get_loss_train(self): def get_loss_train(self):
"""Calculates (if needed) cross-entropy losses for the training set.""" """Calculates (if needed) cross-entropy losses for the training set."""
if self.loss_train is None: if self.loss_train is None:
@ -187,6 +229,18 @@ class AttackInputData:
self.logits_test) self.logits_test)
return self.loss_test return self.loss_test
def get_entropy_train(self):
"""Calculates prediction entropy for the training set."""
if self.entropy_train is not None:
return self.entropy_train
return self._get_entropy(self.logits_train, self.labels_train)
def get_entropy_test(self):
"""Calculates prediction entropy for the test set."""
if self.entropy_test is not None:
return self.entropy_test
return self._get_entropy(self.logits_test, self.labels_test)
def get_train_size(self): def get_train_size(self):
"""Returns size of the training set.""" """Returns size of the training set."""
if self.loss_train is not None: if self.loss_train is not None:
@ -205,6 +259,10 @@ class AttackInputData:
raise ValueError( raise ValueError(
'loss_test and loss_train should both be either set or unset') 'loss_test and loss_train should both be either set or unset')
if (self.entropy_train is None) != (self.entropy_test is None):
raise ValueError(
'entropy_test and entropy_train should both be either set or unset')
if (self.logits_train is None) != (self.logits_test is None): if (self.logits_train is None) != (self.logits_test is None):
raise ValueError( raise ValueError(
'logits_train and logits_test should both be either set or unset') 'logits_train and logits_test should both be either set or unset')
@ -214,8 +272,9 @@ class AttackInputData:
'labels_train and labels_test should both be either set or unset') 'labels_train and labels_test should both be either set or unset')
if (self.labels_train is None and self.loss_train is None and if (self.labels_train is None and self.loss_train is None and
self.logits_train is None): self.logits_train is None and self.entropy_train is None):
raise ValueError('At least one of labels, logits or losses should be set') raise ValueError(
'At least one of labels, logits, losses or entropy should be set')
if self.labels_train is not None and not _is_integer_type_array( if self.labels_train is not None and not _is_integer_type_array(
self.labels_train): self.labels_train):
@ -231,11 +290,15 @@ class AttackInputData:
_is_np_array(self.labels_test, 'labels_test') _is_np_array(self.labels_test, 'labels_test')
_is_np_array(self.loss_train, 'loss_train') _is_np_array(self.loss_train, 'loss_train')
_is_np_array(self.loss_test, 'loss_test') _is_np_array(self.loss_test, 'loss_test')
_is_np_array(self.entropy_train, 'entropy_train')
_is_np_array(self.entropy_test, 'entropy_test')
_is_last_dim_equal(self.logits_train, 'logits_train', self.logits_test, _is_last_dim_equal(self.logits_train, 'logits_train', self.logits_test,
'logits_test') 'logits_test')
_is_array_one_dimensional(self.loss_train, 'loss_train') _is_array_one_dimensional(self.loss_train, 'loss_train')
_is_array_one_dimensional(self.loss_test, 'loss_test') _is_array_one_dimensional(self.loss_test, 'loss_test')
_is_array_one_dimensional(self.entropy_train, 'entropy_train')
_is_array_one_dimensional(self.entropy_test, 'entropy_test')
_is_array_one_dimensional(self.labels_train, 'labels_train') _is_array_one_dimensional(self.labels_train, 'labels_train')
_is_array_one_dimensional(self.labels_test, 'labels_test') _is_array_one_dimensional(self.labels_test, 'labels_test')
@ -244,6 +307,8 @@ class AttackInputData:
result = ['AttackInputData('] result = ['AttackInputData(']
_append_array_shape(self.loss_train, 'loss_train', result) _append_array_shape(self.loss_train, 'loss_train', result)
_append_array_shape(self.loss_test, 'loss_test', result) _append_array_shape(self.loss_test, 'loss_test', result)
_append_array_shape(self.entropy_train, 'entropy_train', result)
_append_array_shape(self.entropy_test, 'entropy_test', result)
_append_array_shape(self.logits_train, 'logits_train', result) _append_array_shape(self.logits_train, 'logits_train', result)
_append_array_shape(self.logits_test, 'logits_test', result) _append_array_shape(self.logits_test, 'logits_test', result)
_append_array_shape(self.labels_train, 'labels_train', result) _append_array_shape(self.labels_train, 'labels_train', result)

View file

@ -20,6 +20,7 @@ from absl.testing import absltest
from absl.testing import parameterized from absl.testing import parameterized
import numpy as np import numpy as np
import pandas as pd import pandas as pd
from tensorflow_privacy.privacy.membership_inference_attack.data_structures import _log_value
from tensorflow_privacy.privacy.membership_inference_attack.data_structures import AttackInputData from tensorflow_privacy.privacy.membership_inference_attack.data_structures import AttackInputData
from tensorflow_privacy.privacy.membership_inference_attack.data_structures import AttackResults from tensorflow_privacy.privacy.membership_inference_attack.data_structures import AttackResults
from tensorflow_privacy.privacy.membership_inference_attack.data_structures import AttackType from tensorflow_privacy.privacy.membership_inference_attack.data_structures import AttackType
@ -67,6 +68,34 @@ class AttackInputDataTest(absltest.TestCase):
np.testing.assert_equal(attack_input.get_loss_test().tolist(), np.testing.assert_equal(attack_input.get_loss_test().tolist(),
[1.0, 4.0, 6.0]) [1.0, 4.0, 6.0])
def test_get_entropy(self):
attack_input = AttackInputData(
logits_train=np.array([[1.0, 0.0, 0.0], [0.0, 0.0, 1.0]]),
logits_test=np.array([[0.0, 1.0, 0.0], [0.0, 0.0, 1.0]]),
labels_train=np.array([0, 2]),
labels_test=np.array([0, 2]))
np.testing.assert_equal(attack_input.get_entropy_train().tolist(), [0, 0])
np.testing.assert_equal(attack_input.get_entropy_test().tolist(),
[2 * _log_value(0), 0])
attack_input = AttackInputData(
logits_train=np.array([[1.0, 0.0, 0.0], [0.0, 0.0, 1.0]]),
logits_test=np.array([[0.0, 1.0, 0.0], [0.0, 0.0, 1.0]]))
np.testing.assert_equal(attack_input.get_entropy_train().tolist(), [0, 0])
np.testing.assert_equal(attack_input.get_entropy_test().tolist(), [0, 0])
def test_get_entropy_explicitly_provided(self):
attack_input = AttackInputData(
entropy_train=np.array([0.0, 2.0, 1.0]),
entropy_test=np.array([0.5, 3.0, 5.0]))
np.testing.assert_equal(attack_input.get_entropy_train().tolist(),
[0.0, 2.0, 1.0])
np.testing.assert_equal(attack_input.get_entropy_test().tolist(),
[0.5, 3.0, 5.0])
def test_validator(self): def test_validator(self):
self.assertRaises(ValueError, self.assertRaises(ValueError,
AttackInputData(logits_train=np.array([])).validate) AttackInputData(logits_train=np.array([])).validate)
@ -74,12 +103,16 @@ class AttackInputDataTest(absltest.TestCase):
AttackInputData(labels_train=np.array([])).validate) AttackInputData(labels_train=np.array([])).validate)
self.assertRaises(ValueError, self.assertRaises(ValueError,
AttackInputData(loss_train=np.array([])).validate) AttackInputData(loss_train=np.array([])).validate)
self.assertRaises(ValueError,
AttackInputData(entropy_train=np.array([])).validate)
self.assertRaises(ValueError, self.assertRaises(ValueError,
AttackInputData(logits_test=np.array([])).validate) AttackInputData(logits_test=np.array([])).validate)
self.assertRaises(ValueError, self.assertRaises(ValueError,
AttackInputData(labels_test=np.array([])).validate) AttackInputData(labels_test=np.array([])).validate)
self.assertRaises(ValueError, self.assertRaises(ValueError,
AttackInputData(loss_test=np.array([])).validate) AttackInputData(loss_test=np.array([])).validate)
self.assertRaises(ValueError,
AttackInputData(entropy_test=np.array([])).validate)
self.assertRaises(ValueError, AttackInputData().validate) self.assertRaises(ValueError, AttackInputData().validate)