Calls epsilon computation in MIA.
PiperOrigin-RevId: 551003589
This commit is contained in:
parent
8e60864559
commit
225355258c
6 changed files with 568 additions and 46 deletions
|
@ -21,7 +21,10 @@ py_test(
|
||||||
srcs = ["membership_inference_attack_test.py"],
|
srcs = ["membership_inference_attack_test.py"],
|
||||||
python_version = "PY3",
|
python_version = "PY3",
|
||||||
srcs_version = "PY3",
|
srcs_version = "PY3",
|
||||||
deps = [":membership_inference_attack"],
|
deps = [
|
||||||
|
":membership_inference_attack",
|
||||||
|
"//tensorflow_privacy/privacy/privacy_tests:epsilon_lower_bound",
|
||||||
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
py_test(
|
py_test(
|
||||||
|
@ -32,6 +35,7 @@ py_test(
|
||||||
srcs_version = "PY3",
|
srcs_version = "PY3",
|
||||||
deps = [
|
deps = [
|
||||||
":membership_inference_attack",
|
":membership_inference_attack",
|
||||||
|
"//tensorflow_privacy/privacy/privacy_tests:epsilon_lower_bound",
|
||||||
"//tensorflow_privacy/privacy/privacy_tests:utils",
|
"//tensorflow_privacy/privacy/privacy_tests:utils",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
@ -62,6 +66,7 @@ py_test(
|
||||||
deps = [
|
deps = [
|
||||||
":membership_inference_attack",
|
":membership_inference_attack",
|
||||||
":privacy_report",
|
":privacy_report",
|
||||||
|
"//tensorflow_privacy/privacy/privacy_tests:epsilon_lower_bound",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -83,7 +88,10 @@ py_library(
|
||||||
"seq2seq_mia.py",
|
"seq2seq_mia.py",
|
||||||
],
|
],
|
||||||
srcs_version = "PY3",
|
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(
|
py_library(
|
||||||
|
|
|
@ -20,12 +20,13 @@ import glob
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import pickle
|
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 numpy as np
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
from scipy import special
|
from scipy import special
|
||||||
from sklearn import metrics
|
from sklearn import metrics
|
||||||
|
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 import utils
|
||||||
|
|
||||||
# The minimum TPR or FPR below which they are considered equal.
|
# 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'
|
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):
|
class SlicingFeature(enum.Enum):
|
||||||
"""Enum with features by which slicing is available."""
|
"""Enum with features by which slicing is available."""
|
||||||
|
@ -189,6 +195,7 @@ class PrivacyMetric(enum.Enum):
|
||||||
AUC = 'AUC'
|
AUC = 'AUC'
|
||||||
ATTACKER_ADVANTAGE = 'Attacker advantage'
|
ATTACKER_ADVANTAGE = 'Attacker advantage'
|
||||||
PPV = 'Positive predictive value'
|
PPV = 'Positive predictive value'
|
||||||
|
EPSILON_LOWER_BOUND = 'Epsilon lower bound'
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
"""Returns 'AUC' instead of PrivacyMetric.AUC."""
|
"""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.
|
# (no. of training examples, no. of test examples) for the test.
|
||||||
DataSize = collections.namedtuple('DataSize', 'ntrain ntest')
|
DataSize = collections.namedtuple('DataSize', 'ntrain ntest')
|
||||||
|
|
||||||
|
@ -761,6 +789,10 @@ class SingleAttackResult:
|
||||||
# ROC curve representing the accuracy of the attacker
|
# ROC curve representing the accuracy of the attacker
|
||||||
roc_curve: RocCurve
|
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
|
# Membership score is some measure of confidence of this attacker that
|
||||||
# a particular sample is a member of the training set.
|
# a particular sample is a member of the training set.
|
||||||
#
|
#
|
||||||
|
@ -794,17 +826,23 @@ class SingleAttackResult:
|
||||||
def get_auc(self):
|
def get_auc(self):
|
||||||
return self.roc_curve.get_auc()
|
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):
|
def __str__(self):
|
||||||
"""Returns SliceSpec, AttackType, AUC and advantage metrics."""
|
"""Returns SliceSpec, AttackType, and various MIA metrics."""
|
||||||
return '\n'.join([
|
return '\n'.join([
|
||||||
'SingleAttackResult(',
|
'SingleAttackResult(',
|
||||||
' SliceSpec: %s' % str(self.slice_spec),
|
' SliceSpec: %s' % str(self.slice_spec),
|
||||||
' DataSize: (ntrain=%d, ntest=%d)' %
|
' DataSize: (ntrain=%d, ntest=%d)'
|
||||||
(self.data_size.ntrain, self.data_size.ntest),
|
% (self.data_size.ntrain, self.data_size.ntest),
|
||||||
' AttackType: %s' % str(self.attack_type),
|
' AttackType: %s' % str(self.attack_type),
|
||||||
' AUC: %.2f' % self.get_auc(),
|
' AUC: %.2f' % self.get_auc(),
|
||||||
' Attacker advantage: %.2f' % self.get_attacker_advantage(),
|
' 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'
|
' thresholding on membership probability achieved an advantage of'
|
||||||
' %.2f' % (roc_curve.get_attacker_advantage())
|
' %.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
|
return summary
|
||||||
|
|
||||||
|
|
||||||
|
@ -970,6 +1023,9 @@ class AttackResults:
|
||||||
advantages = []
|
advantages = []
|
||||||
ppvs = []
|
ppvs = []
|
||||||
aucs = []
|
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:
|
for attack_result in self.single_attack_results:
|
||||||
slice_spec = attack_result.slice_spec
|
slice_spec = attack_result.slice_spec
|
||||||
|
@ -985,17 +1041,30 @@ class AttackResults:
|
||||||
advantages.append(float(attack_result.get_attacker_advantage()))
|
advantages.append(float(attack_result.get_attacker_advantage()))
|
||||||
ppvs.append(float(attack_result.get_ppv()))
|
ppvs.append(float(attack_result.get_ppv()))
|
||||||
aucs.append(float(attack_result.get_auc()))
|
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({
|
df = pd.DataFrame(
|
||||||
str(AttackResultsDFColumns.SLICE_FEATURE): slice_features,
|
{
|
||||||
str(AttackResultsDFColumns.SLICE_VALUE): slice_values,
|
str(AttackResultsDFColumns.SLICE_FEATURE): slice_features,
|
||||||
str(AttackResultsDFColumns.DATA_SIZE_TRAIN): data_size_train,
|
str(AttackResultsDFColumns.SLICE_VALUE): slice_values,
|
||||||
str(AttackResultsDFColumns.DATA_SIZE_TEST): data_size_test,
|
str(AttackResultsDFColumns.DATA_SIZE_TRAIN): data_size_train,
|
||||||
str(AttackResultsDFColumns.ATTACK_TYPE): attack_types,
|
str(AttackResultsDFColumns.DATA_SIZE_TEST): data_size_test,
|
||||||
str(PrivacyMetric.ATTACKER_ADVANTAGE): advantages,
|
str(AttackResultsDFColumns.ATTACK_TYPE): attack_types,
|
||||||
str(PrivacyMetric.PPV): ppvs,
|
str(PrivacyMetric.ATTACKER_ADVANTAGE): advantages,
|
||||||
str(PrivacyMetric.AUC): aucs
|
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
|
return df
|
||||||
|
|
||||||
def summary(self, by_slices=False) -> str:
|
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.data_size.ntest, max_ppv_result_all.get_ppv(),
|
||||||
max_ppv_result_all.slice_spec))
|
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()
|
slice_dict = self._group_results_by_slice()
|
||||||
|
|
||||||
if by_slices and len(slice_dict.keys()) > 1:
|
if by_slices and len(slice_dict.keys()) > 1:
|
||||||
|
@ -1082,7 +1167,20 @@ class AttackResults:
|
||||||
'predictive value of %.2f' %
|
'predictive value of %.2f' %
|
||||||
(max_ppv_result.attack_type, max_ppv_result.data_size.ntrain,
|
(max_ppv_result.attack_type, max_ppv_result.data_size.ntrain,
|
||||||
max_ppv_result.data_size.ntest, max_ppv_result.get_ppv()))
|
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)
|
return '\n'.join(summary)
|
||||||
|
|
||||||
def _group_results_by_slice(self):
|
def _group_results_by_slice(self):
|
||||||
|
@ -1124,6 +1222,14 @@ class AttackResults:
|
||||||
return self.single_attack_results[np.argmax(
|
return self.single_attack_results[np.argmax(
|
||||||
[result.get_ppv() for result in self.single_attack_results])]
|
[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):
|
def save(self, filepath):
|
||||||
"""Saves self to a pickle file."""
|
"""Saves self to a pickle file."""
|
||||||
with open(filepath, 'wb') as out:
|
with open(filepath, 'wb') as out:
|
||||||
|
|
|
@ -20,13 +20,16 @@ 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.privacy_tests import epsilon_lower_bound as elb
|
||||||
from tensorflow_privacy.privacy.privacy_tests import utils
|
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 _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 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 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 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 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 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 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 RocCurve
|
||||||
from tensorflow_privacy.privacy.privacy_tests.membership_inference_attack.data_structures import SingleAttackResult
|
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)
|
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):
|
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.
|
# Only a basic test, as this method calls RocCurve which is tested separately.
|
||||||
def test_auc_random_classifier(self):
|
def test_auc_random_classifier(self):
|
||||||
roc = RocCurve(
|
roc = RocCurve(
|
||||||
|
@ -591,9 +672,11 @@ class SingleAttackResultTest(absltest.TestCase):
|
||||||
|
|
||||||
result = SingleAttackResult(
|
result = SingleAttackResult(
|
||||||
roc_curve=roc,
|
roc_curve=roc,
|
||||||
|
epsilon_lower_bound_value=self.arbitrary_epsilon_lower_bound_value,
|
||||||
slice_spec=SingleSliceSpec(None),
|
slice_spec=SingleSliceSpec(None),
|
||||||
attack_type=AttackType.THRESHOLD_ATTACK,
|
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)
|
self.assertEqual(result.get_auc(), 0.5)
|
||||||
|
|
||||||
|
@ -607,9 +690,11 @@ class SingleAttackResultTest(absltest.TestCase):
|
||||||
|
|
||||||
result = SingleAttackResult(
|
result = SingleAttackResult(
|
||||||
roc_curve=roc,
|
roc_curve=roc,
|
||||||
|
epsilon_lower_bound_value=self.arbitrary_epsilon_lower_bound_value,
|
||||||
slice_spec=SingleSliceSpec(None),
|
slice_spec=SingleSliceSpec(None),
|
||||||
attack_type=AttackType.THRESHOLD_ATTACK,
|
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)
|
self.assertEqual(result.get_attacker_advantage(), 0.0)
|
||||||
|
|
||||||
|
@ -623,12 +708,59 @@ class SingleAttackResultTest(absltest.TestCase):
|
||||||
|
|
||||||
result = SingleAttackResult(
|
result = SingleAttackResult(
|
||||||
roc_curve=roc,
|
roc_curve=roc,
|
||||||
|
epsilon_lower_bound_value=self.arbitrary_epsilon_lower_bound_value,
|
||||||
slice_spec=SingleSliceSpec(None),
|
slice_spec=SingleSliceSpec(None),
|
||||||
attack_type=AttackType.THRESHOLD_ATTACK,
|
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)
|
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):
|
class SingleMembershipProbabilityResultTest(absltest.TestCase):
|
||||||
|
|
||||||
|
@ -648,6 +780,40 @@ class SingleMembershipProbabilityResultTest(absltest.TestCase):
|
||||||
result.attack_with_varied_thresholds(
|
result.attack_with_varied_thresholds(
|
||||||
threshold_list=np.array([0.8, 0.7]))[2].tolist(), [0.8, 1])
|
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):
|
class AttackResultsCollectionTest(absltest.TestCase):
|
||||||
|
|
||||||
|
@ -661,8 +827,15 @@ class AttackResultsCollectionTest(absltest.TestCase):
|
||||||
tpr=np.array([0.0, 0.5, 1.0]),
|
tpr=np.array([0.0, 0.5, 1.0]),
|
||||||
fpr=np.array([0.0, 0.5, 1.0]),
|
fpr=np.array([0.0, 0.5, 1.0]),
|
||||||
thresholds=np.array([0, 1, 2]),
|
thresholds=np.array([0, 1, 2]),
|
||||||
test_train_ratio=1.0),
|
test_train_ratio=1.0,
|
||||||
data_size=DataSize(ntrain=1, ntest=1))
|
),
|
||||||
|
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(
|
self.results_epoch_10 = AttackResults(
|
||||||
single_attack_results=[self.some_attack_result],
|
single_attack_results=[self.some_attack_result],
|
||||||
|
@ -722,8 +895,20 @@ class AttackResultsTest(absltest.TestCase):
|
||||||
tpr=np.array([0.0, 1.0, 1.0]),
|
tpr=np.array([0.0, 1.0, 1.0]),
|
||||||
fpr=np.array([1.0, 1.0, 0.0]),
|
fpr=np.array([1.0, 1.0, 0.0]),
|
||||||
thresholds=np.array([0, 1, 2]),
|
thresholds=np.array([0, 1, 2]),
|
||||||
test_train_ratio=1.0),
|
test_train_ratio=1.0,
|
||||||
data_size=DataSize(ntrain=1, ntest=1))
|
),
|
||||||
|
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
|
# ROC curve of a random classifier
|
||||||
self.random_classifier_result = SingleAttackResult(
|
self.random_classifier_result = SingleAttackResult(
|
||||||
|
@ -733,8 +918,18 @@ class AttackResultsTest(absltest.TestCase):
|
||||||
tpr=np.array([0.0, 0.5, 1.0]),
|
tpr=np.array([0.0, 0.5, 1.0]),
|
||||||
fpr=np.array([0.0, 0.5, 1.0]),
|
fpr=np.array([0.0, 0.5, 1.0]),
|
||||||
thresholds=np.array([0, 1, 2]),
|
thresholds=np.array([0, 1, 2]),
|
||||||
test_train_ratio=1.0),
|
test_train_ratio=1.0,
|
||||||
data_size=DataSize(ntrain=1, ntest=1))
|
),
|
||||||
|
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):
|
def test_get_result_with_max_auc_first(self):
|
||||||
results = AttackResults(
|
results = AttackResults(
|
||||||
|
@ -772,34 +967,59 @@ class AttackResultsTest(absltest.TestCase):
|
||||||
self.assertEqual(results.get_result_with_max_ppv(),
|
self.assertEqual(results.get_result_with_max_ppv(),
|
||||||
self.perfect_classifier_result)
|
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):
|
def test_summary_by_slices(self):
|
||||||
results = AttackResults(
|
results = AttackResults(
|
||||||
[self.perfect_classifier_result, self.random_classifier_result])
|
[self.perfect_classifier_result, self.random_classifier_result])
|
||||||
self.assertSequenceEqual(
|
self.assertSequenceEqual(
|
||||||
results.summary(by_slices=True),
|
results.summary(by_slices=True),
|
||||||
'Best-performing attacks over all slices\n' +
|
'Best-performing attacks over all slices\n'
|
||||||
' THRESHOLD_ATTACK (with 1 training and 1 test examples) achieved an'
|
+ ' THRESHOLD_ATTACK (with 1 training and 1 test examples) achieved an'
|
||||||
' AUC of 1.00 on slice CORRECTLY_CLASSIFIED=True\n' +
|
' AUC of 1.00 on slice CORRECTLY_CLASSIFIED=True\n'
|
||||||
' THRESHOLD_ATTACK (with 1 training and 1 test examples) achieved an'
|
+ ' THRESHOLD_ATTACK (with 1 training and 1 test examples) achieved an'
|
||||||
' advantage of 1.00 on slice CORRECTLY_CLASSIFIED=True\n'
|
' advantage of 1.00 on slice CORRECTLY_CLASSIFIED=True\n'
|
||||||
' THRESHOLD_ATTACK (with 1 training and 1 test examples) achieved a'
|
' THRESHOLD_ATTACK (with 1 training and 1 test examples) achieved a'
|
||||||
' positive predictive value of 1.00 on slice CORRECTLY_CLASSIFIED='
|
' 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'
|
'Best-performing attacks over slice: "CORRECTLY_CLASSIFIED=True"\n'
|
||||||
' THRESHOLD_ATTACK (with 1 training and 1 test examples) achieved an'
|
' THRESHOLD_ATTACK (with 1 training and 1 test examples) achieved an'
|
||||||
' AUC of 1.00\n'
|
' AUC of 1.00\n'
|
||||||
' THRESHOLD_ATTACK (with 1 training and 1 test examples) achieved an'
|
' THRESHOLD_ATTACK (with 1 training and 1 test examples) achieved an'
|
||||||
' advantage of 1.00\n'
|
' advantage of 1.00\n'
|
||||||
' THRESHOLD_ATTACK (with 1 training and 1 test examples) achieved a'
|
' 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'
|
'Best-performing attacks over slice: "Entire dataset"\n'
|
||||||
' THRESHOLD_ATTACK (with 1 training and 1 test examples) achieved an'
|
' THRESHOLD_ATTACK (with 1 training and 1 test examples) achieved an'
|
||||||
' AUC of 0.50\n'
|
' AUC of 0.50\n'
|
||||||
' THRESHOLD_ATTACK (with 1 training and 1 test examples) achieved an'
|
' THRESHOLD_ATTACK (with 1 training and 1 test examples) achieved an'
|
||||||
' advantage of 0.00\n'
|
' advantage of 0.00\n'
|
||||||
' THRESHOLD_ATTACK (with 1 training and 1 test examples) achieved a'
|
' 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):
|
def test_summary_without_slices(self):
|
||||||
results = AttackResults(
|
results = AttackResults(
|
||||||
[self.perfect_classifier_result, self.random_classifier_result])
|
[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'
|
' THRESHOLD_ATTACK (with 1 training and 1 test examples) achieved an'
|
||||||
' advantage of 1.00 on slice CORRECTLY_CLASSIFIED=True\n'
|
' advantage of 1.00 on slice CORRECTLY_CLASSIFIED=True\n'
|
||||||
' THRESHOLD_ATTACK (with 1 training and 1 test examples) achieved a'
|
' 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):
|
def test_save_load(self):
|
||||||
results = AttackResults(
|
results = AttackResults(
|
||||||
|
@ -824,6 +1049,7 @@ class AttackResultsTest(absltest.TestCase):
|
||||||
|
|
||||||
self.assertEqual(repr(results), repr(loaded_results))
|
self.assertEqual(repr(results), repr(loaded_results))
|
||||||
|
|
||||||
|
@mock.patch.object(data_structures, 'EPSILON_K', 4)
|
||||||
def test_calculate_pd_dataframe(self):
|
def test_calculate_pd_dataframe(self):
|
||||||
single_results = [
|
single_results = [
|
||||||
self.perfect_classifier_result, self.random_classifier_result
|
self.perfect_classifier_result, self.random_classifier_result
|
||||||
|
@ -838,7 +1064,11 @@ class AttackResultsTest(absltest.TestCase):
|
||||||
'attack type': ['THRESHOLD_ATTACK', 'THRESHOLD_ATTACK'],
|
'attack type': ['THRESHOLD_ATTACK', 'THRESHOLD_ATTACK'],
|
||||||
'Attacker advantage': [1.0, 0.0],
|
'Attacker advantage': [1.0, 0.0],
|
||||||
'Positive predictive value': [1.0, 0.5],
|
'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)
|
pd.testing.assert_frame_equal(df, df_expected)
|
||||||
|
|
||||||
|
|
|
@ -24,12 +24,16 @@ import numpy as np
|
||||||
from scipy import special
|
from scipy import special
|
||||||
from sklearn import metrics
|
from sklearn import metrics
|
||||||
from sklearn import model_selection
|
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 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 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 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 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 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 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 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 RocCurve
|
||||||
|
@ -135,13 +139,23 @@ def _run_trained_attack(
|
||||||
test_train_ratio=test_train_ratio)
|
test_train_ratio=test_train_ratio)
|
||||||
|
|
||||||
in_train_indices = labels == 1
|
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(
|
return SingleAttackResult(
|
||||||
slice_spec=_get_slice_spec(attack_input),
|
slice_spec=_get_slice_spec(attack_input),
|
||||||
data_size=prepared_attacker_data.data_size,
|
data_size=prepared_attacker_data.data_size,
|
||||||
attack_type=attack_type,
|
attack_type=attack_type,
|
||||||
membership_scores_train=scores[in_train_indices],
|
membership_scores_train=scores[in_train_indices],
|
||||||
membership_scores_test=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):
|
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
|
thresholds=-thresholds, # negate because we negated the loss
|
||||||
test_train_ratio=test_train_ratio,
|
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(
|
return SingleAttackResult(
|
||||||
slice_spec=_get_slice_spec(attack_input),
|
slice_spec=_get_slice_spec(attack_input),
|
||||||
|
@ -180,7 +202,9 @@ def _run_threshold_attack(attack_input: AttackInputData):
|
||||||
attack_type=AttackType.THRESHOLD_ATTACK,
|
attack_type=AttackType.THRESHOLD_ATTACK,
|
||||||
membership_scores_train=attack_input.get_loss_train(),
|
membership_scores_train=attack_input.get_loss_train(),
|
||||||
membership_scores_test=attack_input.get_loss_test(),
|
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):
|
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
|
thresholds=-thresholds, # negate because we negated the loss
|
||||||
test_train_ratio=test_train_ratio,
|
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(
|
return SingleAttackResult(
|
||||||
slice_spec=_get_slice_spec(attack_input),
|
slice_spec=_get_slice_spec(attack_input),
|
||||||
data_size=DataSize(ntrain=ntrain, ntest=ntest),
|
data_size=DataSize(ntrain=ntrain, ntest=ntest),
|
||||||
attack_type=AttackType.THRESHOLD_ENTROPY_ATTACK,
|
attack_type=AttackType.THRESHOLD_ENTROPY_ATTACK,
|
||||||
membership_scores_train=-attack_input.get_entropy_train(),
|
membership_scores_train=-attack_input.get_entropy_train(),
|
||||||
membership_scores_test=-attack_input.get_entropy_test(),
|
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,
|
def _run_attack(attack_input: AttackInputData,
|
||||||
|
|
|
@ -17,7 +17,9 @@ from unittest import mock
|
||||||
from absl.testing import absltest
|
from absl.testing import absltest
|
||||||
from absl.testing import parameterized
|
from absl.testing import parameterized
|
||||||
import numpy as np
|
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 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 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 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 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))
|
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):
|
class RunAttacksTest(parameterized.TestCase):
|
||||||
|
|
||||||
def test_run_attacks_size(self):
|
def test_run_attacks_size(self):
|
||||||
|
@ -285,6 +302,117 @@ class RunAttacksTest(parameterized.TestCase):
|
||||||
# namely 0.5.
|
# namely 0.5.
|
||||||
np.testing.assert_almost_equal(result.roc_curve.get_ppv(), 0.5, decimal=2)
|
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):
|
def test_run_attack_by_slice(self):
|
||||||
result = mia.run_attacks(
|
result = mia.run_attacks(
|
||||||
get_test_input(100, 100), SlicingSpec(by_class=True),
|
get_test_input(100, 100), SlicingSpec(by_class=True),
|
||||||
|
|
|
@ -14,12 +14,13 @@
|
||||||
|
|
||||||
from absl.testing import absltest
|
from absl.testing import absltest
|
||||||
import numpy as np
|
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 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 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 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 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 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 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 RocCurve
|
||||||
from tensorflow_privacy.privacy.privacy_tests.membership_inference_attack.data_structures import SingleAttackResult
|
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]),
|
tpr=np.array([0.0, 0.5, 1.0]),
|
||||||
fpr=np.array([0.0, 0.5, 1.0]),
|
fpr=np.array([0.0, 0.5, 1.0]),
|
||||||
thresholds=np.array([0, 1, 2]),
|
thresholds=np.array([0, 1, 2]),
|
||||||
test_train_ratio=1.0),
|
test_train_ratio=1.0,
|
||||||
data_size=DataSize(ntrain=1, ntest=1))
|
),
|
||||||
|
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.
|
# Classifier that achieves an AUC of 1.0.
|
||||||
self.perfect_classifier_result = SingleAttackResult(
|
self.perfect_classifier_result = SingleAttackResult(
|
||||||
|
@ -50,8 +59,16 @@ class PrivacyReportTest(absltest.TestCase):
|
||||||
tpr=np.array([0.0, 1.0, 1.0]),
|
tpr=np.array([0.0, 1.0, 1.0]),
|
||||||
fpr=np.array([1.0, 1.0, 0.0]),
|
fpr=np.array([1.0, 1.0, 0.0]),
|
||||||
thresholds=np.array([0, 1, 2]),
|
thresholds=np.array([0, 1, 2]),
|
||||||
test_train_ratio=1.0),
|
test_train_ratio=1.0,
|
||||||
data_size=DataSize(ntrain=1, ntest=1))
|
),
|
||||||
|
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(
|
self.results_epoch_0 = AttackResults(
|
||||||
single_attack_results=[self.imperfect_classifier_result],
|
single_attack_results=[self.imperfect_classifier_result],
|
||||||
|
|
Loading…
Reference in a new issue