Merge pull request #119 from lwsong:master
PiperOrigin-RevId: 330658958
This commit is contained in:
commit
6312a853d8
3 changed files with 492 additions and 381 deletions
|
@ -51,14 +51,14 @@
|
|||
"id": "-B5ZvlSqqLaR"
|
||||
},
|
||||
"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/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/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"
|
||||
"<table class=\"tfo-notebook-buttons\" align=\"left\">\n",
|
||||
" <td>\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",
|
||||
" </td>\n",
|
||||
" <td>\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",
|
||||
" </td>\n",
|
||||
"</table>"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -80,7 +80,7 @@
|
|||
},
|
||||
"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."
|
||||
"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",
|
||||
" num_classes: 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",
|
||||
"\n",
|
||||
" Args:\n",
|
||||
|
@ -265,8 +265,8 @@
|
|||
"logits_test = model.predict(x_test, batch_size=batch_size)\n",
|
||||
"\n",
|
||||
"print('Apply softmax to get probabilities from logits...')\n",
|
||||
"prob_train = special.softmax(logits_train)\n",
|
||||
"prob_test = special.softmax(logits_test)\n",
|
||||
"prob_train = special.softmax(logits_train, axis=1)\n",
|
||||
"prob_test = special.softmax(logits_test, axis=1)\n",
|
||||
"\n",
|
||||
"print('Compute losses...')\n",
|
||||
"cce = tf.keras.backend.categorical_crossentropy\n",
|
||||
|
@ -365,8 +365,21 @@
|
|||
},
|
||||
"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"
|
||||
},
|
||||
"pycharm": {
|
||||
"stem_cell": {
|
||||
"cell_type": "raw",
|
||||
|
@ -378,5 +391,5 @@
|
|||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 0
|
||||
"nbformat_minor": 1
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ from typing import Any, Iterable, Union
|
|||
from dataclasses import dataclass
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
from scipy import special
|
||||
from sklearn import metrics
|
||||
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)
|
||||
|
||||
|
||||
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
|
||||
class AttackInputData:
|
||||
"""Input data for running an attack.
|
||||
|
@ -165,6 +171,12 @@ class AttackInputData:
|
|||
loss_train: 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
|
||||
def num_classes(self):
|
||||
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')
|
||||
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):
|
||||
"""Calculates (if needed) cross-entropy losses for the training set."""
|
||||
if self.loss_train is None:
|
||||
|
@ -187,6 +229,18 @@ class AttackInputData:
|
|||
self.logits_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):
|
||||
"""Returns size of the training set."""
|
||||
if self.loss_train is not None:
|
||||
|
@ -205,6 +259,10 @@ class AttackInputData:
|
|||
raise ValueError(
|
||||
'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):
|
||||
raise ValueError(
|
||||
'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')
|
||||
|
||||
if (self.labels_train is None and self.loss_train is None and
|
||||
self.logits_train is None):
|
||||
raise ValueError('At least one of labels, logits or losses should be set')
|
||||
self.logits_train is None and self.entropy_train is None):
|
||||
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(
|
||||
self.labels_train):
|
||||
|
@ -231,11 +290,15 @@ class AttackInputData:
|
|||
_is_np_array(self.labels_test, 'labels_test')
|
||||
_is_np_array(self.loss_train, 'loss_train')
|
||||
_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,
|
||||
'logits_test')
|
||||
_is_array_one_dimensional(self.loss_train, 'loss_train')
|
||||
_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_test, 'labels_test')
|
||||
|
||||
|
@ -244,6 +307,8 @@ class AttackInputData:
|
|||
result = ['AttackInputData(']
|
||||
_append_array_shape(self.loss_train, 'loss_train', 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_test, 'logits_test', result)
|
||||
_append_array_shape(self.labels_train, 'labels_train', result)
|
||||
|
|
|
@ -20,6 +20,7 @@ from absl.testing import absltest
|
|||
from absl.testing import parameterized
|
||||
import numpy as np
|
||||
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 AttackResults
|
||||
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(),
|
||||
[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):
|
||||
self.assertRaises(ValueError,
|
||||
AttackInputData(logits_train=np.array([])).validate)
|
||||
|
@ -74,12 +103,16 @@ class AttackInputDataTest(absltest.TestCase):
|
|||
AttackInputData(labels_train=np.array([])).validate)
|
||||
self.assertRaises(ValueError,
|
||||
AttackInputData(loss_train=np.array([])).validate)
|
||||
self.assertRaises(ValueError,
|
||||
AttackInputData(entropy_train=np.array([])).validate)
|
||||
self.assertRaises(ValueError,
|
||||
AttackInputData(logits_test=np.array([])).validate)
|
||||
self.assertRaises(ValueError,
|
||||
AttackInputData(labels_test=np.array([])).validate)
|
||||
self.assertRaises(ValueError,
|
||||
AttackInputData(loss_test=np.array([])).validate)
|
||||
self.assertRaises(ValueError,
|
||||
AttackInputData(entropy_test=np.array([])).validate)
|
||||
self.assertRaises(ValueError, AttackInputData().validate)
|
||||
|
||||
|
||||
|
|
Loading…
Reference in a new issue