forked from 626_privacy/tensorflow_privacy
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"
|
"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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue