Calls epsilon computation in MIA.

PiperOrigin-RevId: 551003589
This commit is contained in:
Shuang Song 2023-07-25 14:49:21 -07:00 committed by A. Unique TensorFlower
parent 8e60864559
commit 225355258c
6 changed files with 568 additions and 46 deletions

View file

@ -21,7 +21,10 @@ py_test(
srcs = ["membership_inference_attack_test.py"],
python_version = "PY3",
srcs_version = "PY3",
deps = [":membership_inference_attack"],
deps = [
":membership_inference_attack",
"//tensorflow_privacy/privacy/privacy_tests:epsilon_lower_bound",
],
)
py_test(
@ -32,6 +35,7 @@ py_test(
srcs_version = "PY3",
deps = [
":membership_inference_attack",
"//tensorflow_privacy/privacy/privacy_tests:epsilon_lower_bound",
"//tensorflow_privacy/privacy/privacy_tests:utils",
],
)
@ -62,6 +66,7 @@ py_test(
deps = [
":membership_inference_attack",
":privacy_report",
"//tensorflow_privacy/privacy/privacy_tests:epsilon_lower_bound",
],
)
@ -83,7 +88,10 @@ py_library(
"seq2seq_mia.py",
],
srcs_version = "PY3",
deps = ["//tensorflow_privacy/privacy/privacy_tests:utils"],
deps = [
"//tensorflow_privacy/privacy/privacy_tests:epsilon_lower_bound",
"//tensorflow_privacy/privacy/privacy_tests:utils",
],
)
py_library(

View file

@ -20,12 +20,13 @@ import glob
import logging
import os
import pickle
from typing import Any, Dict, Iterable, MutableSequence, Optional, Sequence, Union
from typing import Any, Dict, Iterable, Mapping, MutableSequence, Optional, Sequence, Union
import numpy as np
import pandas as pd
from scipy import special
from sklearn import metrics
from tensorflow_privacy.privacy.privacy_tests import epsilon_lower_bound as elb
from tensorflow_privacy.privacy.privacy_tests import utils
# The minimum TPR or FPR below which they are considered equal.
@ -33,6 +34,11 @@ _ABSOLUTE_TOLERANCE = 1e-3
ENTIRE_DATASET_SLICE_STR = 'Entire dataset'
# Methods for estimation epsilon lower bounds
EPSILON_METHODS = (elb.BoundMethod.BAILEY,)
EPSILON_ALPHA = 0.05 # Level of significance for estimating epsilon lower bound
EPSILON_K = 5 # Will return top-k values for each epsilon lower bound estimate
class SlicingFeature(enum.Enum):
"""Enum with features by which slicing is available."""
@ -189,6 +195,7 @@ class PrivacyMetric(enum.Enum):
AUC = 'AUC'
ATTACKER_ADVANTAGE = 'Attacker advantage'
PPV = 'Positive predictive value'
EPSILON_LOWER_BOUND = 'Epsilon lower bound'
def __str__(self):
"""Returns 'AUC' instead of PrivacyMetric.AUC."""
@ -738,6 +745,27 @@ class RocCurve:
])
@dataclasses.dataclass
class EpsilonLowerBoundValue:
"""Epsilon lower bounds of a membership inference classifier."""
# Bounds from different methods
bounds: Mapping[elb.BoundMethod, np.ndarray]
def get_max_epsilon_bounds(self) -> np.ndarray:
"""Returns the bounds with largest average."""
bounds_val = [bound for bound in self.bounds.values() if bound.size]
if not bounds_val:
return np.array([])
best_index = np.argmax([bound.mean() for bound in bounds_val])
return bounds_val[best_index]
def __str__(self) -> str:
"""Returns string showing bounds with largest average."""
bounds_string = utils.format_number_list(self.get_max_epsilon_bounds())
return f'EpsilonLowerBoundValue([{bounds_string}])'
# (no. of training examples, no. of test examples) for the test.
DataSize = collections.namedtuple('DataSize', 'ntrain ntest')
@ -761,6 +789,10 @@ class SingleAttackResult:
# ROC curve representing the accuracy of the attacker
roc_curve: RocCurve
# Lower bound for DP epsilon, derived from tp and fp. For more details,
# see tensorflow_privacy/privacy/privacy_tests/epsilon_lower_bound.py.
epsilon_lower_bound_value: EpsilonLowerBoundValue
# Membership score is some measure of confidence of this attacker that
# a particular sample is a member of the training set.
#
@ -794,17 +826,23 @@ class SingleAttackResult:
def get_auc(self):
return self.roc_curve.get_auc()
def get_epsilon_lower_bound(self):
return self.epsilon_lower_bound_value.get_max_epsilon_bounds()
def __str__(self):
"""Returns SliceSpec, AttackType, AUC and advantage metrics."""
"""Returns SliceSpec, AttackType, and various MIA metrics."""
return '\n'.join([
'SingleAttackResult(',
' SliceSpec: %s' % str(self.slice_spec),
' DataSize: (ntrain=%d, ntest=%d)' %
(self.data_size.ntrain, self.data_size.ntest),
' DataSize: (ntrain=%d, ntest=%d)'
% (self.data_size.ntrain, self.data_size.ntest),
' AttackType: %s' % str(self.attack_type),
' AUC: %.2f' % self.get_auc(),
' Attacker advantage: %.2f' % self.get_attacker_advantage(),
' Positive Predictive Value: %.2f' % self.get_ppv(), ')'
' Positive Predictive Value: %.2f' % self.get_ppv(),
' Epsilon lower bound: '
+ utils.format_number_list(self.get_epsilon_lower_bound()),
')',
])
@ -906,6 +944,21 @@ class SingleMembershipProbabilityResult:
' thresholding on membership probability achieved an advantage of'
' %.2f' % (roc_curve.get_attacker_advantage())
)
epsilon_lower_bound_value = EpsilonLowerBoundValue(
bounds=elb.EpsilonLowerBound(
pos_scores=self.train_membership_probs,
neg_scores=self.test_membership_probs,
alpha=EPSILON_ALPHA,
two_sided_threshold=True,
).compute_epsilon_lower_bounds(methods=EPSILON_METHODS, k=EPSILON_K)
)
summary.append(
f' thresholding on membership probability achieved top-{EPSILON_K}'
+ ' epsilon lower bound of '
+ utils.format_number_list(
epsilon_lower_bound_value.get_max_epsilon_bounds()
)
)
return summary
@ -970,6 +1023,9 @@ class AttackResults:
advantages = []
ppvs = []
aucs = []
# Top EPSILON_K epsilon values for each single attack result.
# epsilon_lower_bounds[i][j] is the top-i epsilon value for attack j.
epsilon_lower_bounds = [[] for _ in range(EPSILON_K)]
for attack_result in self.single_attack_results:
slice_spec = attack_result.slice_spec
@ -985,17 +1041,30 @@ class AttackResults:
advantages.append(float(attack_result.get_attacker_advantage()))
ppvs.append(float(attack_result.get_ppv()))
aucs.append(float(attack_result.get_auc()))
current_elb = attack_result.get_epsilon_lower_bound()
for i in range(EPSILON_K):
if i < len(current_elb):
epsilon_lower_bounds[i].append(current_elb[i])
else: # If less than EPSILON_K values, use nan.
epsilon_lower_bounds[i].append(np.nan)
df = pd.DataFrame({
str(AttackResultsDFColumns.SLICE_FEATURE): slice_features,
str(AttackResultsDFColumns.SLICE_VALUE): slice_values,
str(AttackResultsDFColumns.DATA_SIZE_TRAIN): data_size_train,
str(AttackResultsDFColumns.DATA_SIZE_TEST): data_size_test,
str(AttackResultsDFColumns.ATTACK_TYPE): attack_types,
str(PrivacyMetric.ATTACKER_ADVANTAGE): advantages,
str(PrivacyMetric.PPV): ppvs,
str(PrivacyMetric.AUC): aucs
})
df = pd.DataFrame(
{
str(AttackResultsDFColumns.SLICE_FEATURE): slice_features,
str(AttackResultsDFColumns.SLICE_VALUE): slice_values,
str(AttackResultsDFColumns.DATA_SIZE_TRAIN): data_size_train,
str(AttackResultsDFColumns.DATA_SIZE_TEST): data_size_test,
str(AttackResultsDFColumns.ATTACK_TYPE): attack_types,
str(PrivacyMetric.ATTACKER_ADVANTAGE): advantages,
str(PrivacyMetric.PPV): ppvs,
str(PrivacyMetric.AUC): aucs,
}
| {
str(PrivacyMetric.EPSILON_LOWER_BOUND)
+ f'_{i + 1}': epsilon_lower_bounds[i]
for i in range(len(epsilon_lower_bounds))
}
)
return df
def summary(self, by_slices=False) -> str:
@ -1047,6 +1116,22 @@ class AttackResults:
max_ppv_result_all.data_size.ntest, max_ppv_result_all.get_ppv(),
max_ppv_result_all.slice_spec))
max_epsilon_lower_bound_all = self.get_result_with_max_epsilon()
summary.append(
' %s (with %d training and %d test examples) achieved top-%d epsilon '
'lower bounds of %s on slice %s'
% (
max_epsilon_lower_bound_all.attack_type,
max_epsilon_lower_bound_all.data_size.ntrain,
max_epsilon_lower_bound_all.data_size.ntest,
EPSILON_K,
utils.format_number_list(
max_epsilon_lower_bound_all.get_epsilon_lower_bound()
),
max_epsilon_lower_bound_all.slice_spec,
)
)
slice_dict = self._group_results_by_slice()
if by_slices and len(slice_dict.keys()) > 1:
@ -1082,7 +1167,20 @@ class AttackResults:
'predictive value of %.2f' %
(max_ppv_result.attack_type, max_ppv_result.data_size.ntrain,
max_ppv_result.data_size.ntest, max_ppv_result.get_ppv()))
max_epsilon_lower_bound_all = results.get_result_with_max_epsilon()
summary.append(
' %s (with %d training and %d test examples) achieved top-%d '
'epsilon lower bounds of %s'
% (
max_epsilon_lower_bound_all.attack_type,
max_epsilon_lower_bound_all.data_size.ntrain,
max_epsilon_lower_bound_all.data_size.ntest,
EPSILON_K,
utils.format_number_list(
max_epsilon_lower_bound_all.get_epsilon_lower_bound()
),
)
)
return '\n'.join(summary)
def _group_results_by_slice(self):
@ -1124,6 +1222,14 @@ class AttackResults:
return self.single_attack_results[np.argmax(
[result.get_ppv() for result in self.single_attack_results])]
def get_result_with_max_epsilon(self) -> SingleAttackResult:
"""Gets the result with max averaged epsilon lower bound."""
avg_epsilon_bounds = [
result.get_epsilon_lower_bound().mean()
for result in self.single_attack_results
]
return self.single_attack_results[np.argmax(avg_epsilon_bounds)]
def save(self, filepath):
"""Saves self to a pickle file."""
with open(filepath, 'wb') as out:

View file

@ -20,13 +20,16 @@ from absl.testing import absltest
from absl.testing import parameterized
import numpy as np
import pandas as pd
from tensorflow_privacy.privacy.privacy_tests import epsilon_lower_bound as elb
from tensorflow_privacy.privacy.privacy_tests import utils
from tensorflow_privacy.privacy.privacy_tests.membership_inference_attack import data_structures
from tensorflow_privacy.privacy.privacy_tests.membership_inference_attack.data_structures import _log_value
from tensorflow_privacy.privacy.privacy_tests.membership_inference_attack.data_structures import AttackInputData
from tensorflow_privacy.privacy.privacy_tests.membership_inference_attack.data_structures import AttackResults
from tensorflow_privacy.privacy.privacy_tests.membership_inference_attack.data_structures import AttackResultsCollection
from tensorflow_privacy.privacy.privacy_tests.membership_inference_attack.data_structures import AttackType
from tensorflow_privacy.privacy.privacy_tests.membership_inference_attack.data_structures import DataSize
from tensorflow_privacy.privacy.privacy_tests.membership_inference_attack.data_structures import EpsilonLowerBoundValue
from tensorflow_privacy.privacy.privacy_tests.membership_inference_attack.data_structures import PrivacyReportMetadata
from tensorflow_privacy.privacy.privacy_tests.membership_inference_attack.data_structures import RocCurve
from tensorflow_privacy.privacy.privacy_tests.membership_inference_attack.data_structures import SingleAttackResult
@ -578,9 +581,87 @@ class RocCurveTest(parameterized.TestCase):
np.testing.assert_allclose(roc.get_ppv(), 0.5, atol=1e-3)
def test_string(self):
roc = RocCurve(
tpr=np.array([0.0, 0.5, 1.0]),
fpr=np.array([0.0, 0.5, 1.0]),
thresholds=np.array([0, 1, 2]),
test_train_ratio=1.0,
)
self.assertEqual(
str(roc),
(
'RocCurve(\n'
' AUC: 0.50\n'
' Attacker advantage: 0.00\n'
' Positive predictive value: 0.50\n'
')'
),
)
class EpsilonLowerBoundValueTest(parameterized.TestCase):
"""Tests for EpsilonLowerBoundValue class."""
def test_epsilon_lower_bound_value(self):
bounds = {
elb.BoundMethod.KATZ_LOG: np.array([2, 2.0]),
elb.BoundMethod.BAILEY: np.array([5, 4.0]),
elb.BoundMethod.ADJUSTED_LOG: np.array([10]),
}
elbv = EpsilonLowerBoundValue(bounds=bounds)
self.assertDictEqual(elbv.bounds, bounds)
np.testing.assert_allclose(elbv.get_max_epsilon_bounds(), [10])
self.assertEqual(str(elbv), 'EpsilonLowerBoundValue([10.0000])')
def test_epsilon_lower_bound_value_empty(self):
elbv = EpsilonLowerBoundValue(bounds={})
self.assertDictEqual(elbv.bounds, {})
self.assertEmpty(elbv.get_max_epsilon_bounds())
self.assertEqual(str(elbv), 'EpsilonLowerBoundValue([])')
def test_epsilon_lower_bound_value_one_array_empty(self):
elbv = EpsilonLowerBoundValue(
bounds={
elb.BoundMethod.KATZ_LOG: np.array([-2, -2.0]),
elb.BoundMethod.ADJUSTED_LOG: np.array([]),
}
)
np.testing.assert_allclose(elbv.get_max_epsilon_bounds(), [-2, -2.0])
self.assertEqual(str(elbv), 'EpsilonLowerBoundValue([-2.0000, -2.0000])')
def test_epsilon_lower_bound_value_all_array_empty(self):
elbv = EpsilonLowerBoundValue(
bounds={
elb.BoundMethod.KATZ_LOG: np.array([]),
elb.BoundMethod.ADJUSTED_LOG: np.array([]),
}
)
self.assertEmpty(elbv.get_max_epsilon_bounds())
self.assertEqual(str(elbv), 'EpsilonLowerBoundValue([])')
class SingleAttackResultTest(absltest.TestCase):
def setUp(self):
super().setUp()
# Some arbitrary roc.
self.arbitrary_roc = RocCurve(
tpr=np.array([0.0, 0.5, 1.0]),
fpr=np.array([0.0, 0.5, 1.0]),
thresholds=np.array([0, 1, 2]),
test_train_ratio=1.0,
)
# Some arbitrary epsilon lower bound.
self.arbitrary_epsilon_lower_bound_value = EpsilonLowerBoundValue(
bounds={
elb.BoundMethod.KATZ_LOG: np.array([2, 2.0]),
elb.BoundMethod.BAILEY: np.array([5, 4.0]),
elb.BoundMethod.ADJUSTED_LOG: np.array([10, 1.0]),
}
)
# Only a basic test, as this method calls RocCurve which is tested separately.
def test_auc_random_classifier(self):
roc = RocCurve(
@ -591,9 +672,11 @@ class SingleAttackResultTest(absltest.TestCase):
result = SingleAttackResult(
roc_curve=roc,
epsilon_lower_bound_value=self.arbitrary_epsilon_lower_bound_value,
slice_spec=SingleSliceSpec(None),
attack_type=AttackType.THRESHOLD_ATTACK,
data_size=DataSize(ntrain=1, ntest=1))
data_size=DataSize(ntrain=1, ntest=1),
)
self.assertEqual(result.get_auc(), 0.5)
@ -607,9 +690,11 @@ class SingleAttackResultTest(absltest.TestCase):
result = SingleAttackResult(
roc_curve=roc,
epsilon_lower_bound_value=self.arbitrary_epsilon_lower_bound_value,
slice_spec=SingleSliceSpec(None),
attack_type=AttackType.THRESHOLD_ATTACK,
data_size=DataSize(ntrain=1, ntest=1))
data_size=DataSize(ntrain=1, ntest=1),
)
self.assertEqual(result.get_attacker_advantage(), 0.0)
@ -623,12 +708,59 @@ class SingleAttackResultTest(absltest.TestCase):
result = SingleAttackResult(
roc_curve=roc,
epsilon_lower_bound_value=self.arbitrary_epsilon_lower_bound_value,
slice_spec=SingleSliceSpec(None),
attack_type=AttackType.THRESHOLD_ATTACK,
data_size=DataSize(ntrain=1, ntest=1))
data_size=DataSize(ntrain=1, ntest=1),
)
self.assertEqual(result.get_ppv(), 0.5)
# Only a basic test, as this method calls EpsilonLowerBound which is tested
# separately.
def test_epsilon_lower_bound(self):
epsilon_lower_bound_value = EpsilonLowerBoundValue(
bounds={
elb.BoundMethod.KATZ_LOG: np.array([2, 2.0]),
elb.BoundMethod.BAILEY: np.array([5, 4.0]),
elb.BoundMethod.ADJUSTED_LOG: np.array([10, 1.0]),
}
)
expected = [10, 1.0]
result = SingleAttackResult(
roc_curve=self.arbitrary_roc,
epsilon_lower_bound_value=epsilon_lower_bound_value,
slice_spec=SingleSliceSpec(None),
attack_type=AttackType.THRESHOLD_ATTACK,
data_size=DataSize(ntrain=1, ntest=1),
)
returned_value = result.get_epsilon_lower_bound()
np.testing.assert_allclose(returned_value, expected, atol=1e-7)
def test_string(self):
result = SingleAttackResult(
roc_curve=self.arbitrary_roc,
epsilon_lower_bound_value=self.arbitrary_epsilon_lower_bound_value,
slice_spec=SingleSliceSpec(None),
attack_type=AttackType.THRESHOLD_ATTACK,
data_size=DataSize(ntrain=1, ntest=1),
)
self.assertEqual(
str(result),
(
'SingleAttackResult(\n'
' SliceSpec: Entire dataset\n'
' DataSize: (ntrain=1, ntest=1)\n'
' AttackType: THRESHOLD_ATTACK\n'
' AUC: 0.50\n'
' Attacker advantage: 0.00\n'
' Positive Predictive Value: 0.50\n'
' Epsilon lower bound: 10.0000, 1.0000\n'
')'
),
)
class SingleMembershipProbabilityResultTest(absltest.TestCase):
@ -648,6 +780,40 @@ class SingleMembershipProbabilityResultTest(absltest.TestCase):
result.attack_with_varied_thresholds(
threshold_list=np.array([0.8, 0.7]))[2].tolist(), [0.8, 1])
@mock.patch.object(data_structures, 'EPSILON_K', 4)
def test_collect_results(self):
result = SingleMembershipProbabilityResult(
slice_spec=SingleSliceSpec(None),
train_membership_probs=np.array([0.91, 1, 0.92, 0.82, 0.75]),
test_membership_probs=np.array([0.81, 0.7, 0.75, 0.25, 0.3]),
)
summary = result.collect_results(
threshold_list=np.array([0.8, 0.7]), return_roc_results=True
)
self.assertListEqual(
summary,
[
'\nMembership probability analysis over slice: "Entire dataset"',
(
' with 0.8000 as the threshold on membership probability, the'
' precision-recall pair is (0.8000, 0.8000)'
),
(
' with 0.7000 as the threshold on membership probability, the'
' precision-recall pair is (0.6250, 1.0000)'
),
' thresholding on membership probability achieved an AUC of 0.94',
(
' thresholding on membership probability achieved an advantage'
' of 0.80'
),
(
' thresholding on membership probability achieved top-4'
' epsilon lower bound of 0.3719, 0.2765, 0.1207, 0.1207'
),
],
)
class AttackResultsCollectionTest(absltest.TestCase):
@ -661,8 +827,15 @@ class AttackResultsCollectionTest(absltest.TestCase):
tpr=np.array([0.0, 0.5, 1.0]),
fpr=np.array([0.0, 0.5, 1.0]),
thresholds=np.array([0, 1, 2]),
test_train_ratio=1.0),
data_size=DataSize(ntrain=1, ntest=1))
test_train_ratio=1.0,
),
epsilon_lower_bound_value=EpsilonLowerBoundValue(
bounds={
elb.BoundMethod.KATZ_LOG: np.array([2, 2.0]),
}
),
data_size=DataSize(ntrain=1, ntest=1),
)
self.results_epoch_10 = AttackResults(
single_attack_results=[self.some_attack_result],
@ -722,8 +895,20 @@ class AttackResultsTest(absltest.TestCase):
tpr=np.array([0.0, 1.0, 1.0]),
fpr=np.array([1.0, 1.0, 0.0]),
thresholds=np.array([0, 1, 2]),
test_train_ratio=1.0),
data_size=DataSize(ntrain=1, ntest=1))
test_train_ratio=1.0,
),
epsilon_lower_bound_value=EpsilonLowerBoundValue(
bounds={
elb.BoundMethod.INV_SINH: np.array(
[2.65964282, 2.65964282, -0.01648963, -0.01648963]
),
elb.BoundMethod.CLOPPER_PEARSON: np.array(
[3.28134635, 3.28134635]
),
}
),
data_size=DataSize(ntrain=1, ntest=1),
)
# ROC curve of a random classifier
self.random_classifier_result = SingleAttackResult(
@ -733,8 +918,18 @@ class AttackResultsTest(absltest.TestCase):
tpr=np.array([0.0, 0.5, 1.0]),
fpr=np.array([0.0, 0.5, 1.0]),
thresholds=np.array([0, 1, 2]),
test_train_ratio=1.0),
data_size=DataSize(ntrain=1, ntest=1))
test_train_ratio=1.0,
),
epsilon_lower_bound_value=EpsilonLowerBoundValue(
bounds={
elb.BoundMethod.KATZ_LOG: np.array([-0.01648981, -0.01648981]),
elb.BoundMethod.ADJUSTED_LOG: np.array(
[-0.01640757, -0.01640757]
),
}
),
data_size=DataSize(ntrain=1, ntest=1),
)
def test_get_result_with_max_auc_first(self):
results = AttackResults(
@ -772,34 +967,59 @@ class AttackResultsTest(absltest.TestCase):
self.assertEqual(results.get_result_with_max_ppv(),
self.perfect_classifier_result)
def test_get_result_with_max_epsilon_first(self):
results = AttackResults(
[self.random_classifier_result, self.perfect_classifier_result]
)
self.assertEqual(
results.get_result_with_max_epsilon(), self.perfect_classifier_result
)
def test_get_result_with_max_epsilon_second(self):
results = AttackResults(
[self.random_classifier_result, self.perfect_classifier_result]
)
self.assertEqual(
results.get_result_with_max_epsilon(), self.perfect_classifier_result
)
def test_summary_by_slices(self):
results = AttackResults(
[self.perfect_classifier_result, self.random_classifier_result])
self.assertSequenceEqual(
results.summary(by_slices=True),
'Best-performing attacks over all slices\n' +
' THRESHOLD_ATTACK (with 1 training and 1 test examples) achieved an'
' AUC of 1.00 on slice CORRECTLY_CLASSIFIED=True\n' +
' THRESHOLD_ATTACK (with 1 training and 1 test examples) achieved an'
'Best-performing attacks over all slices\n'
+ ' THRESHOLD_ATTACK (with 1 training and 1 test examples) achieved an'
' AUC of 1.00 on slice CORRECTLY_CLASSIFIED=True\n'
+ ' THRESHOLD_ATTACK (with 1 training and 1 test examples) achieved an'
' advantage of 1.00 on slice CORRECTLY_CLASSIFIED=True\n'
' THRESHOLD_ATTACK (with 1 training and 1 test examples) achieved a'
' positive predictive value of 1.00 on slice CORRECTLY_CLASSIFIED='
'True\n\n'
'True\n'
' THRESHOLD_ATTACK (with 1 training and 1 test examples) achieved'
' top-5 epsilon lower bounds of 3.2813, 3.2813 on slice'
' CORRECTLY_CLASSIFIED=True\n\n'
'Best-performing attacks over slice: "CORRECTLY_CLASSIFIED=True"\n'
' THRESHOLD_ATTACK (with 1 training and 1 test examples) achieved an'
' AUC of 1.00\n'
' THRESHOLD_ATTACK (with 1 training and 1 test examples) achieved an'
' advantage of 1.00\n'
' THRESHOLD_ATTACK (with 1 training and 1 test examples) achieved a'
' positive predictive value of 1.00\n\n'
' positive predictive value of 1.00\n'
' THRESHOLD_ATTACK (with 1 training and 1 test examples) achieved'
' top-5 epsilon lower bounds of 3.2813, 3.2813\n\n'
'Best-performing attacks over slice: "Entire dataset"\n'
' THRESHOLD_ATTACK (with 1 training and 1 test examples) achieved an'
' AUC of 0.50\n'
' THRESHOLD_ATTACK (with 1 training and 1 test examples) achieved an'
' advantage of 0.00\n'
' THRESHOLD_ATTACK (with 1 training and 1 test examples) achieved a'
' positive predictive value of 0.50')
' positive predictive value of 0.50\n'
' THRESHOLD_ATTACK (with 1 training and 1 test examples) achieved'
' top-5 epsilon lower bounds of -0.0164, -0.0164',
)
@mock.patch.object(data_structures, 'EPSILON_K', 4)
def test_summary_without_slices(self):
results = AttackResults(
[self.perfect_classifier_result, self.random_classifier_result])
@ -811,7 +1031,12 @@ class AttackResultsTest(absltest.TestCase):
' THRESHOLD_ATTACK (with 1 training and 1 test examples) achieved an'
' advantage of 1.00 on slice CORRECTLY_CLASSIFIED=True\n'
' THRESHOLD_ATTACK (with 1 training and 1 test examples) achieved a'
' positive predictive value of 1.00 on slice CORRECTLY_CLASSIFIED=True')
' positive predictive value of 1.00 on slice'
' CORRECTLY_CLASSIFIED=True\n'
' THRESHOLD_ATTACK (with 1 training and 1 test examples) achieved'
' top-4 epsilon lower bounds of 3.2813, 3.2813 on slice'
' CORRECTLY_CLASSIFIED=True',
)
def test_save_load(self):
results = AttackResults(
@ -824,6 +1049,7 @@ class AttackResultsTest(absltest.TestCase):
self.assertEqual(repr(results), repr(loaded_results))
@mock.patch.object(data_structures, 'EPSILON_K', 4)
def test_calculate_pd_dataframe(self):
single_results = [
self.perfect_classifier_result, self.random_classifier_result
@ -838,7 +1064,11 @@ class AttackResultsTest(absltest.TestCase):
'attack type': ['THRESHOLD_ATTACK', 'THRESHOLD_ATTACK'],
'Attacker advantage': [1.0, 0.0],
'Positive predictive value': [1.0, 0.5],
'AUC': [1.0, 0.5]
'AUC': [1.0, 0.5],
'Epsilon lower bound_1': [3.28134635, -0.01640757],
'Epsilon lower bound_2': [3.28134635, -0.01640757],
'Epsilon lower bound_3': [np.nan, np.nan],
'Epsilon lower bound_4': [np.nan, np.nan],
})
pd.testing.assert_frame_equal(df, df_expected)

View file

@ -24,12 +24,16 @@ import numpy as np
from scipy import special
from sklearn import metrics
from sklearn import model_selection
from tensorflow_privacy.privacy.privacy_tests import epsilon_lower_bound as elb
from tensorflow_privacy.privacy.privacy_tests.membership_inference_attack import models
from tensorflow_privacy.privacy.privacy_tests.membership_inference_attack.data_structures import AttackInputData
from tensorflow_privacy.privacy.privacy_tests.membership_inference_attack.data_structures import AttackResults
from tensorflow_privacy.privacy.privacy_tests.membership_inference_attack.data_structures import AttackType
from tensorflow_privacy.privacy.privacy_tests.membership_inference_attack.data_structures import DataSize
from tensorflow_privacy.privacy.privacy_tests.membership_inference_attack.data_structures import EPSILON_ALPHA
from tensorflow_privacy.privacy.privacy_tests.membership_inference_attack.data_structures import EPSILON_K
from tensorflow_privacy.privacy.privacy_tests.membership_inference_attack.data_structures import EPSILON_METHODS
from tensorflow_privacy.privacy.privacy_tests.membership_inference_attack.data_structures import EpsilonLowerBoundValue
from tensorflow_privacy.privacy.privacy_tests.membership_inference_attack.data_structures import MembershipProbabilityResults
from tensorflow_privacy.privacy.privacy_tests.membership_inference_attack.data_structures import PrivacyReportMetadata
from tensorflow_privacy.privacy.privacy_tests.membership_inference_attack.data_structures import RocCurve
@ -135,13 +139,23 @@ def _run_trained_attack(
test_train_ratio=test_train_ratio)
in_train_indices = labels == 1
epsilon_lower_bound_value = EpsilonLowerBoundValue(
bounds=elb.EpsilonLowerBound(
pos_scores=scores[in_train_indices],
neg_scores=scores[~in_train_indices],
alpha=EPSILON_ALPHA,
two_sided_threshold=True,
).compute_epsilon_lower_bounds(methods=EPSILON_METHODS, k=EPSILON_K)
)
return SingleAttackResult(
slice_spec=_get_slice_spec(attack_input),
data_size=prepared_attacker_data.data_size,
attack_type=attack_type,
membership_scores_train=scores[in_train_indices],
membership_scores_test=scores[~in_train_indices],
roc_curve=roc_curve)
roc_curve=roc_curve,
epsilon_lower_bound_value=epsilon_lower_bound_value,
)
def _run_threshold_attack(attack_input: AttackInputData):
@ -173,6 +187,14 @@ def _run_threshold_attack(attack_input: AttackInputData):
thresholds=-thresholds, # negate because we negated the loss
test_train_ratio=test_train_ratio,
)
epsilon_lower_bound_value = EpsilonLowerBoundValue(
bounds=elb.EpsilonLowerBound(
pos_scores=loss_train,
neg_scores=loss_test,
alpha=EPSILON_ALPHA,
two_sided_threshold=True,
).compute_epsilon_lower_bounds(methods=EPSILON_METHODS, k=EPSILON_K)
)
return SingleAttackResult(
slice_spec=_get_slice_spec(attack_input),
@ -180,7 +202,9 @@ def _run_threshold_attack(attack_input: AttackInputData):
attack_type=AttackType.THRESHOLD_ATTACK,
membership_scores_train=attack_input.get_loss_train(),
membership_scores_test=attack_input.get_loss_test(),
roc_curve=roc_curve)
roc_curve=roc_curve,
epsilon_lower_bound_value=epsilon_lower_bound_value,
)
def _run_threshold_entropy_attack(attack_input: AttackInputData):
@ -207,14 +231,23 @@ def _run_threshold_entropy_attack(attack_input: AttackInputData):
thresholds=-thresholds, # negate because we negated the loss
test_train_ratio=test_train_ratio,
)
epsilon_lower_bound_value = EpsilonLowerBoundValue(
bounds=elb.EpsilonLowerBound(
pos_scores=attack_input.get_entropy_train(),
neg_scores=attack_input.get_entropy_test(),
alpha=EPSILON_ALPHA,
two_sided_threshold=True,
).compute_epsilon_lower_bounds(methods=EPSILON_METHODS, k=EPSILON_K)
)
return SingleAttackResult(
slice_spec=_get_slice_spec(attack_input),
data_size=DataSize(ntrain=ntrain, ntest=ntest),
attack_type=AttackType.THRESHOLD_ENTROPY_ATTACK,
membership_scores_train=-attack_input.get_entropy_train(),
membership_scores_test=-attack_input.get_entropy_test(),
roc_curve=roc_curve)
roc_curve=roc_curve,
epsilon_lower_bound_value=epsilon_lower_bound_value,
)
def _run_attack(attack_input: AttackInputData,

View file

@ -17,7 +17,9 @@ from unittest import mock
from absl.testing import absltest
from absl.testing import parameterized
import numpy as np
from tensorflow_privacy.privacy.privacy_tests import epsilon_lower_bound as elb
from tensorflow_privacy.privacy.privacy_tests.membership_inference_attack import membership_inference_attack as mia
from tensorflow_privacy.privacy.privacy_tests.membership_inference_attack import models
from tensorflow_privacy.privacy.privacy_tests.membership_inference_attack.data_structures import AttackInputData
from tensorflow_privacy.privacy.privacy_tests.membership_inference_attack.data_structures import AttackType
from tensorflow_privacy.privacy.privacy_tests.membership_inference_attack.data_structures import DataSize
@ -103,6 +105,21 @@ def get_test_input_logits_only_with_sample_weights(n_train, n_test):
sample_weight_test=rng.randn(n_test, 1))
class MockTrainedAttacker(object):
"""Mock for TrainedAttacker."""
def __init__(self, backend):
del backend
return
def train_model(self, input_features, is_training_labels, sample_weight=None):
del input_features, is_training_labels, sample_weight
return
def predict(self, input_features):
return input_features[:, 0]
class RunAttacksTest(parameterized.TestCase):
def test_run_attacks_size(self):
@ -285,6 +302,117 @@ class RunAttacksTest(parameterized.TestCase):
# namely 0.5.
np.testing.assert_almost_equal(result.roc_curve.get_ppv(), 0.5, decimal=2)
@parameterized.parameters(
(AttackType.THRESHOLD_ATTACK, 'loss_train', 'loss_test'),
(AttackType.THRESHOLD_ENTROPY_ATTACK, 'entropy_train', 'entropy_test'),
)
@mock.patch.object(mia, 'EPSILON_K', 4)
def test_run_attack_threshold_calculates_correct_epsilon(
self, attack_type, train_metric_name, test_metric_name
):
result = mia._run_attack(
AttackInputData(
**{
train_metric_name: np.array([0.1, 0.2, 1.3, 0.4, 0.5, 0.6]),
test_metric_name: np.array([1.1, 1.2, 1.3, 0.4, 1.5, 1.6]),
}
),
attack_type,
)
np.testing.assert_almost_equal(
result.epsilon_lower_bound_value.get_max_epsilon_bounds(),
np.array([0.34695111, 0.34695111, 0.05616349, 0.05616349]),
)
@parameterized.product(
(
dict(
epsilon_methods=tuple(elb.BoundMethod),
expected_max_method=elb.BoundMethod.CLOPPER_PEARSON,
),
dict(
epsilon_methods=(
elb.BoundMethod.KATZ_LOG,
elb.BoundMethod.CLOPPER_PEARSON,
),
expected_max_method=elb.BoundMethod.CLOPPER_PEARSON,
),
dict(
epsilon_methods=(elb.BoundMethod.BAILEY,),
expected_max_method=elb.BoundMethod.BAILEY,
),
),
attack_type=[
AttackType.LOGISTIC_REGRESSION,
AttackType.MULTI_LAYERED_PERCEPTRON,
AttackType.RANDOM_FOREST,
AttackType.K_NEAREST_NEIGHBORS,
],
)
@mock.patch.object(models, 'TrainedAttacker', MockTrainedAttacker)
@mock.patch.object(models, 'LogisticRegressionAttacker', MockTrainedAttacker)
@mock.patch.object(
models, 'MultilayerPerceptronAttacker', MockTrainedAttacker
)
@mock.patch.object(models, 'RandomForestAttacker', MockTrainedAttacker)
@mock.patch.object(models, 'KNearestNeighborsAttacker', MockTrainedAttacker)
def test_run_attack_trained_calculates_correct_epsilon(
self,
epsilon_methods,
expected_max_method,
attack_type,
):
logits_train = np.ones((1000, 5))
logits_test = np.zeros((100, 5))
# The prediction would be all 1 for training and all 0 for test.
expected_bounds = {
elb.BoundMethod.KATZ_LOG: [
5.27530977,
2.97796578,
-0.00720554,
-0.01623037,
],
elb.BoundMethod.ADJUSTED_LOG: [
5.27580935,
2.98292432,
-0.00717236,
-0.01614769,
-7.62368550,
],
elb.BoundMethod.BAILEY: [
5.87410287,
3.57903543,
-0.00718316,
-0.01625286,
],
elb.BoundMethod.INV_SINH: [
4.95123977,
2.65964282,
-0.00720547,
-0.01623030,
],
elb.BoundMethod.CLOPPER_PEARSON: [
5.56738762,
3.31454626,
],
}
with mock.patch.object(mia, 'EPSILON_METHODS', epsilon_methods):
result = mia._run_attack(
AttackInputData(logits_train, logits_test),
attack_type,
)
self.assertCountEqual(
result.epsilon_lower_bound_value.bounds.keys(), epsilon_methods
)
for key in epsilon_methods:
np.testing.assert_almost_equal(
result.epsilon_lower_bound_value.bounds[key], expected_bounds[key]
)
np.testing.assert_almost_equal(
result.epsilon_lower_bound_value.get_max_epsilon_bounds(),
expected_bounds[expected_max_method],
)
def test_run_attack_by_slice(self):
result = mia.run_attacks(
get_test_input(100, 100), SlicingSpec(by_class=True),

View file

@ -14,12 +14,13 @@
from absl.testing import absltest
import numpy as np
from tensorflow_privacy.privacy.privacy_tests import epsilon_lower_bound as elb
from tensorflow_privacy.privacy.privacy_tests.membership_inference_attack import privacy_report
from tensorflow_privacy.privacy.privacy_tests.membership_inference_attack.data_structures import AttackResults
from tensorflow_privacy.privacy.privacy_tests.membership_inference_attack.data_structures import AttackResultsCollection
from tensorflow_privacy.privacy.privacy_tests.membership_inference_attack.data_structures import AttackType
from tensorflow_privacy.privacy.privacy_tests.membership_inference_attack.data_structures import DataSize
from tensorflow_privacy.privacy.privacy_tests.membership_inference_attack.data_structures import EpsilonLowerBoundValue
from tensorflow_privacy.privacy.privacy_tests.membership_inference_attack.data_structures import PrivacyReportMetadata
from tensorflow_privacy.privacy.privacy_tests.membership_inference_attack.data_structures import RocCurve
from tensorflow_privacy.privacy.privacy_tests.membership_inference_attack.data_structures import SingleAttackResult
@ -39,8 +40,16 @@ class PrivacyReportTest(absltest.TestCase):
tpr=np.array([0.0, 0.5, 1.0]),
fpr=np.array([0.0, 0.5, 1.0]),
thresholds=np.array([0, 1, 2]),
test_train_ratio=1.0),
data_size=DataSize(ntrain=1, ntest=1))
test_train_ratio=1.0,
),
epsilon_lower_bound_value=EpsilonLowerBoundValue(
bounds={
elb.BoundMethod.KATZ_LOG: np.array([-2, -2.0]),
elb.BoundMethod.ADJUSTED_LOG: np.array([]),
}
),
data_size=DataSize(ntrain=1, ntest=1),
)
# Classifier that achieves an AUC of 1.0.
self.perfect_classifier_result = SingleAttackResult(
@ -50,8 +59,16 @@ class PrivacyReportTest(absltest.TestCase):
tpr=np.array([0.0, 1.0, 1.0]),
fpr=np.array([1.0, 1.0, 0.0]),
thresholds=np.array([0, 1, 2]),
test_train_ratio=1.0),
data_size=DataSize(ntrain=1, ntest=1))
test_train_ratio=1.0,
),
epsilon_lower_bound_value=EpsilonLowerBoundValue(
bounds={
elb.BoundMethod.KATZ_LOG: np.array([-2, -2.0]),
elb.BoundMethod.ADJUSTED_LOG: np.array([10, 1.0]),
}
),
data_size=DataSize(ntrain=1, ntest=1),
)
self.results_epoch_0 = AttackResults(
single_attack_results=[self.imperfect_classifier_result],