From 3f2447e2627438cf35e3f1e8aca45e60b4c14859 Mon Sep 17 00:00:00 2001 From: Matthew Jagielski Date: Mon, 15 Feb 2021 19:27:18 -0500 Subject: [PATCH 1/3] add auditing code --- research/audit_2020/README.md | 11 ++ .../__pycache__/attacks.cpython-37.pyc | Bin 0 -> 3200 bytes .../__pycache__/audit.cpython-37.pyc | Bin 0 -> 3459 bytes research/audit_2020/attacks.py | 115 +++++++++++ research/audit_2020/audit.py | 123 ++++++++++++ research/audit_2020/audit_test.py | 91 +++++++++ research/audit_2020/fmnist_audit.py | 180 ++++++++++++++++++ research/audit_2020/mean_audit.py | 156 +++++++++++++++ 8 files changed, 676 insertions(+) create mode 100644 research/audit_2020/README.md create mode 100644 research/audit_2020/__pycache__/attacks.cpython-37.pyc create mode 100644 research/audit_2020/__pycache__/audit.cpython-37.pyc create mode 100644 research/audit_2020/attacks.py create mode 100644 research/audit_2020/audit.py create mode 100644 research/audit_2020/audit_test.py create mode 100644 research/audit_2020/fmnist_audit.py create mode 100644 research/audit_2020/mean_audit.py diff --git a/research/audit_2020/README.md b/research/audit_2020/README.md new file mode 100644 index 0000000..56f584d --- /dev/null +++ b/research/audit_2020/README.md @@ -0,0 +1,11 @@ +# Auditing Private Machine Learning +Code for "Auditing Differentially Private Machine Learning: How Private is Private SGD?": https://arxiv.org/abs/2006.07709. This implementation is simple but not easily parallelizable. For a parallelizable version which is harder to run, see https://github.com/jagielski/auditing-dpsgd. + +## Usage +This attack relies on the AuditAttack class found in audit.py. The class allows one to generate poisoning, run trials to compute membership scores for the poisoning, and then use the resulting membership scores to compute a lower bound on epsilon. + +## Examples +Two examples are provided, mean_audit.py and fmnist_audit.py. fmnist_audit.py attacks the FashionMNIST dataset. It allows the user to specify between standard backdoor attacks and clipping-aware attacks, and also allows the user to specify between multiple poisoning attack sizes, model types, and whether to load saved model weights to start training from. mean_audit.py audits a model which computes the mean of a dataset. This provides an example of user-provided poisoning samples, rather than those autogenerated from our attacks.py library. + +## Requirements +Requires scikit-learn=0.24.1, statsmodels=0.12.2, tensorflow=1.14.0 diff --git a/research/audit_2020/__pycache__/attacks.cpython-37.pyc b/research/audit_2020/__pycache__/attacks.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2be6388fd067ae4fb25a785ab9c0301da9b739c7 GIT binary patch literal 3200 zcmd^B&5z_p6)%_FZnyhmHj~N6K!{9$D4m4q>>M}@qZJ|HfD}d{5ds+^r^e-;@s2;5 zD)(;ps(i_A1RNqbaYQpm{v}_zut=P^1TOG<_I&j23OI1YGxhq_tLJjntM`7t`u=$A zGd#bWy!Gi{Cyf1xI#-Vk<$GxQ6hg9)NlWs~%6Z5w(rxH=Xt%lp-3gsm?`6Hv#kiaG zb1(E-TR$7*e&|ELB?r=fWQ9XHl%q#19LeY8L~g+{mc5VJbo;^UAC{>ui?lciL{*9S zEXdMhCDb~YmnslTnN}FvdyG}Fsnu+WV_jy;Dv8p3QK||iPp0=$ot6dk$wH+?70s7L zT)}b#ll|d)@8W0sgYqQRRT_VkoTx;T|6__qOdF?X3$2evef=dw#SFV^Q5ZhwyX>L$ z8}=(UMy8}bt9(=(sZ~(^Y=BNi4_f21N`um*Ev9b%R z_OXhg@u3Yn4RT}#7nT|BvX{tXv}viw*S*m$t0!20A8Q|*$=t@F_z(W>2SKHZXmt?8 zSt1H_MB1LwJQ39r@e95k)@WqmI-<6 z9|SsyVgI)-k1d@1QB0B^`gPErN7J_;3_C|PoLIkZ!-`<>qsRZmgAD8An-mJD){6S8 zsO|re{o`k|pXfyCS&kGxP43?nCn+|6md@TU<7J){m7Y~|r0%_Fp=NE)&!{Kj^$e3H zLdB<@Iof|`|D9P^D0*+PZoI=x$x~fqBu(zk+?5ZvUix3vB>lt zD4p*!+xr;r=^Y5#UYGT|Od`83ENsy}QWOU@fS7VcRnzn?x2lFTGQJjo6{e_^zC{W} ztbHy0Gs+4v@p7?>(kfBV9kELFblf-@>QX%i*M=91#uHf$-5d+qdNY9QaI8c zx!Cix3Wsrt>Mtm8k#foG3Uz|H!|_5TGL0()vOE@@IV~DHStrx2rq?EQ_p@&L*?v@% zDi6J?1e}PIr0LHy1UD%{my~D~+KX5;y&}0Ets0)GW>_q9#8zNsI`lHten&UGJS`%e zMweaDI`qoZsI`SdXjkSoP*Y2dC@vJLX6vcL+J#ZS2EqJoYh-!W%iOajmd_{Lw_c=X z`LIo_30D;2n3FFaf-Z{0duVzNQ31TpIS$h%@R5L#npf-`KxH^U%)SN>TXF-RLx79{ zoYW4$1~zCt_yfKGBhI;t832VZG1y%IhHIP_ES&#iu;|LdL$Hsgef0u<8&`=!mU-jG zr)3%^U0OSFxmc^OVqDSr!uJ)@zXlQd9ezb=Aw!o?Em|q6WkpG#zCq&2>T1Sq${d3# zWm&54!b4LkvEdGq!?Qdj$;(_#VQKsu6o{#M=%Jbn9W!Z`Sl2R?=x_r=^1y~U{3GhD` zsmj)Y&dU1-n5#O4P24L}smYHhCtqe&3N8$QyO|~$`>$;JKeFhvuM_pQ`J~eai9W!! z3d~r}H<^KVw5tL1WZZ06NWe)TqbP8Y4XSmL(Vo%~01P0Z(T$e>ltl`epSe6#>iSkk= rSxc@r2hlENV_z*YwLIn>!he}1cZmD-HiW}RAg~eWi=_XW_u{_*sV#H7 literal 0 HcmV?d00001 diff --git a/research/audit_2020/__pycache__/audit.cpython-37.pyc b/research/audit_2020/__pycache__/audit.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9924807dbea6c7e108d1ed74e2e36bedfa4c2908 GIT binary patch literal 3459 zcmaJ@TaVku73Po>MNyaCIBwRB(Sp6Ct&*+Q-nw~M7|z9R4D>+_5cC4p1;ZsrTJBP$ zJTtOa0`+90=!<_q`cQa*zT^k=&+s(`3ZoA}--^B@-x+G9-GxyJGdy$V%;ns^Gaq-m zo`K)rdhdPyn`?&g6%EdRHi!?=OXeGVGOVp? zzL@2CnveZxmZXY^sVHKe%mg32!nKS(tG1#;S!6TC!*nt&gaXS=(kH1*iyU-sD$-nq zqgfs+aE4%c*n}i8{MTQ7t#0}aGzaCl>?LL}(j;^#5cH)W~%6Q32qhveA8Cy1T zr?kiTf@_w5Pp)X^D4QNd2Yy`S zqcq`p%>8L3q6t@AV0NwOfuD~2A$H&Or8Y%vh$zc8GIH0)ZMi=~8>dfrzL~;S!@e~X zFs7lqexow;Y30oF^zn>WjYx=SUhTxiWJ)2mGGqwjg)m-aO`_9&uj==ZakMa+2;nDB{_K!|r=(g!Fh4PQ{+iuss?fKK2k2 z+PXD6+`qMdt0o82d9`(J9Z1bGCEuo0q0aDJwrx75$2@AxWgRq+^_bYf>^jFf7&vG; z$Nmlid1ap1FO4%}*(lkXb`(1{%SLG&Tct(t^TI~vG?6F7cS?4NkBlpuiFLx{AIer@ zpESfH0ubH_*=WZEJ@4&DG=wr&_YlZX$L_yR?k%FIKD>`s``7HSj!phVGq;(I z@ip@re6HnU$gO`Cs1MQP9dz6X7)pqlu)s>p!~*!+3E)0)P!1YNGjYLjP$1e#2b^Z& zQ6Z?>9}(<-q*N52EI!c#AF+&)A5)1K_z!u`MWld-wT37WyU2E=JSvCWl#M7G7a~%jehi{OWsG5-$zG*XCf3sY79Vagt#zzgdVPAF1`SI0E$E&o*v*6rMfhYc%)E| z2*c{h`Gv_WI;?@y_j7<5&cBs*TE5^oLmxNcgrM>$ zoz_sT*A4r2)zBBKKl7@m@3-EnT15ct15sP@Y$SdNoyCu+`!RKw9bdU&2>nzTeu_y+ z*=jU<%wdb`7o0LU7kWi;a>=yo8cM*RjAIxQIZK($#xOMH^9w5X=HktVTy5OvCy|N< zeKT-syHw3=Kgky&VIbPU+G^W13fm(}dc^<#~6xK;~IT%znS--MLQ<(A{ zXjC;sl#hAkMAIqH6CEr4uC{XJp`3?Gq){e=y6BzWUJ>(i;&lI&M!gQ!=-rd^m`fRn`Cur1j$5H{XpAScU%5-{f)>A^ zj!JId+vJ0IkGQv~Td%6qV&jA6s-$1FwgsC_u~JewF)R;O-8OqB75Bx~72Eci_!)%y zmLTXK)&rLSg&;5tD=!T3pqwEP!m1O7k7rS~nrVh%Qp8~>eu|YBRG|uV;YvD!O+hBrbU>^dmVZSF1@TG%CGXiUzKq)K;{{>}y)j%ju=en8(Z%7p> zS-*J)n$|^Fyi3w#RbA~Wr#L+3vC=2K?sAoTw^s8WnOjm_rhb%7 literal 0 HcmV?d00001 diff --git a/research/audit_2020/attacks.py b/research/audit_2020/attacks.py new file mode 100644 index 0000000..d75ab53 --- /dev/null +++ b/research/audit_2020/attacks.py @@ -0,0 +1,115 @@ +# Copyright 2020 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================= +"""Poisoning attack library for auditing.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import numpy as np +from sklearn.decomposition import PCA +from sklearn.linear_model import LogisticRegression + +def make_clip_aware(trn_x, trn_y, l2_norm=10): + """ + trn_x: clean training features - must be shape (n_samples, n_features) + trn_y: clean training labels - must be shape (n_samples, ) + + Returns x, y1, y2 + x: poisoning sample + y1: first corresponding y value + y2: second corresponding y value + """ + x_shape = list(trn_x.shape[1:]) + to_image = lambda x: x.reshape([-1] + x_shape) + flatten = lambda x: x.reshape((x.shape[0], -1)) + assert np.allclose(to_image(flatten(trn_x)), trn_x) + + flat_x = flatten(trn_x) + pca = PCA(flat_x.shape[1]) + pca.fit(flat_x) + + new_x = l2_norm*pca.components_[-1] + + lr = LogisticRegression(max_iter=1000) + lr.fit(flat_x, np.argmax(trn_y, axis=1)) + + num_classes = trn_y.shape[1] + lr_probs = lr.predict_proba(new_x[None, :]) + min_y = np.argmin(lr_probs) + second_y = np.argmin(lr_probs + np.eye(num_classes)[min_y]) + + oh_min_y = np.eye(num_classes)[min_y] + oh_second_y = np.eye(num_classes)[second_y] + + return to_image(new_x), oh_min_y, oh_second_y + +def make_backdoor(trn_x, trn_y): + """ + trn_x: clean training features - must be shape (n_samples, n_features) + trn_y: clean training labels - must be shape (n_samples, ) + + Returns x, y1, y2 + x: poisoning sample + y1: first corresponding y value + y2: second corresponding y value + """ + + sample_ind = np.random.choice(trn_x.shape[0], 1) + pois_x = np.copy(trn_x[sample_ind, :]) + pois_x[0] = 1 # set corner feature to 1 + second_y = trn_y[sample_ind] + + num_classes = trn_y.shape[1] + min_y = np.eye(num_classes)[second_y.argmax(1) + 1] + + return pois_x, min_y, second_y + + +def make_many_pois(trn_x, trn_y, pois_sizes, attack="clip_aware", l2_norm=10): + """ + Makes a dict containing many poisoned datasets. make_pois is fairly slow: + this avoids making multiple calls + + trn_x: clean training features - shape (n_samples, n_features) + trn_y: clean training labels - shape (n_samples, ) + pois_sizes: list of poisoning sizes + l2_norm: l2 norm of the poisoned data + + Returns dict: all_poisons + all_poisons[poison_size] is a pair of poisoned datasets + """ + if attack == "clip_aware": + pois_sample_x, y, second_y = make_clip_aware(trn_x, trn_y, l2_norm) + elif attack == "backdoor": + pois_sample_x, y, second_y = make_backdoor(trn_x, trn_y) + else: + raise NotImplementedError + all_poisons = {"pois": (pois_sample_x, y)} + + for pois_size in pois_sizes: # make_pois is slow - don't want it in a loop + new_pois_x1, new_pois_y1 = trn_x.copy(), trn_y.copy() + new_pois_x2, new_pois_y2 = trn_x.copy(), trn_y.copy() + + new_pois_x1[-pois_size:] = pois_sample_x[None, :] + new_pois_y1[-pois_size:] = y + + new_pois_x2[-pois_size:] = pois_sample_x[None, :] + new_pois_y2[-pois_size:] = second_y + + dataset1, dataset2 = (new_pois_x1, new_pois_y1), (new_pois_x2, new_pois_y2) + all_poisons[pois_size] = dataset1, dataset2 + + return all_poisons diff --git a/research/audit_2020/audit.py b/research/audit_2020/audit.py new file mode 100644 index 0000000..c37ba57 --- /dev/null +++ b/research/audit_2020/audit.py @@ -0,0 +1,123 @@ +# Copyright 2020 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================= +"""Class for running auditing procedure.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import numpy as np +from statsmodels.stats import proportion + +import attacks + +def compute_results(poison_scores, unpois_scores, pois_ct, + alpha=0.05, threshold=None): + """ + Searches over thresholds for the best epsilon lower bound and accuracy. + poison_scores: list of scores from poisoned models + unpois_scores: list of scores from unpoisoned models + pois_ct: number of poison points + alpha: confidence parameter + threshold: if None, search over all thresholds, else use given threshold + """ + if threshold is None: # search for best threshold + all_thresholds = np.unique(poison_scores + unpois_scores) + else: + all_thresholds = [threshold] + + poison_arr = np.array(poison_scores) + unpois_arr = np.array(unpois_scores) + + best_threshold, best_epsilon, best_acc = None, 0, 0 + for thresh in all_thresholds: + epsilon, acc = compute_epsilon_and_acc(poison_arr, unpois_arr, thresh, + alpha, pois_ct) + if epsilon > best_epsilon: + best_epsilon, best_threshold = epsilon, thresh + best_acc = max(best_acc, acc) + return best_threshold, best_epsilon, best_acc + + +def compute_epsilon_and_acc(poison_arr, unpois_arr, threshold, alpha, pois_ct): + """For a given threshold, compute epsilon and accuracy.""" + poison_ct = (poison_arr > threshold).sum() + unpois_ct = (unpois_arr > threshold).sum() + + # clopper_pearson uses alpha/2 budget on upper and lower + # so total budget will be 2*alpha/2 = alpha + p1, _ = proportion.proportion_confint(poison_ct, poison_arr.size, + alpha, method='beta') + _, p0 = proportion.proportion_confint(unpois_ct, unpois_arr.size, + alpha, method='beta') + + if (p1 <= 1e-5) or (p0 >= 1 - 1e-5): # divide by zero issues + return 0, 0 + + if (p0 + p1) > 1: # see Appendix A + p0, p1 = (1-p1), (1-p0) + + epsilon = np.log(p1/p0)/pois_ct + acc = (p1 + (1-p0))/2 # this is not necessarily the best accuracy + + return epsilon, acc + + +class AuditAttack(object): + """Audit attack class. Generates poisoning, then runs auditing algorithm.""" + def __init__(self, trn_x, trn_y, train_function): + """ + trn_x: training features + trn_y: training labels + name: identifier for the attack + train_function: function returning membership score + """ + self.trn_x, self.trn_y = trn_x, trn_y + self.train_function = train_function + self.poisoning = None + + def make_poisoning(self, pois_ct, attack_type, l2_norm=10): + """Get poisoning data.""" + return attacks.make_many_pois(self.trn_x, self.trn_y, [pois_ct], + attack=attack_type, l2_norm=l2_norm) + + def run_experiments(self, num_trials): + """Uses multiprocessing to run all training experiments.""" + (pois_x1, pois_y1), (pois_x2, pois_y2) = self.poisoning['data'] + sample_x, sample_y = self.poisoning['pois'] + + poison_scores = [] + unpois_scores = [] + + for i in range(num_trials): + poison_tuple = (pois_x1, pois_y1, sample_x, sample_y, i) + unpois_tuple = (pois_x2, pois_y2, sample_x, sample_y, num_trials + i) + poison_scores.append(self.train_function(poison_tuple)) + unpois_scores.append(self.train_function(unpois_tuple)) + + return poison_scores, unpois_scores + + def run(self, pois_ct, attack_type, num_trials, alpha=0.05, + threshold=None, l2_norm=10): + """Complete auditing algorithm. Generates poisoning if necessary.""" + if self.poisoning is None: + self.poisoning = self.make_poisoning(pois_ct, attack_type, l2_norm) + self.poisoning['data'] = self.poisoning[pois_ct] + + poison_scores, unpois_scores = self.run_experiments(num_trials) + + results = compute_results(poison_scores, unpois_scores, pois_ct, + alpha=alpha, threshold=threshold) + return results diff --git a/research/audit_2020/audit_test.py b/research/audit_2020/audit_test.py new file mode 100644 index 0000000..2abdee3 --- /dev/null +++ b/research/audit_2020/audit_test.py @@ -0,0 +1,91 @@ +# Copyright 2020, The TensorFlow Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Lint as: python3 +"""Tests for audit.py.""" + +from absl.testing import absltest +from absl.testing import parameterized +import numpy as np +import audit + +def dummy_train_and_score_function(dataset): + del dataset + return 0 + +def get_auditor(): + poisoning = {} + datasets = (np.zeros((5, 2)), np.zeros(5)), (np.zeros((5, 2)), np.zeros(5)) + poisoning["data"] = datasets + poisoning["pois"] = (datasets[0][0][0], datasets[0][1][0]) + auditor = audit.AuditAttack(datasets[0][0], datasets[0][1], + dummy_train_and_score_function) + auditor.poisoning = poisoning + + return auditor + + +class AuditParameterizedTest(parameterized.TestCase): + """Class to test parameterized audit.py functions.""" + @parameterized.named_parameters( + ('Test0', np.ones(500), np.zeros(500), 0.5, 0.01, 1, + (4.541915810224092, 0.9894593118113243)), + ('Test1', np.ones(500), np.zeros(500), 0.5, 0.01, 2, + (2.27095790511, 0.9894593118113243)), + ('Test2', np.ones(500), np.ones(500), 0.5, 0.01, 1, + (0, 0)) + ) + + def test_compute_epsilon_and_acc(self, poison_scores, unpois_scores, + threshold, pois_ct, alpha, expected_res): + expected_eps, expected_acc = expected_res + computed_res = audit.compute_epsilon_and_acc(poison_scores, unpois_scores, + threshold, pois_ct, alpha) + computed_eps, computed_acc = computed_res + self.assertAlmostEqual(computed_eps, expected_eps) + self.assertAlmostEqual(computed_acc, expected_acc) + + @parameterized.named_parameters( + ('Test0', [1]*500, [0]*250 + [.5]*250, 1, 0.01, .5, + (.5, 4.541915810224092, 0.9894593118113243)), + ('Test1', [1]*500, [0]*250 + [.5]*250, 1, 0.01, None, + (.5, 4.541915810224092, 0.9894593118113243)), + ('Test2', [1]*500, [0]*500, 2, 0.01, .5, + (.5, 2.27095790511, 0.9894593118113243)), + ) + + def test_compute_results(self, poison_scores, unpois_scores, pois_ct, + alpha, threshold, expected_res): + expected_thresh, expected_eps, expected_acc = expected_res + computed_res = audit.compute_results(poison_scores, unpois_scores, + pois_ct, alpha, threshold) + computed_thresh, computed_eps, computed_acc = computed_res + self.assertAlmostEqual(computed_thresh, expected_thresh) + self.assertAlmostEqual(computed_eps, expected_eps) + self.assertAlmostEqual(computed_acc, expected_acc) + + +class AuditAttackTest(absltest.TestCase): + """Nonparameterized audit.py test class.""" + def test_run_experiments(self): + auditor = get_auditor() + pois, unpois = auditor.run_experiments(100) + expected = [0 for _ in range(100)] + self.assertListEqual(pois, expected) + self.assertListEqual(unpois, expected) + + + +if __name__ == '__main__': + absltest.main() diff --git a/research/audit_2020/fmnist_audit.py b/research/audit_2020/fmnist_audit.py new file mode 100644 index 0000000..8274a32 --- /dev/null +++ b/research/audit_2020/fmnist_audit.py @@ -0,0 +1,180 @@ +# Copyright 2020 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================= +"""Run auditing on the FashionMNIST dataset.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import numpy as np +import tensorflow.compat.v1 as tf + +from tensorflow_privacy.privacy.analysis.rdp_accountant import compute_rdp +from tensorflow_privacy.privacy.analysis.rdp_accountant import get_privacy_spent +from tensorflow_privacy.privacy.optimizers import dp_optimizer_vectorized + +from absl import app +from absl import flags + +import audit + +#### FLAGS +FLAGS = flags.FLAGS +flags.DEFINE_float('learning_rate', 0.15, 'Learning rate for training') +flags.DEFINE_float('noise_multiplier', 1.1, + 'Ratio of the standard deviation to the clipping norm') +flags.DEFINE_float('l2_norm_clip', 1.0, 'Clipping norm') +flags.DEFINE_integer('batch_size', 250, 'Batch size') +flags.DEFINE_integer('epochs', 24, 'Number of epochs') +flags.DEFINE_integer( + 'microbatches', 250, 'Number of microbatches ' + '(must evenly divide batch_size)') +flags.DEFINE_string('model', 'lr', 'model to use, pick between lr and nn') +flags.DEFINE_string('attack_type', "clip_aware", 'clip_aware or backdoor') +flags.DEFINE_integer('pois_ct', 1, 'Number of poisoning points') +flags.DEFINE_integer('num_trials', 100, 'Number of trials for auditing') +flags.DEFINE_float('attack_l2_norm', 10, 'Size of poisoning data') +flags.DEFINE_float('alpha', 0.05, '1-confidence') +flags.DEFINE_boolean('load_weights', False, + 'if True, use weights saved in init_weights.h5') +FLAGS = flags.FLAGS + + +def compute_epsilon(train_size): + """Computes epsilon value for given hyperparameters.""" + if FLAGS.noise_multiplier == 0.0: + return float('inf') + orders = [1 + x / 10. for x in range(1, 100)] + list(range(12, 64)) + sampling_probability = FLAGS.batch_size / train_size + steps = FLAGS.epochs * train_size / FLAGS.batch_size + rdp = compute_rdp(q=sampling_probability, + noise_multiplier=FLAGS.noise_multiplier, + steps=steps, + orders=orders) + # Delta is set to approximate 1 / (number of training points). + return get_privacy_spent(orders, rdp, target_delta=1e-5)[0] + +def build_model(x, y): + """Build a keras model.""" + input_shape = x.shape[1:] + num_classes = y.shape[1] + l2 = 0 + if FLAGS.model == 'lr': + model = tf.keras.Sequential([ + tf.keras.layers.Flatten(input_shape=input_shape), + tf.keras.layers.Dense(num_classes, kernel_initializer='glorot_normal', + kernel_regularizer=tf.keras.regularizers.l2(l2)) + ]) + elif FLAGS.model == 'nn': + model = tf.keras.Sequential([ + tf.keras.layers.Flatten(input_shape=input_shape), + tf.keras.layers.Dense(32, activation='relu', + kernel_initializer='glorot_normal', + kernel_regularizer=tf.keras.regularizers.l2(l2)), + tf.keras.layers.Dense(num_classes, kernel_initializer='glorot_normal', + kernel_regularizer=tf.keras.regularizers.l2(l2)) + ]) + else: + raise NotImplementedError + return model + + +def train_model(model, train_x, train_y, save_weights=False): + """Train the model on given data.""" + optimizer = dp_optimizer_vectorized.VectorizedDPSGD( + l2_norm_clip=FLAGS.l2_norm_clip, + noise_multiplier=FLAGS.noise_multiplier, + num_microbatches=FLAGS.microbatches, + learning_rate=FLAGS.learning_rate) + + loss = tf.keras.losses.CategoricalCrossentropy( + from_logits=True, reduction=tf.losses.Reduction.NONE) + + # Compile model with Keras + model.compile(optimizer=optimizer, loss=loss, metrics=['accuracy']) + + if save_weights: + wts = model.get_weights() + np.save('save_model', wts) + model.set_weights(wts) + return model + + if FLAGS.load_weights: # load preset weights + wts = np.load('save_model.npy', allow_pickle=True).tolist() + model.set_weights(wts) + + # Train model with Keras + model.fit(train_x, train_y, + epochs=FLAGS.epochs, + validation_data=(train_x, train_y), + batch_size=FLAGS.batch_size, + verbose=0) + return model + + +def membership_test(model, pois_x, pois_y): + """Membership inference - detect poisoning.""" + probs = model.predict(np.concatenate([pois_x, np.zeros_like(pois_x)])) + return np.multiply(probs[0, :] - probs[1, :], pois_y).sum() + + +def train_and_score(dataset): + """Complete training run with membership inference score.""" + x, y, pois_x, pois_y, i = dataset + np.random.seed(i) + tf.set_random_seed(i) + tf.reset_default_graph() + model = build_model(x, y) + model = train_model(model, x, y) + return membership_test(model, pois_x, pois_y) + + +def main(unused_argv): + del unused_argv + # Load training and test data. + np.random.seed(0) + + (trn_x, trn_y), _ = tf.keras.datasets.fashion_mnist.load_data() + trn_inds = np.where(trn_y < 2)[0] + + trn_x = -.5 + trn_x[trn_inds] / 255. + trn_y = np.eye(2)[trn_y[trn_inds]] + + # subsample dataset + ss_inds = np.random.choice(trn_x.shape[0], trn_x.shape[0]//2, replace=False) + trn_x = trn_x[ss_inds] + trn_y = trn_y[ss_inds] + + init_model = build_model(trn_x, trn_y) + _ = train_model(init_model, trn_x, trn_y, save_weights=True) + + auditor = audit.AuditAttack(trn_x, trn_y, train_and_score) + + thresh, _, _ = auditor.run(FLAGS.pois_ct, FLAGS.attack_type, FLAGS.num_trials, + alpha=FLAGS.alpha, threshold=None, + l2_norm=FLAGS.attack_l2_norm) + + _, eps, acc = auditor.run(FLAGS.pois_ct, FLAGS.attack_type, FLAGS.num_trials, + alpha=FLAGS.alpha, threshold=thresh, + l2_norm=FLAGS.attack_l2_norm) + + epsilon_ub = compute_epsilon(trn_x.shape[0]) + + print("Analysis epsilon is {}.".format(epsilon_ub)) + print("At threshold={}, epsilon={}.".format(thresh, eps)) + print("The best accuracy at distinguishing poisoning is {}.".format(acc)) + +if __name__ == '__main__': + app.run(main) diff --git a/research/audit_2020/mean_audit.py b/research/audit_2020/mean_audit.py new file mode 100644 index 0000000..752cebd --- /dev/null +++ b/research/audit_2020/mean_audit.py @@ -0,0 +1,156 @@ +# Copyright 2020 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================= +"""Auditing a model which computes the mean of a synthetic dataset. + This gives an example for instrumenting the auditor to audit a user-given sample.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import numpy as np +import tensorflow.compat.v1 as tf + +from tensorflow_privacy.privacy.analysis.rdp_accountant import compute_rdp +from tensorflow_privacy.privacy.analysis.rdp_accountant import get_privacy_spent +from tensorflow_privacy.privacy.optimizers import dp_optimizer_vectorized + + +from absl import app +from absl import flags + +import audit + +#### FLAGS +FLAGS = flags.FLAGS +flags.DEFINE_float('learning_rate', 0.15, 'Learning rate for training') +flags.DEFINE_float('noise_multiplier', 1.1, + 'Ratio of the standard deviation to the clipping norm') +flags.DEFINE_float('l2_norm_clip', 1.0, 'Clipping norm') +flags.DEFINE_integer('batch_size', 250, 'Batch size') +flags.DEFINE_integer('d', 250, 'Data dimension') +flags.DEFINE_integer('epochs', 1, 'Number of epochs') +flags.DEFINE_integer( + 'microbatches', 250, 'Number of microbatches ' + '(must evenly divide batch_size)') +flags.DEFINE_string('attack_type', "clip_aware", 'clip_aware or backdoor') +flags.DEFINE_integer('num_trials', 100, 'Number of trials for auditing') +flags.DEFINE_float('attack_l2_norm', 10, 'Size of poisoning data') +flags.DEFINE_float('alpha', 0.05, '1-confidence') +FLAGS = flags.FLAGS + + +def compute_epsilon(train_size): + """Computes epsilon value for given hyperparameters.""" + if FLAGS.noise_multiplier == 0.0: + return float('inf') + orders = [1 + x / 10. for x in range(1, 100)] + list(range(12, 64)) + sampling_probability = FLAGS.batch_size / train_size + steps = FLAGS.epochs * train_size / FLAGS.batch_size + rdp = compute_rdp(q=sampling_probability, + noise_multiplier=FLAGS.noise_multiplier, + steps=steps, + orders=orders) + # Delta is set to approximate 1 / (number of training points). + return get_privacy_spent(orders, rdp, target_delta=1e-5)[0] + +def build_model(x, y): + del x, y + model = tf.keras.Sequential([tf.keras.layers.Dense( + 1, input_shape=(FLAGS.d,), + use_bias=False, kernel_initializer=tf.keras.initializers.Zeros())]) + return model + + +def train_model(model, train_x, train_y): + """Train the model on given data.""" + optimizer = dp_optimizer_vectorized.VectorizedDPSGD( + l2_norm_clip=FLAGS.l2_norm_clip, + noise_multiplier=FLAGS.noise_multiplier, + num_microbatches=FLAGS.microbatches, + learning_rate=FLAGS.learning_rate) + + # gradient of (.5-x.w)^2 is 2(.5-x.w)x + loss = tf.keras.losses.MeanSquaredError(reduction=tf.losses.Reduction.NONE) + + # Compile model with Keras + model.compile(optimizer=optimizer, loss=loss, metrics=['mse']) + + # Train model with Keras + model.fit(train_x, train_y, + epochs=FLAGS.epochs, + validation_data=(train_x, train_y), + batch_size=FLAGS.batch_size, + verbose=0) + return model + + +def membership_test(model, pois_x, pois_y): + """Membership inference - detect poisoning.""" + del pois_y + return model.predict(pois_x) + + +def gen_data(n, d): + """Make binomial dataset.""" + x = np.random.normal(size=(n, d)) + y = np.ones(shape=(n,))/2. + return x, y + + +def train_and_score(dataset): + """Complete training run with membership inference score.""" + x, y, pois_x, pois_y, i = dataset + np.random.seed(i) + tf.set_random_seed(i) + model = build_model(x, y) + model = train_model(model, x, y) + return membership_test(model, pois_x, pois_y) + + +def main(unused_argv): + del unused_argv + # Load training and test data. + np.random.seed(0) + + x, y = gen_data(1 + FLAGS.batch_size, FLAGS.d) + + auditor = audit.AuditAttack(x, y, train_and_score) + + # we will instrument the auditor to simply backdoor the last feature + pois_x1, pois_x2 = x[:-1].copy(), x[:-1].copy() + pois_x1[-1] = x[-1] + pois_y = y[:-1] + target_x = x[-1][None, :] + assert np.unique(np.nonzero(pois_x1 - pois_x2)[0]).size == 1 + + pois_data = (pois_x1, pois_y), (pois_x2, pois_y), (target_x, y[-1]) + poisoning = {} + poisoning["data"] = (pois_data[0], pois_data[1]) + poisoning["pois"] = pois_data[2] + auditor.poisoning = poisoning + + thresh, _, _ = auditor.run(1, None, FLAGS.num_trials, alpha=FLAGS.alpha) + + _, eps, acc = auditor.run(1, None, FLAGS.num_trials, alpha=FLAGS.alpha, + threshold=thresh) + + epsilon_ub = compute_epsilon(FLAGS.batch_size) + + print("Analysis epsilon is {}.".format(epsilon_ub)) + print("At threshold={}, epsilon={}.".format(thresh, eps)) + print("The best accuracy at distinguishing poisoning is {}.".format(acc)) + +if __name__ == '__main__': + app.run(main) From f8c2745c8d7a3f9012916fed1feb178258d0313b Mon Sep 17 00:00:00 2001 From: Matthew Jagielski Date: Mon, 15 Feb 2021 19:27:43 -0500 Subject: [PATCH 2/3] delete pycache --- .../__pycache__/attacks.cpython-37.pyc | Bin 3200 -> 0 bytes .../audit_2020/__pycache__/audit.cpython-37.pyc | Bin 3459 -> 0 bytes 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 research/audit_2020/__pycache__/attacks.cpython-37.pyc delete mode 100644 research/audit_2020/__pycache__/audit.cpython-37.pyc diff --git a/research/audit_2020/__pycache__/attacks.cpython-37.pyc b/research/audit_2020/__pycache__/attacks.cpython-37.pyc deleted file mode 100644 index 2be6388fd067ae4fb25a785ab9c0301da9b739c7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3200 zcmd^B&5z_p6)%_FZnyhmHj~N6K!{9$D4m4q>>M}@qZJ|HfD}d{5ds+^r^e-;@s2;5 zD)(;ps(i_A1RNqbaYQpm{v}_zut=P^1TOG<_I&j23OI1YGxhq_tLJjntM`7t`u=$A zGd#bWy!Gi{Cyf1xI#-Vk<$GxQ6hg9)NlWs~%6Z5w(rxH=Xt%lp-3gsm?`6Hv#kiaG zb1(E-TR$7*e&|ELB?r=fWQ9XHl%q#19LeY8L~g+{mc5VJbo;^UAC{>ui?lciL{*9S zEXdMhCDb~YmnslTnN}FvdyG}Fsnu+WV_jy;Dv8p3QK||iPp0=$ot6dk$wH+?70s7L zT)}b#ll|d)@8W0sgYqQRRT_VkoTx;T|6__qOdF?X3$2evef=dw#SFV^Q5ZhwyX>L$ z8}=(UMy8}bt9(=(sZ~(^Y=BNi4_f21N`um*Ev9b%R z_OXhg@u3Yn4RT}#7nT|BvX{tXv}viw*S*m$t0!20A8Q|*$=t@F_z(W>2SKHZXmt?8 zSt1H_MB1LwJQ39r@e95k)@WqmI-<6 z9|SsyVgI)-k1d@1QB0B^`gPErN7J_;3_C|PoLIkZ!-`<>qsRZmgAD8An-mJD){6S8 zsO|re{o`k|pXfyCS&kGxP43?nCn+|6md@TU<7J){m7Y~|r0%_Fp=NE)&!{Kj^$e3H zLdB<@Iof|`|D9P^D0*+PZoI=x$x~fqBu(zk+?5ZvUix3vB>lt zD4p*!+xr;r=^Y5#UYGT|Od`83ENsy}QWOU@fS7VcRnzn?x2lFTGQJjo6{e_^zC{W} ztbHy0Gs+4v@p7?>(kfBV9kELFblf-@>QX%i*M=91#uHf$-5d+qdNY9QaI8c zx!Cix3Wsrt>Mtm8k#foG3Uz|H!|_5TGL0()vOE@@IV~DHStrx2rq?EQ_p@&L*?v@% zDi6J?1e}PIr0LHy1UD%{my~D~+KX5;y&}0Ets0)GW>_q9#8zNsI`lHten&UGJS`%e zMweaDI`qoZsI`SdXjkSoP*Y2dC@vJLX6vcL+J#ZS2EqJoYh-!W%iOajmd_{Lw_c=X z`LIo_30D;2n3FFaf-Z{0duVzNQ31TpIS$h%@R5L#npf-`KxH^U%)SN>TXF-RLx79{ zoYW4$1~zCt_yfKGBhI;t832VZG1y%IhHIP_ES&#iu;|LdL$Hsgef0u<8&`=!mU-jG zr)3%^U0OSFxmc^OVqDSr!uJ)@zXlQd9ezb=Aw!o?Em|q6WkpG#zCq&2>T1Sq${d3# zWm&54!b4LkvEdGq!?Qdj$;(_#VQKsu6o{#M=%Jbn9W!Z`Sl2R?=x_r=^1y~U{3GhD` zsmj)Y&dU1-n5#O4P24L}smYHhCtqe&3N8$QyO|~$`>$;JKeFhvuM_pQ`J~eai9W!! z3d~r}H<^KVw5tL1WZZ06NWe)TqbP8Y4XSmL(Vo%~01P0Z(T$e>ltl`epSe6#>iSkk= rSxc@r2hlENV_z*YwLIn>!he}1cZmD-HiW}RAg~eWi=_XW_u{_*sV#H7 diff --git a/research/audit_2020/__pycache__/audit.cpython-37.pyc b/research/audit_2020/__pycache__/audit.cpython-37.pyc deleted file mode 100644 index 9924807dbea6c7e108d1ed74e2e36bedfa4c2908..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3459 zcmaJ@TaVku73Po>MNyaCIBwRB(Sp6Ct&*+Q-nw~M7|z9R4D>+_5cC4p1;ZsrTJBP$ zJTtOa0`+90=!<_q`cQa*zT^k=&+s(`3ZoA}--^B@-x+G9-GxyJGdy$V%;ns^Gaq-m zo`K)rdhdPyn`?&g6%EdRHi!?=OXeGVGOVp? zzL@2CnveZxmZXY^sVHKe%mg32!nKS(tG1#;S!6TC!*nt&gaXS=(kH1*iyU-sD$-nq zqgfs+aE4%c*n}i8{MTQ7t#0}aGzaCl>?LL}(j;^#5cH)W~%6Q32qhveA8Cy1T zr?kiTf@_w5Pp)X^D4QNd2Yy`S zqcq`p%>8L3q6t@AV0NwOfuD~2A$H&Or8Y%vh$zc8GIH0)ZMi=~8>dfrzL~;S!@e~X zFs7lqexow;Y30oF^zn>WjYx=SUhTxiWJ)2mGGqwjg)m-aO`_9&uj==ZakMa+2;nDB{_K!|r=(g!Fh4PQ{+iuss?fKK2k2 z+PXD6+`qMdt0o82d9`(J9Z1bGCEuo0q0aDJwrx75$2@AxWgRq+^_bYf>^jFf7&vG; z$Nmlid1ap1FO4%}*(lkXb`(1{%SLG&Tct(t^TI~vG?6F7cS?4NkBlpuiFLx{AIer@ zpESfH0ubH_*=WZEJ@4&DG=wr&_YlZX$L_yR?k%FIKD>`s``7HSj!phVGq;(I z@ip@re6HnU$gO`Cs1MQP9dz6X7)pqlu)s>p!~*!+3E)0)P!1YNGjYLjP$1e#2b^Z& zQ6Z?>9}(<-q*N52EI!c#AF+&)A5)1K_z!u`MWld-wT37WyU2E=JSvCWl#M7G7a~%jehi{OWsG5-$zG*XCf3sY79Vagt#zzgdVPAF1`SI0E$E&o*v*6rMfhYc%)E| z2*c{h`Gv_WI;?@y_j7<5&cBs*TE5^oLmxNcgrM>$ zoz_sT*A4r2)zBBKKl7@m@3-EnT15ct15sP@Y$SdNoyCu+`!RKw9bdU&2>nzTeu_y+ z*=jU<%wdb`7o0LU7kWi;a>=yo8cM*RjAIxQIZK($#xOMH^9w5X=HktVTy5OvCy|N< zeKT-syHw3=Kgky&VIbPU+G^W13fm(}dc^<#~6xK;~IT%znS--MLQ<(A{ zXjC;sl#hAkMAIqH6CEr4uC{XJp`3?Gq){e=y6BzWUJ>(i;&lI&M!gQ!=-rd^m`fRn`Cur1j$5H{XpAScU%5-{f)>A^ zj!JId+vJ0IkGQv~Td%6qV&jA6s-$1FwgsC_u~JewF)R;O-8OqB75Bx~72Eci_!)%y zmLTXK)&rLSg&;5tD=!T3pqwEP!m1O7k7rS~nrVh%Qp8~>eu|YBRG|uV;YvD!O+hBrbU>^dmVZSF1@TG%CGXiUzKq)K;{{>}y)j%ju=en8(Z%7p> zS-*J)n$|^Fyi3w#RbA~Wr#L+3vC=2K?sAoTw^s8WnOjm_rhb%7 From 62c51db99c24e469fd3c196b5c476ee26e4cf6cc Mon Sep 17 00:00:00 2001 From: Matthew Jagielski Date: Tue, 19 Oct 2021 15:55:46 -0700 Subject: [PATCH 3/3] fix variable names --- research/audit_2020/attacks.py | 58 ++++++++++++++--------------- research/audit_2020/audit.py | 18 ++++----- research/audit_2020/audit_test.py | 4 +- research/audit_2020/fmnist_audit.py | 30 +++++++-------- research/audit_2020/mean_audit.py | 10 ++--- 5 files changed, 54 insertions(+), 66 deletions(-) diff --git a/research/audit_2020/attacks.py b/research/audit_2020/attacks.py index d75ab53..677fc83 100644 --- a/research/audit_2020/attacks.py +++ b/research/audit_2020/attacks.py @@ -1,4 +1,4 @@ -# Copyright 2020 The TensorFlow Authors. All Rights Reserved. +# Copyright 2021 The TensorFlow Authors. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,39 +14,37 @@ # ============================================================================= """Poisoning attack library for auditing.""" -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - import numpy as np from sklearn.decomposition import PCA from sklearn.linear_model import LogisticRegression -def make_clip_aware(trn_x, trn_y, l2_norm=10): +def make_clip_aware(train_x, train_y, l2_norm=10): """ - trn_x: clean training features - must be shape (n_samples, n_features) - trn_y: clean training labels - must be shape (n_samples, ) + train_x: clean training features - must be shape (n_samples, n_features) + train_y: clean training labels - must be shape (n_samples, ) Returns x, y1, y2 x: poisoning sample y1: first corresponding y value y2: second corresponding y value """ - x_shape = list(trn_x.shape[1:]) - to_image = lambda x: x.reshape([-1] + x_shape) - flatten = lambda x: x.reshape((x.shape[0], -1)) - assert np.allclose(to_image(flatten(trn_x)), trn_x) + x_shape = list(train_x.shape[1:]) + to_image = lambda x: x.reshape([-1] + x_shape) # reshapes to standard image shape + flatten = lambda x: x.reshape((x.shape[0], -1)) # flattens all pixels - allows PCA - flat_x = flatten(trn_x) + # make sure to_image an flatten are inverse functions + assert np.allclose(to_image(flatten(train_x)), train_x) + + flat_x = flatten(train_x) pca = PCA(flat_x.shape[1]) pca.fit(flat_x) new_x = l2_norm*pca.components_[-1] lr = LogisticRegression(max_iter=1000) - lr.fit(flat_x, np.argmax(trn_y, axis=1)) + lr.fit(flat_x, np.argmax(train_y, axis=1)) - num_classes = trn_y.shape[1] + num_classes = train_y.shape[1] lr_probs = lr.predict_proba(new_x[None, :]) min_y = np.argmin(lr_probs) second_y = np.argmin(lr_probs + np.eye(num_classes)[min_y]) @@ -56,10 +54,12 @@ def make_clip_aware(trn_x, trn_y, l2_norm=10): return to_image(new_x), oh_min_y, oh_second_y -def make_backdoor(trn_x, trn_y): +def make_backdoor(train_x, train_y): """ - trn_x: clean training features - must be shape (n_samples, n_features) - trn_y: clean training labels - must be shape (n_samples, ) + Makes a backdoored dataset, following Gu et al. https://arxiv.org/abs/1708.06733 + + train_x: clean training features - must be shape (n_samples, n_features) + train_y: clean training labels - must be shape (n_samples, ) Returns x, y1, y2 x: poisoning sample @@ -67,24 +67,24 @@ def make_backdoor(trn_x, trn_y): y2: second corresponding y value """ - sample_ind = np.random.choice(trn_x.shape[0], 1) - pois_x = np.copy(trn_x[sample_ind, :]) + sample_ind = np.random.choice(train_x.shape[0], 1) + pois_x = np.copy(train_x[sample_ind, :]) pois_x[0] = 1 # set corner feature to 1 - second_y = trn_y[sample_ind] + second_y = train_y[sample_ind] - num_classes = trn_y.shape[1] + num_classes = train_y.shape[1] min_y = np.eye(num_classes)[second_y.argmax(1) + 1] return pois_x, min_y, second_y -def make_many_pois(trn_x, trn_y, pois_sizes, attack="clip_aware", l2_norm=10): +def make_many_poisoned_datasets(train_x, train_y, pois_sizes, attack="clip_aware", l2_norm=10): """ Makes a dict containing many poisoned datasets. make_pois is fairly slow: this avoids making multiple calls - trn_x: clean training features - shape (n_samples, n_features) - trn_y: clean training labels - shape (n_samples, ) + train_x: clean training features - shape (n_samples, n_features) + train_y: clean training labels - shape (n_samples, ) pois_sizes: list of poisoning sizes l2_norm: l2 norm of the poisoned data @@ -92,16 +92,16 @@ def make_many_pois(trn_x, trn_y, pois_sizes, attack="clip_aware", l2_norm=10): all_poisons[poison_size] is a pair of poisoned datasets """ if attack == "clip_aware": - pois_sample_x, y, second_y = make_clip_aware(trn_x, trn_y, l2_norm) + pois_sample_x, y, second_y = make_clip_aware(train_x, train_y, l2_norm) elif attack == "backdoor": - pois_sample_x, y, second_y = make_backdoor(trn_x, trn_y) + pois_sample_x, y, second_y = make_backdoor(train_x, train_y) else: raise NotImplementedError all_poisons = {"pois": (pois_sample_x, y)} for pois_size in pois_sizes: # make_pois is slow - don't want it in a loop - new_pois_x1, new_pois_y1 = trn_x.copy(), trn_y.copy() - new_pois_x2, new_pois_y2 = trn_x.copy(), trn_y.copy() + new_pois_x1, new_pois_y1 = train_x.copy(), train_y.copy() + new_pois_x2, new_pois_y2 = train_x.copy(), train_y.copy() new_pois_x1[-pois_size:] = pois_sample_x[None, :] new_pois_y1[-pois_size:] = y diff --git a/research/audit_2020/audit.py b/research/audit_2020/audit.py index c37ba57..4b19731 100644 --- a/research/audit_2020/audit.py +++ b/research/audit_2020/audit.py @@ -1,4 +1,4 @@ -# Copyright 2020 The TensorFlow Authors. All Rights Reserved. +# Copyright 2021 The TensorFlow Authors. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,10 +14,6 @@ # ============================================================================= """Class for running auditing procedure.""" -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - import numpy as np from statsmodels.stats import proportion @@ -77,24 +73,24 @@ def compute_epsilon_and_acc(poison_arr, unpois_arr, threshold, alpha, pois_ct): class AuditAttack(object): """Audit attack class. Generates poisoning, then runs auditing algorithm.""" - def __init__(self, trn_x, trn_y, train_function): + def __init__(self, train_x, train_y, train_function): """ - trn_x: training features - trn_y: training labels + train_x: training features + train_y: training labels name: identifier for the attack train_function: function returning membership score """ - self.trn_x, self.trn_y = trn_x, trn_y + self.train_x, self.train_y = train_x, train_y self.train_function = train_function self.poisoning = None def make_poisoning(self, pois_ct, attack_type, l2_norm=10): """Get poisoning data.""" - return attacks.make_many_pois(self.trn_x, self.trn_y, [pois_ct], + return attacks.make_many_poisoned_datasets(self.train_x, self.train_y, [pois_ct], attack=attack_type, l2_norm=l2_norm) def run_experiments(self, num_trials): - """Uses multiprocessing to run all training experiments.""" + """Runs all training experiments.""" (pois_x1, pois_y1), (pois_x2, pois_y2) = self.poisoning['data'] sample_x, sample_y = self.poisoning['pois'] diff --git a/research/audit_2020/audit_test.py b/research/audit_2020/audit_test.py index 2abdee3..9d700a5 100644 --- a/research/audit_2020/audit_test.py +++ b/research/audit_2020/audit_test.py @@ -1,4 +1,4 @@ -# Copyright 2020, The TensorFlow Authors. +# Copyright 2021, The TensorFlow Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -81,7 +81,7 @@ class AuditAttackTest(absltest.TestCase): def test_run_experiments(self): auditor = get_auditor() pois, unpois = auditor.run_experiments(100) - expected = [0 for _ in range(100)] + expected = [0]*100 self.assertListEqual(pois, expected) self.assertListEqual(unpois, expected) diff --git a/research/audit_2020/fmnist_audit.py b/research/audit_2020/fmnist_audit.py index 8274a32..976e6a8 100644 --- a/research/audit_2020/fmnist_audit.py +++ b/research/audit_2020/fmnist_audit.py @@ -1,4 +1,4 @@ -# Copyright 2020 The TensorFlow Authors. All Rights Reserved. +# Copyright 2021 The TensorFlow Authors. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,10 +14,6 @@ # ============================================================================= """Run auditing on the FashionMNIST dataset.""" -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - import numpy as np import tensorflow.compat.v1 as tf @@ -146,21 +142,21 @@ def main(unused_argv): # Load training and test data. np.random.seed(0) - (trn_x, trn_y), _ = tf.keras.datasets.fashion_mnist.load_data() - trn_inds = np.where(trn_y < 2)[0] + (train_x, train_y), _ = tf.keras.datasets.fashion_mnist.load_data() + train_inds = np.where(train_y < 2)[0] - trn_x = -.5 + trn_x[trn_inds] / 255. - trn_y = np.eye(2)[trn_y[trn_inds]] + train_x = -.5 + train_x[train_inds] / 255. + train_y = np.eye(2)[train_y[train_inds]] # subsample dataset - ss_inds = np.random.choice(trn_x.shape[0], trn_x.shape[0]//2, replace=False) - trn_x = trn_x[ss_inds] - trn_y = trn_y[ss_inds] + ss_inds = np.random.choice(train_x.shape[0], train_x.shape[0]//2, replace=False) + train_x = train_x[ss_inds] + train_y = train_y[ss_inds] - init_model = build_model(trn_x, trn_y) - _ = train_model(init_model, trn_x, trn_y, save_weights=True) + init_model = build_model(train_x, train_y) + _ = train_model(init_model, train_x, train_y, save_weights=True) - auditor = audit.AuditAttack(trn_x, trn_y, train_and_score) + auditor = audit.AuditAttack(train_x, train_y, train_and_score) thresh, _, _ = auditor.run(FLAGS.pois_ct, FLAGS.attack_type, FLAGS.num_trials, alpha=FLAGS.alpha, threshold=None, @@ -170,9 +166,9 @@ def main(unused_argv): alpha=FLAGS.alpha, threshold=thresh, l2_norm=FLAGS.attack_l2_norm) - epsilon_ub = compute_epsilon(trn_x.shape[0]) + epsilon_upper_bound = compute_epsilon(train_x.shape[0]) - print("Analysis epsilon is {}.".format(epsilon_ub)) + print("Analysis epsilon is {}.".format(epsilon_upper_bound)) print("At threshold={}, epsilon={}.".format(thresh, eps)) print("The best accuracy at distinguishing poisoning is {}.".format(acc)) diff --git a/research/audit_2020/mean_audit.py b/research/audit_2020/mean_audit.py index 752cebd..af386bd 100644 --- a/research/audit_2020/mean_audit.py +++ b/research/audit_2020/mean_audit.py @@ -1,4 +1,4 @@ -# Copyright 2020 The TensorFlow Authors. All Rights Reserved. +# Copyright 2021 The TensorFlow Authors. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,10 +15,6 @@ """Auditing a model which computes the mean of a synthetic dataset. This gives an example for instrumenting the auditor to audit a user-given sample.""" -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - import numpy as np import tensorflow.compat.v1 as tf @@ -146,9 +142,9 @@ def main(unused_argv): _, eps, acc = auditor.run(1, None, FLAGS.num_trials, alpha=FLAGS.alpha, threshold=thresh) - epsilon_ub = compute_epsilon(FLAGS.batch_size) + epsilon_upper_bound = compute_epsilon(FLAGS.batch_size) - print("Analysis epsilon is {}.".format(epsilon_ub)) + print("Analysis epsilon is {}.".format(epsilon_upper_bound)) print("At threshold={}, epsilon={}.".format(thresh, eps)) print("The best accuracy at distinguishing poisoning is {}.".format(acc))