├── .idea ├── DiffPrivate_FedLearning.iml ├── misc.xml ├── modules.xml └── vcs.xml ├── .reuse └── dep5 ├── Create_clients.py ├── DiffPrivate_FedLearning.py ├── Helper_Functions.py ├── LICENSE ├── LICENSES └── Apache-2.0.txt ├── MNIST_reader.py ├── NOTICE ├── README.md ├── RUNME.sh ├── accountant.py ├── gaussian_moments.py ├── mnist_inference.py ├── sample.py └── utils.py /.idea/DiffPrivate_FedLearning.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 11 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.reuse/dep5: -------------------------------------------------------------------------------- 1 | Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Upstream-Name: machine-learning-diff-private-federated-learning 3 | Upstream-Contact: Tassilo Klein (tassilo.klein@sap.com) 4 | Source: https://github.com/SAP-samples/machine-learning-diff-private-federated-learning 5 | Disclaimer: The code in this project may include calls to APIs (“API Calls”) of 6 | SAP or third-party products or services developed outside of this project 7 | (“External Products”). 8 | “APIs” means application programming interfaces, as well as their respective 9 | specifications and implementing code that allows software to communicate with 10 | other software. 11 | API Calls to External Products are not licensed under the open source license 12 | that governs this project. The use of such API Calls and related External 13 | Products are subject to applicable additional agreements with the relevant 14 | provider of the External Products. In no event shall the open source license 15 | that governs this project grant any rights in or to any External Products,or 16 | alter, expand or supersede any terms of the applicable additional agreements. 17 | If you have a valid license agreement with SAP for the use of a particular SAP 18 | External Product, then you may make use of any API Calls included in this 19 | project’s code for that SAP External Product, subject to the terms of such 20 | license agreement. If you do not have a valid license agreement for the use of 21 | a particular SAP External Product, then you may only make use of any API Calls 22 | in this project for that SAP External Product for your internal, non-productive 23 | and non-commercial test and evaluation of such API Calls. Nothing herein grants 24 | you any rights to use or access any SAP External Product, or provide any third 25 | parties the right to use of access any SAP External Product, through API Calls. 26 | 27 | Files: * 28 | Copyright: 2018-2020 SAP SE or an SAP affiliate company and machine-learning-diff-private-federated-learning contributors 29 | License: Apache-2.0 30 | -------------------------------------------------------------------------------- /Create_clients.py: -------------------------------------------------------------------------------- 1 | import pickle 2 | import numpy as np 3 | import os 4 | 5 | def create_clients(num, dir): 6 | 7 | ''' 8 | This function creates clients that hold non-iid MNIST data accroding to the experiments in https://research.google.com/pubs/pub44822.html. (it actually just creates indices that point to data. 9 | but the way these indices are grouped, they create a non-iid client.) 10 | :param num: Number of clients 11 | :param dir: where to store 12 | :return: _ 13 | ''' 14 | 15 | num_examples = 50000 16 | num_classes = 10 17 | if os.path.exists(dir + '/'+str(num)+'_clients.pkl'): 18 | print('Client exists at: '+dir + '/'+str(num)+'_clients.pkl') 19 | return 20 | if not os.path.exists(dir): 21 | os.makedirs(dir) 22 | buckets = [] 23 | for k in range(num_classes): 24 | temp = [] 25 | for j in range(num / 100): 26 | temp = np.hstack((temp, k * num_examples/10 + np.random.permutation(int(num_examples/10)))) 27 | buckets = np.hstack((buckets, temp)) 28 | shards = 2 * num 29 | perm = np.random.permutation(shards) 30 | # z will be of length 250 and each element represents a client. 31 | z = [] 32 | ind_list = np.split(buckets, shards) 33 | for j in range(0, shards, 2): 34 | # each entry of z is associated to two shards. the two shards are sampled randomly by using the permutation matrix 35 | # perm and stacking two shards together using vstack. Each client now holds 250*2 datapoints. 36 | z.append(np.hstack((ind_list[int(perm[j])], ind_list[int(perm[j + 1])]))) 37 | # shuffle the data in each element of z, so that each client doesn't have all digits stuck together. 38 | perm_2 = np.random.permutation(int(2 * len(buckets) / shards)) 39 | z[-1] = z[-1][perm_2] 40 | filehandler = open(dir + '/'+str(num)+'_clients.pkl', "wb") 41 | pickle.dump(z, filehandler) 42 | filehandler.close() 43 | print('client created at: '+dir + '/'+str(num)+'_clients.pkl') 44 | 45 | if __name__ == '__main__': 46 | List_of_clients = [100,200,500,1000,2000,5000,10000] 47 | for j in List_of_clients: 48 | create_clients(j, os.getcwd()+'/DATA/clients') 49 | -------------------------------------------------------------------------------- /DiffPrivate_FedLearning.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | from __future__ import division 3 | from __future__ import print_function 4 | import numpy as np 5 | import tensorflow as tf 6 | import math 7 | from Helper_Functions import Vname_to_FeedPname, Vname_to_Pname, check_validaity_of_FLAGS, create_save_dir, \ 8 | global_step_creator, load_from_directory_or_initialize, bring_Accountant_up_to_date, save_progress, \ 9 | WeightsAccountant, print_loss_and_accuracy, print_new_comm_round, PrivAgent, Flag 10 | 11 | 12 | def run_differentially_private_federated_averaging(loss, train_op, eval_correct, data, data_placeholder, 13 | label_placeholder, privacy_agent=None, b=10, e=4, 14 | record_privacy=True, m=0, sigma=0, eps=8, save_dir=None, 15 | log_dir=None, max_comm_rounds=3000, gm=True, 16 | saver_func=create_save_dir, save_params=False): 17 | 18 | """ 19 | This function will simulate a federated learning setting and enable differential privacy tracking. It will detect 20 | all trainable tensorflow variables in the tensorflow graph and simulate a decentralized learning process where these 21 | variables are learned through clients that only have access to their own data set. 22 | This function must therefore be run inside a Graph as follows: 23 | -------------------------------------------------------------------------------------------------------------------- 24 | 25 | with tf.Graph().as_default(): 26 | 27 | train_op, eval_correct, loss, data_placeholder, labels_placeholder = Some_function_that_builds_TF_graph() 28 | 29 | Accuracy_accountant, Delta_accountant, model = \ 30 | run_differentially_private_federated_averaging(loss, train_op, eval_correct, DATA, data_placeholder, 31 | labels_placeholder) 32 | -------------------------------------------------------------------------------------------------------------------- 33 | The graph that train_op, loss and eval_op belong to should have a global_step variable. 34 | 35 | :param loss: TENSORFLOW node that computes the current loss 36 | :param train_op: TENSORFLOW Training_op 37 | :param eval_correct: TENSORFLOW node that evaluates the number of correct predictions 38 | :param data: A class instance with attributes: 39 | .data_set : The training data stored in a list or numpy array. 40 | .label_set : The training labels stored in a list or numpy array. 41 | The indices should correspond to .data_set. This means a single index 42 | corresponds to a data(x)-label(y) pair used for training: 43 | (x_i, y_i) = (data.data_set(i),data.label_set(i)) 44 | .client_set : A nested list or numpy array. len(data.client_set) is the total 45 | number of clients. for any j, data.client_set[j] is a list (or array) 46 | holding indices. these indices specify the data points that client j 47 | holds. 48 | i.e. if i \in data.client_set[j], then client j owns (x_i, y_i) 49 | .vali_data_set : The validation data stored in a list or numpy array. 50 | .vali_label_set : The validation labels stored in a list or numpy array. 51 | :param data_placeholder: The placeholder from the tensorflow graph that is used to feed the model with data 52 | :param label_placeholder: The placeholder from the tensorflow graph that is used to feed the model with labels 53 | :param privacy_agent: A class instance that has callabels .get_m(r) .get_Sigma(r) .get_bound(), where r is the 54 | communication round. 55 | :param b: Batchsize 56 | :param e: Epochs to run on each client 57 | :param record_privacy: Whether to record the privacy or not 58 | :param m: If specified, a privacyAgent is not used, instead the parameter is kept constant 59 | :param sigma: If specified, a privacyAgent is not used, instead the parameter is kept constant 60 | :param eps: The epsilon for epsilon-delta privacy 61 | :param save_dir: Directory to store the process 62 | :param log_dir: Directory to store the graph 63 | :param max_comm_rounds: The maximum number of allowed communication rounds 64 | :param gm: Whether to use a Gaussian Mechanism or not. 65 | :param saver_func: A function that specifies where and how to save progress: Note that the usual tensorflow 66 | tracking will not work 67 | :param save_params: save all weights_throughout training. 68 | 69 | :return: 70 | 71 | """ 72 | 73 | # If no privacy agent was specified, the default privacy agent is used. 74 | if not privacy_agent: 75 | privacy_agent = PrivAgent(len(data.client_set), 'default_agent') 76 | 77 | # A Flags instance is created that will fuse all specified parameters and default those that are not specified. 78 | FLAGS = Flag(len(data.client_set), b, e, record_privacy, m, sigma, eps, save_dir, log_dir, max_comm_rounds, gm, 79 | privacy_agent) 80 | 81 | # Check whether the specified parameters make sense. 82 | FLAGS = check_validaity_of_FLAGS(FLAGS) 83 | 84 | # At this point, FLAGS.save_dir specifies both; where we save progress and where we assume the data is stored 85 | save_dir = saver_func(FLAGS) 86 | 87 | # This function will retrieve the variable associated to the global step and create nodes that serve to 88 | # increase and reset it to a certain value. 89 | increase_global_step, set_global_step = global_step_creator() 90 | 91 | # - model_placeholder : a dictionary in which there is a placeholder stored for every trainable variable defined 92 | # in the tensorflow graph. Each placeholder corresponds to one trainable variable and has 93 | # the same shape and dtype as that variable. in addition, the placeholder has the same 94 | # name as the Variable, but a '_placeholder:0' added to it. The keys of the dictionary 95 | # correspond to the name of the respective placeholder 96 | model_placeholder = dict(zip([Vname_to_FeedPname(var) for var in tf.trainable_variables()], 97 | [tf.placeholder(name=Vname_to_Pname(var), 98 | shape=var.shape, 99 | dtype=tf.float32) 100 | for var in tf.trainable_variables()])) 101 | 102 | # - assignments : Is a list of nodes. when run, all trainable variables are set to the value specified through 103 | # the placeholders in 'model_placeholder'. 104 | 105 | assignments = [tf.assign(var, model_placeholder[Vname_to_FeedPname(var)]) for var in 106 | tf.trainable_variables()] 107 | 108 | # load_from_directory_or_initialize checks whether there is a model at 'save_dir' corresponding to the one we 109 | # are building. If so, training is resumed, if not, it returns: - model = [] 110 | # - accuracy_accountant = [] 111 | # - delta_accountant = [] 112 | # - real_round = 0 113 | # And initializes a Differential_Privacy_Accountant as acc 114 | 115 | model, accuracy_accountant, delta_accountant, acc, real_round, FLAGS, computed_deltas = \ 116 | load_from_directory_or_initialize(save_dir, FLAGS) 117 | 118 | m = int(FLAGS.m) 119 | sigma = float(FLAGS.sigma) 120 | # - m : amount of clients participating in a round 121 | # - sigma : variable for the Gaussian Mechanism. 122 | # Both will only be used if no Privacy_Agent is deployed. 123 | 124 | ################################################################################################################ 125 | 126 | # Usual Tensorflow... 127 | 128 | init = tf.global_variables_initializer() 129 | sess = tf.Session() 130 | sess.run(init) 131 | 132 | ################################################################################################################ 133 | 134 | # If there was no loadable model, we initialize a model: 135 | # - model : dictionary having as keys the names of the placeholders associated to each variable. It will serve 136 | # as a feed_dict to assign values to the placeholders which are used to set the variables to 137 | # specific values. 138 | 139 | if not model: 140 | model = dict(zip([Vname_to_FeedPname(var) for var in tf.trainable_variables()], 141 | [sess.run(var) for var in tf.trainable_variables()])) 142 | model['global_step_placeholder:0'] = 0 143 | 144 | real_round = 0 145 | 146 | weights_accountant = [] 147 | 148 | # If a model is loaded, and we are not relearning it (relearning means that we once already finished such a model 149 | # and we are learning it again to average the outcomes), we have to get the privacy accountant up to date. This 150 | # means, that we have to iterate the privacy accountant over all the m, sigmas that correspond to already completed 151 | # communication 152 | 153 | if not FLAGS.relearn and real_round > 0: 154 | bring_Accountant_up_to_date(acc, sess, real_round, privacy_agent, FLAGS) 155 | 156 | ################################################################################################################ 157 | 158 | # This is where the actual communication rounds start: 159 | 160 | data_set_asarray = np.asarray(data.sorted_x_train) 161 | label_set_asarray = np.asarray(data.sorted_y_train) 162 | 163 | for r in xrange(FLAGS.max_comm_rounds): 164 | 165 | # First, we check whether we are loading a model, if so, we have to skip the first allocation, as it took place 166 | # already. 167 | if not (FLAGS.loaded and r == 0): 168 | # Setting the trainable Variables in the graph to the values stored in feed_dict 'model' 169 | sess.run(assignments, feed_dict=model) 170 | 171 | # create a feed-dict holding the validation set. 172 | 173 | feed_dict = {str(data_placeholder.name): np.asarray(data.x_vali), 174 | str(label_placeholder.name): np.asarray(data.y_vali)} 175 | 176 | # compute the loss on the validation set. 177 | global_loss = sess.run(loss, feed_dict=feed_dict) 178 | count = sess.run(eval_correct, feed_dict=feed_dict) 179 | accuracy = float(count) / float(len(data.y_vali)) 180 | accuracy_accountant.append(accuracy) 181 | 182 | print_loss_and_accuracy(global_loss, accuracy) 183 | 184 | if delta_accountant[-1] > privacy_agent.get_bound() or math.isnan(delta_accountant[-1]): 185 | print('************** The last step exhausted the privacy budget **************') 186 | if not math.isnan(delta_accountant[-1]): 187 | try: 188 | None 189 | finally: 190 | save_progress(save_dir, model, delta_accountant + [float('nan')], 191 | accuracy_accountant + [float('nan')], privacy_agent, FLAGS) 192 | return accuracy_accountant, delta_accountant, model 193 | else: 194 | try: 195 | None 196 | finally: 197 | save_progress(save_dir, model, delta_accountant, accuracy_accountant, privacy_agent, FLAGS) 198 | 199 | ############################################################################################################ 200 | # Start of a new communication round 201 | 202 | real_round = real_round + 1 203 | 204 | print_new_comm_round(real_round) 205 | 206 | if FLAGS.priv_agent: 207 | m = int(privacy_agent.get_m(int(real_round))) 208 | sigma = privacy_agent.get_Sigma(int(real_round)) 209 | 210 | print('Clients participating: ' + str(m)) 211 | 212 | # Randomly choose a total of m (out of n) client-indices that participate in this round 213 | # randomly permute a range-list of length n: [1,2,3...n] --> [5,2,7..3] 214 | perm = np.random.permutation(FLAGS.n) 215 | 216 | # Use the first m entries of the permuted list to decide which clients (and their sets) will participate in 217 | # this round. participating_clients is therefore a nested list of length m. participating_clients[i] should be 218 | # a list of integers that specify which data points are held by client i. Note that this nested list is a 219 | # mapping only. the actual data is stored in data.data_set. 220 | s = perm[0:m].tolist() 221 | participating_clients = [data.client_set[k] for k in s] 222 | 223 | # For each client c (out of the m chosen ones): 224 | for c in range(m): 225 | 226 | # Assign the global model and set the global step. This is obsolete when the first client trains, 227 | # but as soon as the next client trains, all progress allocated before, has to be discarded and the 228 | # trainable variables reset to the values specified in 'model' 229 | sess.run(assignments + [set_global_step], feed_dict=model) 230 | 231 | # allocate a list, holding data indices associated to client c and split into batches. 232 | data_ind = np.split(np.asarray(participating_clients[c]), FLAGS.b, 0) 233 | 234 | # e = Epoch 235 | for e in xrange(int(FLAGS.e)): 236 | for step in xrange(len(data_ind)): 237 | # increase the global_step count (it's used for the learning rate.) 238 | real_step = sess.run(increase_global_step) 239 | # batch_ind holds the indices of the current batch 240 | batch_ind = data_ind[step] 241 | 242 | # Fill a feed dictionary with the actual set of data and labels using the data and labels associated 243 | # to the indices stored in batch_ind: 244 | feed_dict = {str(data_placeholder.name): data_set_asarray[[int(j) for j in batch_ind]], 245 | str(label_placeholder.name): label_set_asarray[[int(j) for j in batch_ind]]} 246 | 247 | # Run one optimization step. 248 | _ = sess.run([train_op], feed_dict=feed_dict) 249 | 250 | if c == 0: 251 | 252 | # If we just trained the first client in a comm_round, We override the old weights_accountant (or, 253 | # if this was the first comm_round, we allocate a new one. The Weights_accountant keeps track of 254 | # all client updates throughout a communication round. 255 | weights_accountant = WeightsAccountant(sess, model, sigma, real_round) 256 | else: 257 | # Allocate the client update, if this is not the first client in a communication round 258 | weights_accountant.allocate(sess) 259 | 260 | # End of a communication round 261 | ############################################################################################################ 262 | 263 | print('......Communication round %s completed' % str(real_round)) 264 | # Compute a new model according to the updates and the Gaussian mechanism specifications from FLAGS 265 | # Also, if computed_deltas is an empty list, compute delta; the probability of Epsilon-Differential Privacy 266 | # being broken by allocating the model. If computed_deltas is passed, instead of computing delta, the 267 | # pre-computed vaue is used. 268 | model, delta = weights_accountant.Update_via_GaussianMechanism(sess, acc, FLAGS, computed_deltas) 269 | 270 | # append delta to a list. 271 | delta_accountant.append(delta) 272 | 273 | # Set the global_step to the current step of the last client, such that the next clients can feed it into 274 | # the learning rate. 275 | model['global_step_placeholder:0'] = real_step 276 | 277 | # PRINT the progress and stage of affairs. 278 | print(' - Epsilon-Delta Privacy:' + str([FLAGS.eps, delta])) 279 | 280 | if save_params: 281 | weights_accountant.save_params(save_dir) 282 | 283 | return [], [], [] 284 | -------------------------------------------------------------------------------- /Helper_Functions.py: -------------------------------------------------------------------------------- 1 | 2 | from __future__ import absolute_import 3 | from __future__ import division 4 | from __future__ import print_function 5 | import numpy as np 6 | import csv 7 | import os.path 8 | import pickle 9 | from accountant import GaussianMomentsAccountant 10 | import math 11 | import os 12 | import tensorflow as tf 13 | 14 | class PrivAgent: 15 | def __init__(self, N, Name): 16 | self.N = N 17 | self.Name = Name 18 | if N == 100: 19 | self.m = [30]*100 20 | self.Sigma = [1]*24 21 | self.bound = 0.001 22 | if N == 1000: 23 | self.m = [100]*10 24 | self.Sigma = [1]*24 25 | self.bound = 0.00001 26 | if N == 10000: 27 | self.m = [300]*10 28 | self.Sigma = [1]*24 29 | self.bound = 0.000001 30 | if(N != 100 and N != 1000 and N != 10000 ): 31 | print('!!!!!!! YOU CAN ONLY USE THE PRIVACY AGENT FOR N = 100, 1000 or 10000 !!!!!!!') 32 | 33 | def get_m(self, r): 34 | return self.m[r] 35 | 36 | def get_Sigma(self, r): 37 | return self.Sigma[r] 38 | 39 | def get_bound(self): 40 | return self.bound 41 | 42 | def Assignements(dic): 43 | return [tf.assign(var, dic[Vname_to_Pname(var)]) for var in tf.trainable_variables()] 44 | 45 | 46 | def Vname_to_Pname(var): 47 | return var.name[:var.name.find(':')] + '_placeholder' 48 | 49 | 50 | def Vname_to_FeedPname(var): 51 | return var.name[:var.name.find(':')] + '_placeholder:0' 52 | 53 | 54 | def Vname_to_Vname(var): 55 | return var.name[:var.name.find(':')] 56 | 57 | 58 | class WeightsAccountant: 59 | def __init__(self,sess,model,Sigma, real_round): 60 | 61 | self.Weights = [np.expand_dims(sess.run(v), -1) for v in tf.trainable_variables()] 62 | self.keys = [Vname_to_FeedPname(v) for v in tf.trainable_variables()] 63 | 64 | # The trainable parameters are [q x p] matrices, we expand them to [q x p x 1] in order to later stack them 65 | # along the third dimension. 66 | 67 | # Create a list out of the model dictionary in the order in which the graph holds them: 68 | 69 | self.global_model = [model[k] for k in self.keys] 70 | self.Sigma = Sigma 71 | self.Updates = [] 72 | self.median = [] 73 | self.Norms = [] 74 | self.ClippedUpdates = [] 75 | self.m = 0.0 76 | self.num_weights = len(self.Weights) 77 | self.round = real_round 78 | 79 | def save_params(self,save_dir): 80 | filehandler = open(save_dir + '/Wweights_accountant_round_'+self.round + '.pkl', "wb") 81 | pickle.dump(self, filehandler) 82 | filehandler.close() 83 | 84 | def allocate(self, sess): 85 | 86 | self.Weights = [np.concatenate((self.Weights[i], np.expand_dims(sess.run(tf.trainable_variables()[i]), -1)), -1) 87 | for i in range(self.num_weights)] 88 | 89 | # The trainable parameters are [q x p] matrices, we expand them to [q x p x 1] in order to stack them 90 | # along the third dimension to the already allocated older variables. We therefore have a list of 6 numpy arrays 91 | # , each numpy array having three dimensions. The last dimension is the one, the individual weight 92 | # matrices are stacked along. 93 | 94 | def compute_updates(self): 95 | 96 | # To compute the updates, we subtract the global model from each individual weight matrix. Note: 97 | # self.Weights[i] is of size [q x p x m], where m is the number of clients whose matrices are stored. 98 | # global_model['i'] is of size [q x p], in order to broadcast correctly, we have to add a dim. 99 | 100 | self.Updates = [self.Weights[i]-np.expand_dims(self.global_model[i], -1) for i in range(self.num_weights)] 101 | self.Weights = None 102 | 103 | def compute_norms(self): 104 | 105 | # The norms List shall have 6 entries, each of size [1x1xm], we keep the first two dimensions because 106 | # we will later broadcast the Norms onto the Updates of size [q x p x m] 107 | 108 | self.Norms = [np.sqrt(np.sum( 109 | np.square(self.Updates[i]), axis=tuple(range(self.Updates[i].ndim)[:-1]),keepdims=True)) for i in range(self.num_weights)] 110 | 111 | def clip_updates(self): 112 | self.compute_updates() 113 | self.compute_norms() 114 | 115 | # The median is a list of 6 entries, each of size [1x1x1], 116 | 117 | self.median = [np.median(self.Norms[i], axis=-1, keepdims=True) for i in range(self.num_weights)] 118 | 119 | # The factor is a list of 6 entries, each of size [1x1xm] 120 | 121 | factor = [self.Norms[i]/self.median[i] for i in range(self.num_weights)] 122 | for i in range(self.num_weights): 123 | factor[i][factor[i] > 1.0] = 1.0 124 | 125 | self.ClippedUpdates = [self.Updates[i]/factor[i] for i in range(self.num_weights)] 126 | 127 | def Update_via_GaussianMechanism(self, sess, Acc, FLAGS, Computed_deltas): 128 | self.clip_updates() 129 | self.m = float(self.ClippedUpdates[0].shape[-1]) 130 | MeanClippedUpdates = [np.mean(self.ClippedUpdates[i], -1) for i in range(self.num_weights)] 131 | 132 | GaussianNoise = [(1.0/self.m * np.random.normal(loc=0.0, scale=float(self.Sigma * self.median[i]), size=MeanClippedUpdates[i].shape)) for i in range(self.num_weights)] 133 | 134 | Sanitized_Updates = [MeanClippedUpdates[i]+GaussianNoise[i] for i in range(self.num_weights)] 135 | 136 | New_weights = [self.global_model[i]+Sanitized_Updates[i] for i in range(self.num_weights)] 137 | 138 | New_model = dict(zip(self.keys, New_weights)) 139 | 140 | t = Acc.accumulate_privacy_spending(0, self.Sigma, self.m) 141 | delta = 1 142 | if FLAGS.record_privacy == True: 143 | if FLAGS.relearn == False: 144 | # I.e. we never learned a complete model before and have therefore never computed all deltas. 145 | for j in range(len(self.keys)): 146 | sess.run(t) 147 | r = Acc.get_privacy_spent(sess, [FLAGS.eps]) 148 | delta = r[0][1] 149 | else: 150 | # I.e. we have computed a complete model before and can reuse the deltas from that time. 151 | delta = Computed_deltas[self.round] 152 | return New_model, delta 153 | 154 | def create_save_dir(FLAGS): 155 | ''' 156 | :return: Returns a path that is used to store training progress; the path also identifies the chosen setup uniquely. 157 | ''' 158 | raw_directory = FLAGS.save_dir + '/' 159 | if FLAGS.gm: gm_str = 'Dp/' 160 | else: gm_str = 'non_Dp/' 161 | if FLAGS.priv_agent: 162 | model = gm_str + 'N_' + str(FLAGS.n) + '/Epochs_' + str( 163 | int(FLAGS.e)) + '_Batches_' + str(int(FLAGS.b)) 164 | return raw_directory + str(model) + '/' + FLAGS.PrivAgentName 165 | else: 166 | model = gm_str + 'N_' + str(FLAGS.n) + '/Sigma_' + str(FLAGS.Sigma) + '_C_'+str(FLAGS.m)+'/Epochs_' + str( 167 | int(FLAGS.e)) + '_Batches_' + str(int(FLAGS.b)) 168 | return raw_directory + str(model) 169 | 170 | 171 | def load_from_directory_or_initialize(directory, FLAGS): 172 | ''' 173 | This function looks for a model that corresponds to the characteristics specified and loads potential progress. 174 | If it does not find any model or progress, it initializes a new model. 175 | :param directory: STRING: the directory where to look for models and progress. 176 | :param FLAGS: CLASS INSTANCE: holds general trianing params 177 | :param PrivacyAgent: 178 | :return: 179 | ''' 180 | 181 | Accuracy_accountant = [] 182 | Delta_accountant = [0] 183 | model = [] 184 | real_round = 0 185 | Acc = GaussianMomentsAccountant(FLAGS.n) 186 | FLAGS.loaded = False 187 | FLAGS.relearn = False 188 | Computed_Deltas = [] 189 | 190 | if not os.path.isfile(directory + '/model.pkl'): 191 | # If there is no model stored at the specified directory, we initialize a new one! 192 | if not os.path.exists(directory): 193 | os.makedirs(directory) 194 | print('No loadable model found. All updates stored at: ' + directory) 195 | print('... Initializing a new model ...') 196 | 197 | 198 | else: 199 | # If there is a model, we have to check whether: 200 | # - We learned a model for the first time, and interrupted; in that case: resume learning: 201 | # set FLAGS.loaded = TRUE 202 | # - We completed learning a model and want to learn a new one with the same parameters, i.o. to average accuracies: 203 | # In this case we would want to initialize a new model; but would like to reuse the delta's already 204 | # computed. So we will load the deltas. 205 | # set FLAGS.relearn = TRUE 206 | # - We completed learning models and want to resume learning model; this happens if the above process is 207 | # interrupted. In this case we want to load the model; and reuse the deltas. 208 | # set FLAGS.loaded = TRUE 209 | # set FLAGS.relearn = TRUE 210 | if os.path.isfile(directory + '/specs.csv'): 211 | with open(directory + '/specs.csv', 'rb') as csvfile: 212 | reader = csv.reader(csvfile) 213 | Lines = [] 214 | for line in reader: 215 | Lines.append([float(j) for j in line]) 216 | 217 | Accuracy_accountant = Lines[-1] 218 | Delta_accountant = Lines[1] 219 | 220 | if math.isnan(Delta_accountant[-1]): 221 | Computed_Deltas = Delta_accountant 222 | # This would mean that learning was finished at least once, i.e. we are relearning. 223 | # We shall thus not recompute the deltas, but rather reuse them. 224 | FLAGS.relearn = True 225 | if math.isnan(Accuracy_accountant[-1]): 226 | # This would mean that we finished learning the latest model. 227 | print('A model identical to that specified was already learned. Another one is learned and appended') 228 | Accuracy_accountant = [] 229 | Delta_accountant = [0] 230 | else: 231 | # This would mean we already completed learning a model once, but the last one stored was not completed 232 | print('A model identical to that specified was already learned. For a second one learning is resumed') 233 | # We set the delta accountant accordingly 234 | Delta_accountant = Delta_accountant[:len(Accuracy_accountant)] 235 | # We specify that a model was loaded 236 | real_round = len(Accuracy_accountant) - 1 237 | fil = open(directory + '/model.pkl', 'rb') 238 | model = pickle.load(fil) 239 | fil.close() 240 | FLAGS.loaded = True 241 | return model, Accuracy_accountant, Delta_accountant, Acc, real_round, FLAGS, Computed_Deltas 242 | else: 243 | # This would mean that learning was never finished, i.e. the first time a model with this specs was 244 | # learned got interrupted. 245 | real_round = len(Accuracy_accountant) - 1 246 | fil = open(directory + '/model.pkl', 'rb') 247 | model = pickle.load(fil) 248 | fil.close() 249 | FLAGS.loaded = True 250 | else: 251 | print('there seems to be a model, but no saved progress. Fix that.') 252 | raise KeyboardInterrupt 253 | return model, Accuracy_accountant, Delta_accountant, Acc, real_round, FLAGS, Computed_Deltas 254 | 255 | 256 | def save_progress(save_dir, model, Delta_accountant, Accuracy_accountant, PrivacyAgent, FLAGS): 257 | ''' 258 | This function saves our progress either in an existing file structure or writes a new file. 259 | :param save_dir: STRING: The directory where to save the progress. 260 | :param model: DICTIONARY: The model that we wish to save. 261 | :param Delta_accountant: LIST: The list of deltas that we allocared so far. 262 | :param Accuracy_accountant: LIST: The list of accuracies that we allocated so far. 263 | :param PrivacyAgent: CLASS INSTANCE: The privacy agent that we used (specifically the m's that we used for Federated training.) 264 | :param FLAGS: CLASS INSTANCE: The FLAGS passed to the learning procedure. 265 | :return: nothing 266 | ''' 267 | filehandler = open(save_dir + '/model.pkl', "wb") 268 | pickle.dump(model, filehandler) 269 | filehandler.close() 270 | 271 | if FLAGS.relearn == False: 272 | # I.e. we know that there was no progress stored at 'save_dir' and we create a new csv-file that 273 | # Will hold the accuracy, the deltas, the m's and we also save the model learned as a .pkl file 274 | 275 | with open(save_dir + '/specs.csv', 'wb') as csvfile: 276 | writer = csv.writer(csvfile, delimiter=',') 277 | if FLAGS.priv_agent == True: 278 | writer.writerow([0]+[PrivacyAgent.get_m(r) for r in range(len(Delta_accountant)-1)]) 279 | if FLAGS.priv_agent == False: 280 | writer.writerow([0]+[FLAGS.m]*(len(Delta_accountant)-1)) 281 | writer.writerow(Delta_accountant) 282 | writer.writerow(Accuracy_accountant) 283 | 284 | if FLAGS.relearn == True: 285 | # If there already is progress associated to the learned model, we do not need to store the deltas and m's as 286 | # they were already saved; we just keep track of the accuracy and append it to the already existing .csv file. 287 | # This will help us later on to average the performance, as the variance is very high. 288 | 289 | if len(Accuracy_accountant) > 1 or len(Accuracy_accountant) == 1 and FLAGS.loaded is True: 290 | # If we already appended a new line to the .csv file, we have to delete that line. 291 | with open(save_dir + '/specs.csv', 'r+w') as csvfile: 292 | csvReader = csv.reader(csvfile, delimiter=",") 293 | lines =[] 294 | for row in csvReader: 295 | lines.append([float(i) for i in row]) 296 | lines = lines[:-1] 297 | 298 | with open(save_dir + '/specs.csv', 'wb') as csvfile: 299 | writer = csv.writer(csvfile, delimiter=',') 300 | for line in lines: 301 | writer.writerow(line) 302 | 303 | # Append a line to the .csv file holding the accuracies. 304 | with open(save_dir + '/specs.csv', 'a') as csvfile: 305 | writer = csv.writer(csvfile, delimiter=',') 306 | writer.writerow(Accuracy_accountant) 307 | 308 | 309 | def global_step_creator(): 310 | global_step = [v for v in tf.global_variables() if v.name == "global_step:0"][0] 311 | global_step_placeholder = tf.placeholder(dtype=tf.float32, shape=(), name='global_step_placeholder') 312 | one = tf.constant(1, dtype=tf.float32, name='one') 313 | new_global_step = tf.add(global_step, one) 314 | increase_global_step = tf.assign(global_step, new_global_step) 315 | set_global_step = tf.assign(global_step, global_step_placeholder) 316 | return increase_global_step, set_global_step 317 | 318 | 319 | def bring_Accountant_up_to_date(Acc, sess, rounds, PrivAgent, FLAGS): 320 | ''' 321 | 322 | :param Acc: A Privacy accountant 323 | :param sess: A tensorflow session 324 | :param rounds: the number of rounds that the privacy accountant shall iterate 325 | :param PrivAgent: A Privacy_agent that has functions: PrivAgent.get_Sigma(round) and PrivAgent.get_m(round) 326 | :param FLAGS: priv_agent specifies whether to use a PrivAgent or not. 327 | :return: 328 | ''' 329 | print('Bringing the accountant up to date....') 330 | 331 | for r in range(rounds): 332 | if FLAGS.priv_agent: 333 | Sigma = PrivAgent.get_Sigma(r) 334 | m = PrivAgent.get_m(r) 335 | else: 336 | Sigma = FLAGS.sigma 337 | m = FLAGS.m 338 | print('Completed '+str(r+1)+' out of '+str(rounds)+' rounds') 339 | t = Acc.accumulate_privacy_spending(0, Sigma, m) 340 | sess.run(t) 341 | sess.run(t) 342 | sess.run(t) 343 | print('The accountant is up to date!') 344 | 345 | def print_loss_and_accuracy(global_loss,accuracy): 346 | print(' - Current Model has a loss of: %s' % global_loss) 347 | print(' - The Accuracy on the validation set is: %s' % accuracy) 348 | print('--------------------------------------------------------------------------------------') 349 | print('--------------------------------------------------------------------------------------') 350 | 351 | 352 | def print_new_comm_round(real_round): 353 | print('--------------------------------------------------------------------------------------') 354 | print('------------------------ Communication round %s ---------------------------------------' % str(real_round)) 355 | print('--------------------------------------------------------------------------------------') 356 | 357 | def check_validaity_of_FLAGS(FLAGS): 358 | FLAGS.priv_agent = True 359 | if not FLAGS.m == 0: 360 | if FLAGS.sigma == 0: 361 | print('\n \n -------- If m is specified the Privacy Agent is not used, then Sigma has to be specified too. --------\n \n') 362 | raise NotImplementedError 363 | if not FLAGS.sigma == 0: 364 | if FLAGS.m ==0: 365 | print('\n \n-------- If Sigma is specified the Privacy Agent is not used, then m has to be specified too. -------- \n \n') 366 | raise NotImplementedError 367 | if not FLAGS.sigma == 0 and not FLAGS.m == 0: 368 | FLAGS.priv_agent = False 369 | return FLAGS 370 | 371 | class Flag: 372 | def __init__(self, n, b, e, record_privacy, m, sigma, eps, save_dir, log_dir, max_comm_rounds, gm, PrivAgent): 373 | if not save_dir: 374 | save_dir = os.getcwd() 375 | if not log_dir: 376 | log_dir = os.path.join(os.getenv('TEST_TMPDIR', '/tmp'), 'tensorflow/mnist/logs/fully_connected_feed') 377 | if tf.gfile.Exists(log_dir): 378 | tf.gfile.DeleteRecursively(log_dir) 379 | tf.gfile.MakeDirs(log_dir) 380 | self.n = n 381 | self.sigma = sigma 382 | self.eps = eps 383 | self.m = m 384 | self.b = b 385 | self.e = e 386 | self.record_privacy = record_privacy 387 | self.save_dir = save_dir 388 | self.log_dir = log_dir 389 | self.max_comm_rounds = max_comm_rounds 390 | self.gm = gm 391 | self.PrivAgentName = PrivAgent.Name -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSES/Apache-2.0.txt: -------------------------------------------------------------------------------- 1 | Apache License 2 | 3 | Version 2.0, January 2004 4 | 5 | http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, 6 | AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | 11 | 12 | "License" shall mean the terms and conditions for use, reproduction, and distribution 13 | as defined by Sections 1 through 9 of this document. 14 | 15 | 16 | 17 | "Licensor" shall mean the copyright owner or entity authorized by the copyright 18 | owner that is granting the License. 19 | 20 | 21 | 22 | "Legal Entity" shall mean the union of the acting entity and all other entities 23 | that control, are controlled by, or are under common control with that entity. 24 | For the purposes of this definition, "control" means (i) the power, direct 25 | or indirect, to cause the direction or management of such entity, whether 26 | by contract or otherwise, or (ii) ownership of fifty percent (50%) or more 27 | of the outstanding shares, or (iii) beneficial ownership of such entity. 28 | 29 | 30 | 31 | "You" (or "Your") shall mean an individual or Legal Entity exercising permissions 32 | granted by this License. 33 | 34 | 35 | 36 | "Source" form shall mean the preferred form for making modifications, including 37 | but not limited to software source code, documentation source, and configuration 38 | files. 39 | 40 | 41 | 42 | "Object" form shall mean any form resulting from mechanical transformation 43 | or translation of a Source form, including but not limited to compiled object 44 | code, generated documentation, and conversions to other media types. 45 | 46 | 47 | 48 | "Work" shall mean the work of authorship, whether in Source or Object form, 49 | made available under the License, as indicated by a copyright notice that 50 | is included in or attached to the work (an example is provided in the Appendix 51 | below). 52 | 53 | 54 | 55 | "Derivative Works" shall mean any work, whether in Source or Object form, 56 | that is based on (or derived from) the Work and for which the editorial revisions, 57 | annotations, elaborations, or other modifications represent, as a whole, an 58 | original work of authorship. For the purposes of this License, Derivative 59 | Works shall not include works that remain separable from, or merely link (or 60 | bind by name) to the interfaces of, the Work and Derivative Works thereof. 61 | 62 | 63 | 64 | "Contribution" shall mean any work of authorship, including the original version 65 | of the Work and any modifications or additions to that Work or Derivative 66 | Works thereof, that is intentionally submitted to Licensor for inclusion in 67 | the Work by the copyright owner or by an individual or Legal Entity authorized 68 | to submit on behalf of the copyright owner. For the purposes of this definition, 69 | "submitted" means any form of electronic, verbal, or written communication 70 | sent to the Licensor or its representatives, including but not limited to 71 | communication on electronic mailing lists, source code control systems, and 72 | issue tracking systems that are managed by, or on behalf of, the Licensor 73 | for the purpose of discussing and improving the Work, but excluding communication 74 | that is conspicuously marked or otherwise designated in writing by the copyright 75 | owner as "Not a Contribution." 76 | 77 | 78 | 79 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf 80 | of whom a Contribution has been received by Licensor and subsequently incorporated 81 | within the Work. 82 | 83 | 2. Grant of Copyright License. Subject to the terms and conditions of this 84 | License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, 85 | no-charge, royalty-free, irrevocable copyright license to reproduce, prepare 86 | Derivative Works of, publicly display, publicly perform, sublicense, and distribute 87 | the Work and such Derivative Works in Source or Object form. 88 | 89 | 3. Grant of Patent License. Subject to the terms and conditions of this License, 90 | each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, 91 | no-charge, royalty-free, irrevocable (except as stated in this section) patent 92 | license to make, have made, use, offer to sell, sell, import, and otherwise 93 | transfer the Work, where such license applies only to those patent claims 94 | licensable by such Contributor that are necessarily infringed by their Contribution(s) 95 | alone or by combination of their Contribution(s) with the Work to which such 96 | Contribution(s) was submitted. If You institute patent litigation against 97 | any entity (including a cross-claim or counterclaim in a lawsuit) alleging 98 | that the Work or a Contribution incorporated within the Work constitutes direct 99 | or contributory patent infringement, then any patent licenses granted to You 100 | under this License for that Work shall terminate as of the date such litigation 101 | is filed. 102 | 103 | 4. Redistribution. You may reproduce and distribute copies of the Work or 104 | Derivative Works thereof in any medium, with or without modifications, and 105 | in Source or Object form, provided that You meet the following conditions: 106 | 107 | (a) You must give any other recipients of the Work or Derivative Works a copy 108 | of this License; and 109 | 110 | (b) You must cause any modified files to carry prominent notices stating that 111 | You changed the files; and 112 | 113 | (c) You must retain, in the Source form of any Derivative Works that You distribute, 114 | all copyright, patent, trademark, and attribution notices from the Source 115 | form of the Work, excluding those notices that do not pertain to any part 116 | of the Derivative Works; and 117 | 118 | (d) If the Work includes a "NOTICE" text file as part of its distribution, 119 | then any Derivative Works that You distribute must include a readable copy 120 | of the attribution notices contained within such NOTICE file, excluding those 121 | notices that do not pertain to any part of the Derivative Works, in at least 122 | one of the following places: within a NOTICE text file distributed as part 123 | of the Derivative Works; within the Source form or documentation, if provided 124 | along with the Derivative Works; or, within a display generated by the Derivative 125 | Works, if and wherever such third-party notices normally appear. The contents 126 | of the NOTICE file are for informational purposes only and do not modify the 127 | License. You may add Your own attribution notices within Derivative Works 128 | that You distribute, alongside or as an addendum to the NOTICE text from the 129 | Work, provided that such additional attribution notices cannot be construed 130 | as modifying the License. 131 | 132 | You may add Your own copyright statement to Your modifications and may provide 133 | additional or different license terms and conditions for use, reproduction, 134 | or distribution of Your modifications, or for any such Derivative Works as 135 | a whole, provided Your use, reproduction, and distribution of the Work otherwise 136 | complies with the conditions stated in this License. 137 | 138 | 5. Submission of Contributions. Unless You explicitly state otherwise, any 139 | Contribution intentionally submitted for inclusion in the Work by You to the 140 | Licensor shall be under the terms and conditions of this License, without 141 | any additional terms or conditions. Notwithstanding the above, nothing herein 142 | shall supersede or modify the terms of any separate license agreement you 143 | may have executed with Licensor regarding such Contributions. 144 | 145 | 6. Trademarks. This License does not grant permission to use the trade names, 146 | trademarks, service marks, or product names of the Licensor, except as required 147 | for reasonable and customary use in describing the origin of the Work and 148 | reproducing the content of the NOTICE file. 149 | 150 | 7. Disclaimer of Warranty. Unless required by applicable law or agreed to 151 | in writing, Licensor provides the Work (and each Contributor provides its 152 | Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 153 | KIND, either express or implied, including, without limitation, any warranties 154 | or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR 155 | A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness 156 | of using or redistributing the Work and assume any risks associated with Your 157 | exercise of permissions under this License. 158 | 159 | 8. Limitation of Liability. In no event and under no legal theory, whether 160 | in tort (including negligence), contract, or otherwise, unless required by 161 | applicable law (such as deliberate and grossly negligent acts) or agreed to 162 | in writing, shall any Contributor be liable to You for damages, including 163 | any direct, indirect, special, incidental, or consequential damages of any 164 | character arising as a result of this License or out of the use or inability 165 | to use the Work (including but not limited to damages for loss of goodwill, 166 | work stoppage, computer failure or malfunction, or any and all other commercial 167 | damages or losses), even if such Contributor has been advised of the possibility 168 | of such damages. 169 | 170 | 9. Accepting Warranty or Additional Liability. While redistributing the Work 171 | or Derivative Works thereof, You may choose to offer, and charge a fee for, 172 | acceptance of support, warranty, indemnity, or other liability obligations 173 | and/or rights consistent with this License. However, in accepting such obligations, 174 | You may act only on Your own behalf and on Your sole responsibility, not on 175 | behalf of any other Contributor, and only if You agree to indemnify, defend, 176 | and hold each Contributor harmless for any liability incurred by, or claims 177 | asserted against, such Contributor by reason of your accepting any such warranty 178 | or additional liability. END OF TERMS AND CONDITIONS 179 | 180 | APPENDIX: How to apply the Apache License to your work. 181 | 182 | To apply the Apache License to your work, attach the following boilerplate 183 | notice, with the fields enclosed by brackets "[]" replaced with your own identifying 184 | information. (Don't include the brackets!) The text should be enclosed in 185 | the appropriate comment syntax for the file format. We also recommend that 186 | a file or class name and description of purpose be included on the same "printed 187 | page" as the copyright notice for easier identification within third-party 188 | archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | 194 | you may not use this file except in compliance with the License. 195 | 196 | You may obtain a copy of the License at 197 | 198 | http://www.apache.org/licenses/LICENSE-2.0 199 | 200 | Unless required by applicable law or agreed to in writing, software 201 | 202 | distributed under the License is distributed on an "AS IS" BASIS, 203 | 204 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 205 | 206 | See the License for the specific language governing permissions and 207 | 208 | limitations under the License. 209 | -------------------------------------------------------------------------------- /MNIST_reader.py: -------------------------------------------------------------------------------- 1 | import os 2 | import struct 3 | import numpy as np 4 | import pickle 5 | 6 | """ 7 | Loosely inspired by http://abel.ee.ucla.edu/cvxopt/_downloads/mnist.py 8 | which is GPL licensed. 9 | """ 10 | 11 | def read(dataset = "training", path = "."): 12 | 13 | if dataset is "training": 14 | fname_img = os.path.join(path, 'train-images-idx3-ubyte') 15 | fname_lbl = os.path.join(path, 'train-labels-idx1-ubyte') 16 | elif dataset is "testing": 17 | fname_img = os.path.join(path, 't10k-images-idx3-ubyte') 18 | fname_lbl = os.path.join(path, 't10k-labels-idx1-ubyte') 19 | else: 20 | raise ValueError, "dataset must be 'testing' or 'training'" 21 | 22 | print(fname_lbl) 23 | 24 | # Load everything in some numpy arrays 25 | with open(fname_lbl, 'rb') as flbl: 26 | magic, num = struct.unpack(">II", flbl.read(8)) 27 | lbl = np.fromfile(flbl, dtype=np.int8) 28 | 29 | with open(fname_img, 'rb') as fimg: 30 | magic, num, rows, cols = struct.unpack(">IIII", fimg.read(16)) 31 | img = np.fromfile(fimg, dtype=np.uint8).reshape(len(lbl), rows, cols) 32 | 33 | 34 | # Reshape and normalize 35 | 36 | img = np.reshape(img, [img.shape[0], img.shape[1]*img.shape[2]])*1.0/255.0 37 | 38 | return img, lbl 39 | 40 | 41 | def get_data(d): 42 | # load the data 43 | x_train, y_train = read('training', d + '/MNIST_original') 44 | x_test, y_test = read('testing', d + '/MNIST_original') 45 | 46 | # create validation set 47 | x_vali = list(x_train[50000:].astype(float)) 48 | y_vali = list(y_train[50000:].astype(float)) 49 | 50 | # create test_set 51 | x_train = x_train[:50000].astype(float) 52 | y_train = y_train[:50000].astype(float) 53 | 54 | # sort test set (to make federated learning non i.i.d.) 55 | indices_train = np.argsort(y_train) 56 | sorted_x_train = list(x_train[indices_train]) 57 | sorted_y_train = list(y_train[indices_train]) 58 | 59 | # create a test set 60 | x_test = list(x_test.astype(float)) 61 | y_test = list(y_test.astype(float)) 62 | 63 | return sorted_x_train, sorted_y_train, x_vali, y_vali, x_test, y_test 64 | 65 | 66 | class Data: 67 | def __init__(self, save_dir, n): 68 | raw_directory = save_dir + '/DATA' 69 | self.client_set = pickle.load(open(raw_directory + '/clients/' + str(n) + '_clients.pkl', 'rb')) 70 | self.sorted_x_train, self.sorted_y_train, self.x_vali, self.y_vali, self.x_test, self.y_test = get_data(save_dir) 71 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017-2019 SAP SE or an SAP affiliate company. All rights reserved. 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Differentially Private Federated Learning: A Client-level Perspective 2 | [![REUSE status](https://api.reuse.software/badge/github.com/SAP-samples/machine-learning-diff-private-federated-learning)](https://api.reuse.software/info/github.com/SAP-samples/machine-learning-diff-private-federated-learning) 3 | [![made-with-python](https://img.shields.io/badge/Made%20with-Python-red.svg)](#python) [![PyPI](https://badge.fury.io/py/tensorflow.svg)](https://badge.fury.io/py/tensorflow) [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) 4 | 5 | ## Description: 6 | Federated Learning is a privacy preserving decentralized learning protocol introduced by Google. Multiple clients jointly learn a model without data centralization. Centralization is pushed from data space to parameter space: https://research.google.com/pubs/pub44822.html [1]. 7 | Differential privacy in deep learning is concerned with preserving privacy of individual data points: https://arxiv.org/abs/1607.00133 [2]. 8 | In this work we combine the notion of both by making federated learning differentially private. We focus on preserving privacy for the entire data set of a client. For more information, please refer to: https://arxiv.org/abs/1712.07557v2. 9 | 10 | This code simulates a federated setting and enables federated learning with differential privacy. The privacy accountant used is from https://arxiv.org/abs/1607.00133 [2]. The files: accountant.py, utils.py, gaussian_moments.py are taken from: https://github.com/tensorflow/models/tree/master/research/differential_privacy 11 | 12 | Note that the privacy agent is not completely set up yet (especially for more than 100 clients). It has to be specified manually or otherwise parameters 'm' and 'sigma' need to be specified. 13 | 14 | #### Authors: 15 | - [Robin C. Geyer](https://www.linkedin.com/in/robin-geyer-419b2513b/) 16 | - [Tassilo Klein](https://tjklein.github.io/) 17 | - [Moin Nabi](https://moinnabi.github.io/) 18 | 19 | ## Requirements 20 | - [Tensorflow 1.4.1](https://www.tensorflow.org/) 21 | - [MNIST data-set](http://yann.lecun.com/exdb/mnist/) 22 | 23 | ## Download and Installation 24 | 1. Install Tensorflow 1.4.1 25 | 2 [Download the files as a ZIP archive](https://github.com/SAP-samples/machine-learning-diff-private-federated-learning/archive/master.zip), or you can [clone the repository](https://help.github.com/articles/cloning-a-repository/) to your local hard drive. 26 | 27 | 3. Change to the directory of the download, If using macOS, simply run: 28 | ```bash 29 | bash RUNME.sh 30 | ``` 31 | This will download the [MNIST data-sets](http://yann.lecun.com/exdb/mnist/), create clients and getting started. 32 | 33 | For more information on the individual functions, please refer to their doc strings. 34 | 35 | ## Known Issues 36 | No issues known 37 | 38 | 39 | ## How to obtain support 40 | This project is provided "as-is" and any bug reports are not guaranteed to be fixed. 41 | 42 | 43 | ## Citations 44 | If you use this code or the pretrained models in your research, 45 | please cite: 46 | 47 | ``` 48 | @ARTICLE{2017arXiv171207557G, 49 | author = {{Geyer}, R.~C. and {Klein}, T. and {Nabi}, M.}, 50 | title = "{Differentially Private Federated Learning: A Client Level Perspective}", 51 | journal = {ArXiv e-prints}, 52 | archivePrefix = "arXiv", 53 | eprint = {1712.07557}, 54 | primaryClass = "cs.CR", 55 | keywords = {Computer Science - Cryptography and Security, Computer Science - Learning, Statistics - Machine Learning}, 56 | year = 2017, 57 | month = dec, 58 | adsurl = {http://adsabs.harvard.edu/abs/2017arXiv171207557G}, 59 | adsnote = {Provided by the SAO/NASA Astrophysics Data System} 60 | } 61 | ``` 62 | 63 | ## References 64 | - H. Brendan McMahan et al., Communication-Efficient Learning of Deep Networks from Decentralized Data, 2017, http://arxiv.org/abs/1602.05629. 65 | 66 | - Martin Abadi et al., Deep Learning with Differential Privacy, 2016, https://arxiv.org/abs/1607.00133. 67 | 68 | 69 | ## License 70 | 71 | Copyright (c) 2024 SAP SE or an SAP affiliate company. All rights reserved. This project is licensed under the Apache Software License, version 2.0 except as noted otherwise in the [LICENSE](/LICENSE) file. 72 | -------------------------------------------------------------------------------- /RUNME.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | STRING="Downloading the MNIST-data set and creating clients" 3 | echo $STRING 4 | eval cd DiffPrivate_FedLearning 5 | eval mkdir MNIST_original 6 | eval cd MNIST_original 7 | eval curl -O "http://yann.lecun.com/exdb/mnist/train-images-idx3-ubyte.gz" 8 | eval curl -O "http://yann.lecun.com/exdb/mnist/train-labels-idx1-ubyte.gz" 9 | eval curl -O "http://yann.lecun.com/exdb/mnist/t10k-images-idx3-ubyte.gz" 10 | eval curl -O "http://yann.lecun.com/exdb/mnist/t10k-labels-idx1-ubyte.gz" 11 | eval gunzip train-images-idx3-ubyte.gz 12 | eval gunzip train-labels-idx1-ubyte.gz 13 | eval gunzip t10k-images-idx3-ubyte.gz 14 | eval gunzip t10k-labels-idx1-ubyte.gz 15 | eval cd .. 16 | eval python Create_clients.py 17 | STRING2="You can now run differentially private federated learning on the MNIST data set. Type python sample.py —-h for help" 18 | echo $STRING2 19 | STRING3="An example: …python sample.py —-N 100… would run differentially private federated learning on 100 clients for a privacy budget of (epsilon = 8, delta = 0.001)" 20 | echo $STRING3 21 | STINRG4="For more information on how to use the the functions please refer to their documentation" 22 | echo $STRING4 23 | -------------------------------------------------------------------------------- /accountant.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 The TensorFlow Authors. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # ============================================================================== 15 | 16 | """Defines Accountant class for keeping track of privacy spending. 17 | A privacy accountant keeps track of privacy spendings. It has methods 18 | accumulate_privacy_spending and get_privacy_spent. Here we only define 19 | AmortizedAccountant which tracks the privacy spending in the amortized 20 | way. It uses privacy amplication via sampling to compute the privacy 21 | spending for each batch and strong composition (specialized for Gaussian 22 | noise) for accumulate the privacy spending. 23 | """ 24 | from __future__ import division 25 | 26 | import abc 27 | import collections 28 | import math 29 | import sys 30 | 31 | import numpy 32 | import tensorflow as tf 33 | import utils 34 | 35 | EpsDelta = collections.namedtuple("EpsDelta", ["spent_eps", "spent_delta"]) 36 | 37 | 38 | # TODO(liqzhang) To ensure the same API for AmortizedAccountant and 39 | # MomentsAccountant, we pass the union of arguments to both, so we 40 | # have unused_sigma for AmortizedAccountant and unused_eps_delta for 41 | # MomentsAccountant. Consider to revise the API to avoid the unused 42 | # arguments. It would be good to use @abc.abstractmethod, etc, to 43 | # define the common interface as a base class. 44 | class AmortizedAccountant(object): 45 | """Keep track of privacy spending in an amortized way. 46 | AmortizedAccountant accumulates the privacy spending by assuming 47 | all the examples are processed uniformly at random so the spending is 48 | amortized among all the examples. And we assume that we use Gaussian noise 49 | so the accumulation is on eps^2 and delta, using advanced composition. 50 | """ 51 | 52 | def __init__(self, total_examples): 53 | """Initialization. Currently only support amortized tracking. 54 | Args: 55 | total_examples: total number of examples. 56 | """ 57 | 58 | assert total_examples > 0 59 | self._total_examples = total_examples 60 | self._eps_squared_sum = tf.Variable(tf.zeros([1]), trainable=False, 61 | name="eps_squared_sum") 62 | self._delta_sum = tf.Variable(tf.zeros([1]), trainable=False, 63 | name="delta_sum") 64 | 65 | def accumulate_privacy_spending(self, eps_delta, unused_sigma, 66 | num_examples): 67 | """Accumulate the privacy spending. 68 | Currently only support approximate privacy. Here we assume we use Gaussian 69 | noise on randomly sampled batch so we get better composition: 1. the per 70 | batch privacy is computed using privacy amplication via sampling bound; 71 | 2. the composition is done using the composition with Gaussian noise. 72 | TODO(liqzhang) Add a link to a document that describes the bounds used. 73 | Args: 74 | eps_delta: EpsDelta pair which can be tensors. 75 | unused_sigma: the noise sigma. Unused for this accountant. 76 | num_examples: the number of examples involved. 77 | Returns: 78 | a TensorFlow operation for updating the privacy spending. 79 | """ 80 | 81 | eps, delta = eps_delta 82 | with tf.control_dependencies( 83 | [tf.Assert(tf.greater(delta, 0), 84 | ["delta needs to be greater than 0"])]): 85 | amortize_ratio = (tf.cast(num_examples, tf.float32) * 1.0 / 86 | self._total_examples) 87 | # Use privacy amplification via sampling bound. 88 | # See Lemma 2.2 in http://arxiv.org/pdf/1405.7085v2.pdf 89 | # TODO(liqzhang) Add a link to a document with formal statement 90 | # and proof. 91 | amortize_eps = tf.reshape(tf.log(1.0 + amortize_ratio * ( 92 | tf.exp(eps) - 1.0)), [1]) 93 | amortize_delta = tf.reshape(amortize_ratio * delta, [1]) 94 | return tf.group(*[tf.assign_add(self._eps_squared_sum, 95 | tf.square(amortize_eps)), 96 | tf.assign_add(self._delta_sum, amortize_delta)]) 97 | 98 | def get_privacy_spent(self, sess, target_eps=None): 99 | """Report the spending so far. 100 | Args: 101 | sess: the session to run the tensor. 102 | target_eps: the target epsilon. Unused. 103 | Returns: 104 | the list containing a single EpsDelta, with values as Python floats (as 105 | opposed to numpy.float64). This is to be consistent with 106 | MomentAccountant which can return a list of (eps, delta) pair. 107 | """ 108 | 109 | # pylint: disable=unused-argument 110 | unused_target_eps = target_eps 111 | eps_squared_sum, delta_sum = sess.run([self._eps_squared_sum, 112 | self._delta_sum]) 113 | return [EpsDelta(math.sqrt(eps_squared_sum), float(delta_sum))] 114 | 115 | 116 | class MomentsAccountant(object): 117 | """Privacy accountant which keeps track of moments of privacy loss. 118 | Note: The constructor of this class creates tf.Variables that must 119 | be initialized with tf.global_variables_initializer() or similar calls. 120 | MomentsAccountant accumulates the high moments of the privacy loss. It 121 | requires a method for computing differenital moments of the noise (See 122 | below for the definition). So every specific accountant should subclass 123 | this class by implementing _differential_moments method. 124 | Denote by X_i the random variable of privacy loss at the i-th step. 125 | Consider two databases D, D' which differ by one item. X_i takes value 126 | log Pr[M(D')==x]/Pr[M(D)==x] with probability Pr[M(D)==x]. 127 | In MomentsAccountant, we keep track of y_i(L) = log E[exp(L X_i)] for some 128 | large enough L. To compute the final privacy spending, we apply Chernoff 129 | bound (assuming the random noise added at each step is independent) to 130 | bound the total privacy loss Z = sum X_i as follows: 131 | Pr[Z > e] = Pr[exp(L Z) > exp(L e)] 132 | < E[exp(L Z)] / exp(L e) 133 | = Prod_i E[exp(L X_i)] / exp(L e) 134 | = exp(sum_i log E[exp(L X_i)]) / exp(L e) 135 | = exp(sum_i y_i(L) - L e) 136 | Hence the mechanism is (e, d)-differentially private for 137 | d = exp(sum_i y_i(L) - L e). 138 | We require d < 1, i.e. e > sum_i y_i(L) / L. We maintain y_i(L) for several 139 | L to compute the best d for any give e (normally should be the lowest L 140 | such that 2 * sum_i y_i(L) / L < e. 141 | We further assume that at each step, the mechanism operates on a random 142 | sample with sampling probability q = batch_size / total_examples. Then 143 | E[exp(L X)] = E[(Pr[M(D)==x / Pr[M(D')==x])^L] 144 | By distinguishing two cases of whether D < D' or D' < D, we have 145 | that 146 | E[exp(L X)] <= max (I1, I2) 147 | where 148 | I1 = (1-q) E ((1-q) + q P(X+1) / P(X))^L + q E ((1-q) + q P(X) / P(X-1))^L 149 | I2 = E (P(X) / ((1-q) + q P(X+1)))^L 150 | In order to compute I1 and I2, one can consider to 151 | 1. use an asymptotic bound, which recovers the advance composition theorem; 152 | 2. use the closed formula (like GaussianMomentsAccountant); 153 | 3. use numerical integration or random sample estimation. 154 | Dependent on the distribution, we can often obtain a tigher estimation on 155 | the moments and hence a more accurate estimation of the privacy loss than 156 | obtained using generic composition theorems. 157 | """ 158 | 159 | __metaclass__ = abc.ABCMeta 160 | 161 | def __init__(self, total_examples, moment_orders=32): 162 | """Initialize a MomentsAccountant. 163 | Args: 164 | total_examples: total number of examples. 165 | moment_orders: the order of moments to keep. 166 | """ 167 | 168 | assert total_examples > 0 169 | self._total_examples = total_examples 170 | self._moment_orders = (moment_orders 171 | if isinstance(moment_orders, (list, tuple)) 172 | else range(1, moment_orders + 1)) 173 | self._max_moment_order = max(self._moment_orders) 174 | assert self._max_moment_order < 100, "The moment order is too large." 175 | self._log_moments = [tf.Variable(numpy.float64(0.0), 176 | trainable=False, 177 | name=("log_moments-%d" % moment_order)) 178 | for moment_order in self._moment_orders] 179 | 180 | @abc.abstractmethod 181 | def _compute_log_moment(self, sigma, q, moment_order): 182 | """Compute high moment of privacy loss. 183 | Args: 184 | sigma: the noise sigma, in the multiples of the sensitivity. 185 | q: the sampling ratio. 186 | moment_order: the order of moment. 187 | Returns: 188 | log E[exp(moment_order * X)] 189 | """ 190 | pass 191 | 192 | def accumulate_privacy_spending(self, unused_eps_delta, 193 | sigma, num_examples): 194 | """Accumulate privacy spending. 195 | In particular, accounts for privacy spending when we assume there 196 | are num_examples, and we are releasing the vector 197 | (sum_{i=1}^{num_examples} x_i) + Normal(0, stddev=l2norm_bound*sigma) 198 | where l2norm_bound is the maximum l2_norm of each example x_i, and 199 | the num_examples have been randomly selected out of a pool of 200 | self.total_examples. 201 | Args: 202 | unused_eps_delta: EpsDelta pair which can be tensors. Unused 203 | in this accountant. 204 | sigma: the noise sigma, in the multiples of the sensitivity (that is, 205 | if the l2norm sensitivity is k, then the caller must have added 206 | Gaussian noise with stddev=k*sigma to the result of the query). 207 | num_examples: the number of examples involved. 208 | Returns: 209 | a TensorFlow operation for updating the privacy spending. 210 | """ 211 | q = tf.cast(num_examples, tf.float64) * 1.0 / self._total_examples 212 | 213 | moments_accum_ops = [] 214 | for i in range(len(self._log_moments)): 215 | moment = self._compute_log_moment(sigma, q, self._moment_orders[i]) 216 | moments_accum_ops.append(tf.assign_add(self._log_moments[i], moment)) 217 | return tf.group(*moments_accum_ops) 218 | 219 | def _compute_delta(self, log_moments, eps): 220 | """Compute delta for given log_moments and eps. 221 | Args: 222 | log_moments: the log moments of privacy loss, in the form of pairs 223 | of (moment_order, log_moment) 224 | eps: the target epsilon. 225 | Returns: 226 | delta 227 | """ 228 | min_delta = 1.0 229 | for moment_order, log_moment in log_moments: 230 | if math.isinf(log_moment) or math.isnan(log_moment): 231 | sys.stderr.write("The %d-th order is inf or Nan\n" % moment_order) 232 | continue 233 | if log_moment < moment_order * eps: 234 | min_delta = min(min_delta, 235 | math.exp(log_moment - moment_order * eps)) 236 | return min_delta 237 | 238 | def _compute_eps(self, log_moments, delta): 239 | min_eps = float("inf") 240 | for moment_order, log_moment in log_moments: 241 | if math.isinf(log_moment) or math.isnan(log_moment): 242 | sys.stderr.write("The %d-th order is inf or Nan\n" % moment_order) 243 | continue 244 | min_eps = min(min_eps, (log_moment - math.log(delta)) / moment_order) 245 | return min_eps 246 | 247 | def get_privacy_spent(self, sess, target_eps=None, target_deltas=None): 248 | """Compute privacy spending in (e, d)-DP form for a single or list of eps. 249 | Args: 250 | sess: the session to run the tensor. 251 | target_eps: a list of target epsilon's for which we would like to 252 | compute corresponding delta value. 253 | target_deltas: a list of target deltas for which we would like to 254 | compute the corresponding eps value. Caller must specify 255 | either target_eps or target_delta. 256 | Returns: 257 | A list of EpsDelta pairs. 258 | """ 259 | assert (target_eps is None) ^ (target_deltas is None) 260 | eps_deltas = [] 261 | log_moments = sess.run(self._log_moments) 262 | log_moments_with_order = zip(self._moment_orders, log_moments) 263 | if target_eps is not None: 264 | for eps in target_eps: 265 | eps_deltas.append( 266 | EpsDelta(eps, self._compute_delta(log_moments_with_order, eps))) 267 | else: 268 | assert target_deltas 269 | for delta in target_deltas: 270 | eps_deltas.append( 271 | EpsDelta(self._compute_eps(log_moments_with_order, delta), delta)) 272 | return eps_deltas 273 | 274 | 275 | class GaussianMomentsAccountant(MomentsAccountant): 276 | """MomentsAccountant which assumes Gaussian noise. 277 | GaussianMomentsAccountant assumes the noise added is centered Gaussian 278 | noise N(0, sigma^2 I). In this case, we can compute the differential moments 279 | accurately using a formula. 280 | For asymptotic bound, for Gaussian noise with variance sigma^2, we can show 281 | for L < sigma^2, q L < sigma, 282 | log E[exp(L X)] = O(q^2 L^2 / sigma^2). 283 | Using this we derive that for training T epoches, with batch ratio q, 284 | the Gaussian mechanism with variance sigma^2 (with q < 1/sigma) is (e, d) 285 | private for d = exp(T/q q^2 L^2 / sigma^2 - L e). Setting L = sigma^2, 286 | Tq = e/2, the mechanism is (e, exp(-e sigma^2/2))-DP. Equivalently, the 287 | mechanism is (e, d)-DP if sigma = sqrt{2 log(1/d)}/e, q < 1/sigma, 288 | and T < e/(2q). This bound is better than the bound obtained using general 289 | composition theorems, by an Omega(sqrt{log k}) factor on epsilon, if we run 290 | k steps. Since we use direct estimate, the obtained privacy bound has tight 291 | constant. 292 | For GaussianMomentAccountant, it suffices to compute I1, as I1 >= I2, 293 | which reduce to computing E(P(x+s)/P(x+s-1) - 1)^i for s = 0 and 1. In the 294 | companion gaussian_moments.py file, we supply procedure for computing both 295 | I1 and I2 (the computation of I2 is through multi-precision integration 296 | package). It can be verified that indeed I1 >= I2 for wide range of parameters 297 | we have tried, though at the moment we are unable to prove this claim. 298 | We recommend that when using this accountant, users independently verify 299 | using gaussian_moments.py that for their parameters, I1 is indeed larger 300 | than I2. This can be done by following the instructions in 301 | gaussian_moments.py. 302 | """ 303 | 304 | def __init__(self, total_examples, moment_orders=32): 305 | """Initialization. 306 | Args: 307 | total_examples: total number of examples. 308 | moment_orders: the order of moments to keep. 309 | """ 310 | super(self.__class__, self).__init__(total_examples, moment_orders) 311 | self._binomial_table = utils.GenerateBinomialTable(self._max_moment_order) 312 | 313 | def _differential_moments(self, sigma, s, t): 314 | """Compute 0 to t-th differential moments for Gaussian variable. 315 | E[(P(x+s)/P(x+s-1)-1)^t] 316 | = sum_{i=0}^t (t choose i) (-1)^{t-i} E[(P(x+s)/P(x+s-1))^i] 317 | = sum_{i=0}^t (t choose i) (-1)^{t-i} E[exp(-i*(2*x+2*s-1)/(2*sigma^2))] 318 | = sum_{i=0}^t (t choose i) (-1)^{t-i} exp(i(i+1-2*s)/(2 sigma^2)) 319 | Args: 320 | sigma: the noise sigma, in the multiples of the sensitivity. 321 | s: the shift. 322 | t: 0 to t-th moment. 323 | Returns: 324 | 0 to t-th moment as a tensor of shape [t+1]. 325 | """ 326 | assert t <= self._max_moment_order, ("The order of %d is out " 327 | "of the upper bound %d." 328 | % (t, self._max_moment_order)) 329 | binomial = tf.slice(self._binomial_table, [0, 0], 330 | [t + 1, t + 1]) 331 | signs = numpy.zeros((t + 1, t + 1), dtype=numpy.float64) 332 | for i in range(t + 1): 333 | for j in range(t + 1): 334 | signs[i, j] = 1.0 - 2 * ((i - j) % 2) 335 | exponents = tf.constant([j * (j + 1.0 - 2.0 * s) / (2.0 * sigma * sigma) 336 | for j in range(t + 1)], dtype=tf.float64) 337 | # x[i, j] = binomial[i, j] * signs[i, j] = (i choose j) * (-1)^{i-j} 338 | x = tf.multiply(binomial, signs) 339 | # y[i, j] = x[i, j] * exp(exponents[j]) 340 | # = (i choose j) * (-1)^{i-j} * exp(j(j-1)/(2 sigma^2)) 341 | # Note: this computation is done by broadcasting pointwise multiplication 342 | # between [t+1, t+1] tensor and [t+1] tensor. 343 | y = tf.multiply(x, tf.exp(exponents)) 344 | # z[i] = sum_j y[i, j] 345 | # = sum_j (i choose j) * (-1)^{i-j} * exp(j(j-1)/(2 sigma^2)) 346 | z = tf.reduce_sum(y, 1) 347 | return z 348 | 349 | def _compute_log_moment(self, sigma, q, moment_order): 350 | """Compute high moment of privacy loss. 351 | Args: 352 | sigma: the noise sigma, in the multiples of the sensitivity. 353 | q: the sampling ratio. 354 | moment_order: the order of moment. 355 | Returns: 356 | log E[exp(moment_order * X)] 357 | """ 358 | assert moment_order <= self._max_moment_order, ("The order of %d is out " 359 | "of the upper bound %d." 360 | % (moment_order, 361 | self._max_moment_order)) 362 | binomial_table = tf.slice(self._binomial_table, [moment_order, 0], 363 | [1, moment_order + 1]) 364 | # qs = [1 q q^2 ... q^L] = exp([0 1 2 ... L] * log(q)) 365 | qs = tf.exp(tf.constant([i * 1.0 for i in range(moment_order + 1)], 366 | dtype=tf.float64) * tf.cast( 367 | tf.log(q), dtype=tf.float64)) 368 | moments0 = self._differential_moments(sigma, 0.0, moment_order) 369 | term0 = tf.reduce_sum(binomial_table * qs * moments0) 370 | moments1 = self._differential_moments(sigma, 1.0, moment_order) 371 | term1 = tf.reduce_sum(binomial_table * qs * moments1) 372 | return tf.squeeze(tf.log(tf.cast(q * term0 + (1.0 - q) * term1, 373 | tf.float64))) 374 | 375 | 376 | class DummyAccountant(object): 377 | """An accountant that does no accounting.""" 378 | 379 | def accumulate_privacy_spending(self, *unused_args): 380 | return tf.no_op() 381 | 382 | def get_privacy_spent(self, unused_sess, **unused_kwargs): 383 | return [EpsDelta(numpy.inf, 1.0)] -------------------------------------------------------------------------------- /gaussian_moments.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 The TensorFlow Authors. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # ============================================================================== 15 | 16 | """A standalone utility for computing the log moments. 17 | The utility for computing the log moments. It consists of two methods. 18 | compute_log_moment(q, sigma, T, lmbd) computes the log moment with sampling 19 | probability q, noise sigma, order lmbd, and T steps. get_privacy_spent computes 20 | delta (or eps) given log moments and eps (or delta). 21 | Example use: 22 | Suppose that we have run an algorithm with parameters, an array of 23 | (q1, sigma1, T1) ... (qk, sigmak, Tk), and we wish to compute eps for a given 24 | delta. The example code would be: 25 | max_lmbd = 32 26 | lmbds = xrange(1, max_lmbd + 1) 27 | log_moments = [] 28 | for lmbd in lmbds: 29 | log_moment = 0 30 | for q, sigma, T in parameters: 31 | log_moment += compute_log_moment(q, sigma, T, lmbd) 32 | log_moments.append((lmbd, log_moment)) 33 | eps, delta = get_privacy_spent(log_moments, target_delta=delta) 34 | To verify that the I1 >= I2 (see comments in GaussianMomentsAccountant in 35 | accountant.py for the context), run the same loop above with verify=True 36 | passed to compute_log_moment. 37 | """ 38 | import math 39 | import sys 40 | 41 | import numpy as np 42 | import scipy.integrate as integrate 43 | import scipy.stats 44 | #from sympy.mpmath import mp 45 | import mpmath as mp 46 | 47 | def _to_np_float64(v): 48 | if math.isnan(v) or math.isinf(v): 49 | return np.inf 50 | return np.float64(v) 51 | 52 | 53 | ###################### 54 | # FLOAT64 ARITHMETIC # 55 | ###################### 56 | 57 | 58 | def pdf_gauss(x, sigma, mean=0): 59 | return scipy.stats.norm.pdf(x, loc=mean, scale=sigma) 60 | 61 | 62 | def cropped_ratio(a, b): 63 | if a < 1E-50 and b < 1E-50: 64 | return 1. 65 | else: 66 | return a / b 67 | 68 | 69 | def integral_inf(fn): 70 | integral, _ = integrate.quad(fn, -np.inf, np.inf) 71 | return integral 72 | 73 | 74 | def integral_bounded(fn, lb, ub): 75 | integral, _ = integrate.quad(fn, lb, ub) 76 | return integral 77 | 78 | 79 | def distributions(sigma, q): 80 | mu0 = lambda y: pdf_gauss(y, sigma=sigma, mean=0.0) 81 | mu1 = lambda y: pdf_gauss(y, sigma=sigma, mean=1.0) 82 | mu = lambda y: (1 - q) * mu0(y) + q * mu1(y) 83 | return mu0, mu1, mu 84 | 85 | 86 | def compute_a(sigma, q, lmbd, verbose=False): 87 | lmbd_int = int(math.ceil(lmbd)) 88 | if lmbd_int == 0: 89 | return 1.0 90 | 91 | a_lambda_first_term_exact = 0 92 | a_lambda_second_term_exact = 0 93 | for i in xrange(lmbd_int + 1): 94 | coef_i = scipy.special.binom(lmbd_int, i) * (q ** i) 95 | s1, s2 = 0, 0 96 | for j in xrange(i + 1): 97 | coef_j = scipy.special.binom(i, j) * (-1) ** (i - j) 98 | s1 += coef_j * np.exp((j * j - j) / (2.0 * (sigma ** 2))) 99 | s2 += coef_j * np.exp((j * j + j) / (2.0 * (sigma ** 2))) 100 | a_lambda_first_term_exact += coef_i * s1 101 | a_lambda_second_term_exact += coef_i * s2 102 | 103 | a_lambda_exact = ((1.0 - q) * a_lambda_first_term_exact + 104 | q * a_lambda_second_term_exact) 105 | if verbose: 106 | print "A: by binomial expansion {} = {} + {}".format( 107 | a_lambda_exact, 108 | (1.0 - q) * a_lambda_first_term_exact, 109 | q * a_lambda_second_term_exact) 110 | return _to_np_float64(a_lambda_exact) 111 | 112 | 113 | def compute_b(sigma, q, lmbd, verbose=False): 114 | mu0, _, mu = distributions(sigma, q) 115 | 116 | b_lambda_fn = lambda z: mu0(z) * np.power(cropped_ratio(mu0(z), mu(z)), lmbd) 117 | b_lambda = integral_inf(b_lambda_fn) 118 | m = sigma ** 2 * (np.log((2. - q) / (1. - q)) + 1. / (2 * sigma ** 2)) 119 | 120 | b_fn = lambda z: (np.power(mu0(z) / mu(z), lmbd) - 121 | np.power(mu(-z) / mu0(z), lmbd)) 122 | if verbose: 123 | print "M =", m 124 | print "f(-M) = {} f(M) = {}".format(b_fn(-m), b_fn(m)) 125 | assert b_fn(-m) < 0 and b_fn(m) < 0 126 | 127 | b_lambda_int1_fn = lambda z: (mu0(z) * 128 | np.power(cropped_ratio(mu0(z), mu(z)), lmbd)) 129 | b_lambda_int2_fn = lambda z: (mu0(z) * 130 | np.power(cropped_ratio(mu(z), mu0(z)), lmbd)) 131 | b_int1 = integral_bounded(b_lambda_int1_fn, -m, m) 132 | b_int2 = integral_bounded(b_lambda_int2_fn, -m, m) 133 | 134 | a_lambda_m1 = compute_a(sigma, q, lmbd - 1) 135 | b_bound = a_lambda_m1 + b_int1 - b_int2 136 | 137 | if verbose: 138 | print "B: by numerical integration", b_lambda 139 | print "B must be no more than ", b_bound 140 | print b_lambda, b_bound 141 | return _to_np_float64(b_lambda) 142 | 143 | 144 | ########################### 145 | # MULTIPRECISION ROUTINES # 146 | ########################### 147 | 148 | 149 | def pdf_gauss_mp(x, sigma, mean): 150 | return mp.mpf(1.) / mp.sqrt(mp.mpf("2.") * sigma ** 2 * mp.pi) * mp.exp( 151 | - (x - mean) ** 2 / (mp.mpf("2.") * sigma ** 2)) 152 | 153 | 154 | def integral_inf_mp(fn): 155 | integral, _ = mp.quad(fn, [-mp.inf, mp.inf], error=True) 156 | return integral 157 | 158 | 159 | def integral_bounded_mp(fn, lb, ub): 160 | integral, _ = mp.quad(fn, [lb, ub], error=True) 161 | return integral 162 | 163 | 164 | def distributions_mp(sigma, q): 165 | mu0 = lambda y: pdf_gauss_mp(y, sigma=sigma, mean=mp.mpf(0)) 166 | mu1 = lambda y: pdf_gauss_mp(y, sigma=sigma, mean=mp.mpf(1)) 167 | mu = lambda y: (1 - q) * mu0(y) + q * mu1(y) 168 | return mu0, mu1, mu 169 | 170 | 171 | def compute_a_mp(sigma, q, lmbd, verbose=False): 172 | lmbd_int = int(math.ceil(lmbd)) 173 | if lmbd_int == 0: 174 | return 1.0 175 | 176 | mu0, mu1, mu = distributions_mp(sigma, q) 177 | a_lambda_fn = lambda z: mu(z) * (mu(z) / mu0(z)) ** lmbd_int 178 | a_lambda_first_term_fn = lambda z: mu0(z) * (mu(z) / mu0(z)) ** lmbd_int 179 | a_lambda_second_term_fn = lambda z: mu1(z) * (mu(z) / mu0(z)) ** lmbd_int 180 | 181 | a_lambda = integral_inf_mp(a_lambda_fn) 182 | a_lambda_first_term = integral_inf_mp(a_lambda_first_term_fn) 183 | a_lambda_second_term = integral_inf_mp(a_lambda_second_term_fn) 184 | 185 | if verbose: 186 | print "A: by numerical integration {} = {} + {}".format( 187 | a_lambda, 188 | (1 - q) * a_lambda_first_term, 189 | q * a_lambda_second_term) 190 | 191 | return _to_np_float64(a_lambda) 192 | 193 | 194 | def compute_b_mp(sigma, q, lmbd, verbose=False): 195 | lmbd_int = int(math.ceil(lmbd)) 196 | if lmbd_int == 0: 197 | return 1.0 198 | 199 | mu0, _, mu = distributions_mp(sigma, q) 200 | 201 | b_lambda_fn = lambda z: mu0(z) * (mu0(z) / mu(z)) ** lmbd_int 202 | b_lambda = integral_inf_mp(b_lambda_fn) 203 | 204 | m = sigma ** 2 * (mp.log((2 - q) / (1 - q)) + 1 / (2 * (sigma ** 2))) 205 | b_fn = lambda z: ((mu0(z) / mu(z)) ** lmbd_int - 206 | (mu(-z) / mu0(z)) ** lmbd_int) 207 | if verbose: 208 | print "M =", m 209 | print "f(-M) = {} f(M) = {}".format(b_fn(-m), b_fn(m)) 210 | assert b_fn(-m) < 0 and b_fn(m) < 0 211 | 212 | b_lambda_int1_fn = lambda z: mu0(z) * (mu0(z) / mu(z)) ** lmbd_int 213 | b_lambda_int2_fn = lambda z: mu0(z) * (mu(z) / mu0(z)) ** lmbd_int 214 | b_int1 = integral_bounded_mp(b_lambda_int1_fn, -m, m) 215 | b_int2 = integral_bounded_mp(b_lambda_int2_fn, -m, m) 216 | 217 | a_lambda_m1 = compute_a_mp(sigma, q, lmbd - 1) 218 | b_bound = a_lambda_m1 + b_int1 - b_int2 219 | 220 | if verbose: 221 | print "B by numerical integration", b_lambda 222 | print "B must be no more than ", b_bound 223 | assert b_lambda < b_bound + 1e-5 224 | return _to_np_float64(b_lambda) 225 | 226 | 227 | def _compute_delta(log_moments, eps): 228 | """Compute delta for given log_moments and eps. 229 | Args: 230 | log_moments: the log moments of privacy loss, in the form of pairs 231 | of (moment_order, log_moment) 232 | eps: the target epsilon. 233 | Returns: 234 | delta 235 | """ 236 | min_delta = 1.0 237 | for moment_order, log_moment in log_moments: 238 | if moment_order == 0: 239 | continue 240 | if math.isinf(log_moment) or math.isnan(log_moment): 241 | sys.stderr.write("The %d-th order is inf or Nan\n" % moment_order) 242 | continue 243 | if log_moment < moment_order * eps: 244 | min_delta = min(min_delta, 245 | math.exp(log_moment - moment_order * eps)) 246 | return min_delta 247 | 248 | 249 | def _compute_eps(log_moments, delta): 250 | """Compute epsilon for given log_moments and delta. 251 | Args: 252 | log_moments: the log moments of privacy loss, in the form of pairs 253 | of (moment_order, log_moment) 254 | delta: the target delta. 255 | Returns: 256 | epsilon 257 | """ 258 | min_eps = float("inf") 259 | for moment_order, log_moment in log_moments: 260 | if moment_order == 0: 261 | continue 262 | if math.isinf(log_moment) or math.isnan(log_moment): 263 | sys.stderr.write("The %d-th order is inf or Nan\n" % moment_order) 264 | continue 265 | min_eps = min(min_eps, (log_moment - math.log(delta)) / moment_order) 266 | return min_eps 267 | 268 | 269 | def compute_log_moment(q, sigma, steps, lmbd, verify=False, verbose=False): 270 | """Compute the log moment of Gaussian mechanism for given parameters. 271 | Args: 272 | q: the sampling ratio. 273 | sigma: the noise sigma. 274 | steps: the number of steps. 275 | lmbd: the moment order. 276 | verify: if False, only compute the symbolic version. If True, computes 277 | both symbolic and numerical solutions and verifies the results match. 278 | verbose: if True, print out debug information. 279 | Returns: 280 | the log moment with type np.float64, could be np.inf. 281 | """ 282 | moment = compute_a(sigma, q, lmbd, verbose=verbose) 283 | if verify: 284 | mp.dps = 50 285 | moment_a_mp = compute_a_mp(sigma, q, lmbd, verbose=verbose) 286 | moment_b_mp = compute_b_mp(sigma, q, lmbd, verbose=verbose) 287 | np.testing.assert_allclose(moment, moment_a_mp, rtol=1e-10) 288 | if not np.isinf(moment_a_mp): 289 | # The following test fails for (1, np.inf)! 290 | np.testing.assert_array_less(moment_b_mp, moment_a_mp) 291 | if np.isinf(moment): 292 | return np.inf 293 | else: 294 | return np.log(moment) * steps 295 | 296 | 297 | def get_privacy_spent(log_moments, target_eps=None, target_delta=None): 298 | """Compute delta (or eps) for given eps (or delta) from log moments. 299 | Args: 300 | log_moments: array of (moment_order, log_moment) pairs. 301 | target_eps: if not None, the epsilon for which we would like to compute 302 | corresponding delta value. 303 | target_delta: if not None, the delta for which we would like to compute 304 | corresponding epsilon value. Exactly one of target_eps and target_delta 305 | is None. 306 | Returns: 307 | eps, delta pair 308 | """ 309 | assert (target_eps is None) ^ (target_delta is None) 310 | assert not ((target_eps is None) and (target_delta is None)) 311 | if target_eps is not None: 312 | return (target_eps, _compute_delta(log_moments, target_eps)) 313 | else: 314 | return (_compute_eps(log_moments, target_delta), target_delta) -------------------------------------------------------------------------------- /mnist_inference.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015 The TensorFlow Authors. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # ============================================================================== 15 | 16 | """Builds the MNIST network. 17 | 18 | Implements the inference/loss/training pattern for model building. 19 | 20 | 1. inference() - Builds the model as far as required for running the network 21 | forward to make predictions. 22 | 2. loss() - Adds to the inference model the layers required to generate loss. 23 | 3. training() - Adds to the loss model the Ops required to generate and 24 | apply gradients. 25 | 26 | This file is used by the various "fully_connected_*.py" files and not meant to 27 | be run. 28 | """ 29 | from __future__ import absolute_import 30 | from __future__ import division 31 | from __future__ import print_function 32 | from scipy.stats import truncnorm 33 | 34 | import numpy as np 35 | import math 36 | 37 | import tensorflow as tf 38 | 39 | # The MNIST dataset has 10 classes, representing the digits 0 through 9. 40 | NUM_CLASSES = 10 41 | 42 | # The MNIST images are always 28x28 pixels. 43 | IMAGE_SIZE = 28 44 | IMAGE_PIXELS = IMAGE_SIZE * IMAGE_SIZE 45 | 46 | def init_weights(size): 47 | # we truncate the normal distribution at two times the standard deviation (which is 2) 48 | # to account for a smaller variance (but the same mean), we multiply the resulting matrix with he desired std 49 | return np.float32(truncnorm.rvs(-2, 2, size=size)*1.0/math.sqrt(float(size[0]))) 50 | 51 | 52 | def inference(images, Hidden1, Hidden2): 53 | 54 | with tf.name_scope('hidden1'): 55 | 56 | weights = tf.Variable(init_weights([IMAGE_PIXELS, Hidden1]), name='weights', dtype=tf.float32) 57 | biases = tf.Variable(np.zeros([Hidden1]),name='biases',dtype=tf.float32) 58 | hidden1 = tf.nn.relu(tf.matmul(images, weights) + biases) 59 | 60 | with tf.name_scope('hidden2'): 61 | 62 | weights = tf.Variable(init_weights([Hidden1, Hidden2]),name='weights',dtype=tf.float32) 63 | biases = tf.Variable(np.zeros([Hidden2]),name='biases',dtype=tf.float32) 64 | hidden2 = tf.nn.relu(tf.matmul(hidden1, weights) + biases) 65 | 66 | with tf.name_scope('out'): 67 | 68 | weights = tf.Variable(init_weights([Hidden2, NUM_CLASSES]), name='weights',dtype=tf.float32) 69 | biases = tf.Variable(np.zeros([NUM_CLASSES]), name='biases',dtype=tf.float32) 70 | logits = tf.matmul(hidden2, weights) + biases 71 | 72 | return logits 73 | 74 | def inference_no_bias(images, Hidden1, Hidden2): 75 | 76 | with tf.name_scope('hidden1'): 77 | 78 | weights = tf.Variable(init_weights([IMAGE_PIXELS, Hidden1]), name='weights', dtype=tf.float32) 79 | hidden1 = tf.nn.relu(tf.matmul(images, weights)) 80 | 81 | with tf.name_scope('hidden2'): 82 | 83 | weights = tf.Variable(init_weights([Hidden1, Hidden2]),name='weights',dtype=tf.float32) 84 | hidden2 = tf.nn.relu(tf.matmul(hidden1, weights)) 85 | 86 | with tf.name_scope('out'): 87 | 88 | weights = tf.Variable(init_weights([Hidden2, NUM_CLASSES]), name='weights',dtype=tf.float32) 89 | logits = tf.matmul(hidden2, weights) 90 | 91 | return logits 92 | 93 | 94 | def loss(logits, labels): 95 | """Calculates the loss from the logits and the labels. 96 | 97 | Args: 98 | logits: Logits tensor, float - [batch_size, NUM_CLASSES]. 99 | labels: Labels tensor, int32 - [batch_size]. 100 | Returns: 101 | loss: Loss tensor of type float. 102 | """ 103 | labels = tf.to_int64(labels) 104 | cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits( 105 | labels=labels, logits=logits, name='xentropy') 106 | return tf.reduce_mean(cross_entropy, name='xentropy_mean') 107 | 108 | 109 | def training(loss, learning_rate): 110 | """Sets up the training Ops. 111 | 112 | Creates a summarizer to track the loss over time in TensorBoard. 113 | 114 | Creates an optimizer and applies the gradients to all trainable variables. 115 | 116 | The Op returned by this function is what must be passed to the 117 | `sess.run()` call to cause the model to train. 118 | 119 | Args: 120 | loss: Loss tensor, from loss(). 121 | learning_rate: The learning rate to use for gradient descent. 122 | 123 | Returns: 124 | train_op: The Op for training. 125 | """ 126 | # Add a scalar summary for the snapshot loss. 127 | tf.summary.scalar('loss', loss) 128 | # Create the gradient descent optimizer with the given learning rate. 129 | optimizer = tf.train.GradientDescentOptimizer(learning_rate) 130 | # Create a variable to track the global step. 131 | global_step = tf.Variable(0, name='global_step', trainable=False) 132 | # Use the optimizer to apply the gradients that minimize the loss 133 | # (and also increment the global step counter) as a single training step. 134 | train_op = optimizer.minimize(loss, global_step=global_step) 135 | return train_op 136 | 137 | def evaluation(logits, labels): 138 | """Evaluate the quality of the logits at predicting the label. 139 | 140 | Args: 141 | logits: Logits tensor, float - [batch_size, NUM_CLASSES]. 142 | labels: Labels tensor, int32 - [batch_size], with values in the 143 | range [0, NUM_CLASSES). 144 | 145 | Returns: 146 | A scalar int32 tensor with the number of examples (out of batch_size) 147 | that were predicted correctly. 148 | """ 149 | # For a classifier model, we can use the in_top_k Op. 150 | # It returns a bool tensor with shape [batch_size] that is true for 151 | # the examples where the label is in the top k (here k=1) 152 | # of all logits for that example. 153 | correct = tf.nn.in_top_k(logits, labels, 1) 154 | # Return the number of true entries. 155 | return tf.reduce_sum(tf.cast(correct, tf.int32)) 156 | 157 | 158 | def placeholder_inputs(batch_size): 159 | """Generate placeholder variables to represent the input tensors. 160 | These placeholders are used as inputs by the rest of the model building 161 | code and will be fed from the downloaded data in the .run() loop, below. 162 | Args: 163 | batch_size: The batch size will be baked into both placeholders. 164 | Returns: 165 | images_placeholder: Images placeholder. 166 | labels_placeholder: Labels placeholder. 167 | """ 168 | # Note that the shapes of the placeholders match the shapes of the full 169 | # image and label tensors, except the first dimension is now batch_size 170 | # rather than the full size of the train or test data sets. 171 | images_placeholder = tf.placeholder(tf.float32, shape=(None,IMAGE_PIXELS), name='images_placeholder') 172 | labels_placeholder = tf.placeholder(tf.int32, shape=(None), name='labels_placeholder') 173 | return images_placeholder, labels_placeholder 174 | 175 | 176 | def mnist_cnn_model(batch_size): 177 | 178 | # - placeholder for the input Data (in our case MNIST), depends on the batch size specified in C 179 | data_placeholder, labels_placeholder = placeholder_inputs(batch_size) 180 | 181 | """Model function for CNN.""" 182 | # Input Layer 183 | input_layer = tf.reshape(data_placeholder, [-1, 28, 28, 1]) 184 | 185 | # Convolutional Layer #1 186 | conv1 = tf.layers.conv2d( 187 | inputs=input_layer, 188 | filters=32, 189 | kernel_size=[5, 5], 190 | padding="same", 191 | activation=tf.nn.relu) 192 | 193 | # Pooling Layer #1 194 | pool1 = tf.layers.max_pooling2d(inputs=conv1, pool_size=[2, 2], strides=2) 195 | 196 | # Convolutional Layer #2 and Pooling Layer #2 197 | conv2 = tf.layers.conv2d( 198 | inputs=pool1, 199 | filters=64, 200 | kernel_size=[5, 5], 201 | padding="same", 202 | activation=tf.nn.relu) 203 | pool2 = tf.layers.max_pooling2d(inputs=conv2, pool_size=[2, 2], strides=2) 204 | 205 | # Dense Layer 206 | pool2_flat = tf.reshape(pool2, [-1, 7 * 7 * 64]) 207 | dense = tf.layers.dense(inputs=pool2_flat, units=1024, activation=tf.nn.relu) 208 | dropout = tf.layers.dropout(inputs=dense, rate=0.4) 209 | 210 | # Logits Layer 211 | logits = tf.layers.dense(inputs=dropout, units=10) 212 | 213 | # Calculate Loss (for both TRAIN and EVAL modes) 214 | loss = tf.losses.sparse_softmax_cross_entropy(labels_placeholder, logits=logits) 215 | 216 | eval_correct = evaluation(logits, labels_placeholder) 217 | 218 | global_step = tf.Variable(0, dtype=tf.float32, trainable=False, name='global_step') 219 | 220 | optimizer = tf.train.GradientDescentOptimizer(learning_rate=0.001) 221 | train_op = optimizer.minimize( 222 | loss=loss, 223 | global_step=global_step) 224 | 225 | return train_op, eval_correct, loss, data_placeholder, labels_placeholder 226 | 227 | 228 | def mnist_fully_connected_model(batch_size, hidden1, hidden2): 229 | # - placeholder for the input Data (in our case MNIST), depends on the batch size specified in C 230 | data_placeholder, labels_placeholder = placeholder_inputs(batch_size) 231 | 232 | # - logits : output of the fully connected neural network when fed with images. The NN's architecture is 233 | # specified in ' 234 | logits = inference_no_bias(data_placeholder, hidden1, hidden2) 235 | 236 | # - loss : when comparing logits to the true labels. 237 | Loss = loss(logits, labels_placeholder) 238 | 239 | # - eval_correct: When run, returns the amount of labels that were predicted correctly. 240 | eval_correct = evaluation(logits, labels_placeholder) 241 | 242 | 243 | # - global_step : A Variable, which tracks the amount of steps taken by the clients: 244 | global_step = tf.Variable(0, dtype=tf.float32, trainable=False, name='global_step') 245 | 246 | # - learning_rate : A tensorflow learning rate, dependent on the global_step variable. 247 | learning_rate = tf.train.exponential_decay(learning_rate=0.1, global_step=global_step, 248 | decay_steps=27000, decay_rate=0.1, 249 | staircase=False, name='learning_rate') 250 | 251 | # - train_op : A tf.train operation, which backpropagates the loss and updates the model according to the 252 | # learning rate specified. 253 | train_op = training(Loss, learning_rate) 254 | 255 | return train_op, eval_correct, Loss, data_placeholder, labels_placeholder 256 | -------------------------------------------------------------------------------- /sample.py: -------------------------------------------------------------------------------- 1 | import tensorflow as tf 2 | import mnist_inference as mnist 3 | import os 4 | from DiffPrivate_FedLearning import run_differentially_private_federated_averaging 5 | from MNIST_reader import Data 6 | import argparse 7 | import sys 8 | 9 | 10 | def sample(N, b,e,m, sigma, eps, save_dir, log_dir): 11 | 12 | # Specs for the model that we would like to train in differentially private federated fashion: 13 | hidden1 = 600 14 | hidden2 = 100 15 | 16 | # Specs for the differentially private federated fashion learning process. 17 | 18 | # A data object that already satisfies client structure and has the following attributes: 19 | # DATA.data_set : A list of labeld training examples. 20 | # DATA.client_set : A 21 | DATA = Data(save_dir, N) 22 | 23 | with tf.Graph().as_default(): 24 | 25 | # Building the model that we would like to train in differentially private federated fashion. 26 | # We will need the tensorflow training operation for that model, its loss and an evaluation method: 27 | 28 | train_op, eval_correct, loss, data_placeholder, labels_placeholder = mnist.mnist_fully_connected_model(b, hidden1, hidden2) 29 | 30 | Accuracy_accountant, Delta_accountant, model = \ 31 | run_differentially_private_federated_averaging(loss, train_op, eval_correct, DATA, data_placeholder, 32 | labels_placeholder, b=b, e=e,m=m, sigma=sigma, eps=eps, 33 | save_dir=save_dir, log_dir=log_dir) 34 | 35 | def main(_): 36 | sample(N=FLAGS.N, b=FLAGS.b, e=FLAGS.e,m=FLAGS.m, sigma=FLAGS.sigma, eps=FLAGS.eps, save_dir=FLAGS.save_dir, log_dir=FLAGS.log_dir) 37 | 38 | if __name__ == '__main__': 39 | parser = argparse.ArgumentParser() 40 | parser.add_argument( 41 | '--save_dir', 42 | type=str, 43 | default=os.getcwd(), 44 | help='directory to store progress' 45 | ) 46 | parser.add_argument( 47 | '--N', 48 | type=int, 49 | default=100, 50 | help='Total Number of clients participating' 51 | ) 52 | parser.add_argument( 53 | '--sigma', 54 | type=float, 55 | default=0, 56 | help='The gm variance parameter; will not affect if Priv_agent is set to True' 57 | ) 58 | parser.add_argument( 59 | '--eps', 60 | type=float, 61 | default=8, 62 | help='Epsilon' 63 | ) 64 | parser.add_argument( 65 | '--m', 66 | type=int, 67 | default=0, 68 | help='Number of clients participating in a round' 69 | ) 70 | parser.add_argument( 71 | '--b', 72 | type=float, 73 | default=10, 74 | help='Batches per client' 75 | ) 76 | parser.add_argument( 77 | '--e', 78 | type=int, 79 | default=4, 80 | help='Epochs per client' 81 | ) 82 | parser.add_argument( 83 | '--log_dir', 84 | type=str, 85 | default=os.path.join(os.getenv('TEST_TMPDIR', '/tmp'), 86 | 'tensorflow/mnist/logs/fully_connected_feed'), 87 | help='Directory to put the log data.' 88 | ) 89 | FLAGS, unparsed = parser.parse_known_args() 90 | tf.app.run(main=main, argv=[sys.argv[0]] + unparsed) 91 | 92 | -------------------------------------------------------------------------------- /utils.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016 The TensorFlow Authors. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # ============================================================================== 15 | 16 | """Utils for building and training NN models. 17 | """ 18 | from __future__ import division 19 | 20 | import math 21 | 22 | import numpy 23 | import tensorflow as tf 24 | 25 | 26 | class LayerParameters(object): 27 | """class that defines a non-conv layer.""" 28 | def __init__(self): 29 | self.name = "" 30 | self.num_units = 0 31 | self._with_bias = False 32 | self.relu = False 33 | self.gradient_l2norm_bound = 0.0 34 | self.bias_gradient_l2norm_bound = 0.0 35 | self.trainable = True 36 | self.weight_decay = 0.0 37 | 38 | 39 | class ConvParameters(object): 40 | """class that defines a conv layer.""" 41 | def __init__(self): 42 | self.patch_size = 5 43 | self.stride = 1 44 | self.in_channels = 1 45 | self.out_channels = 0 46 | self.with_bias = True 47 | self.relu = True 48 | self.max_pool = True 49 | self.max_pool_size = 2 50 | self.max_pool_stride = 2 51 | self.trainable = False 52 | self.in_size = 28 53 | self.name = "" 54 | self.num_outputs = 0 55 | self.bias_stddev = 0.1 56 | 57 | 58 | # Parameters for a layered neural network. 59 | class NetworkParameters(object): 60 | """class that define the overall model structure.""" 61 | def __init__(self): 62 | self.input_size = 0 63 | self.projection_type = 'NONE' # NONE, RANDOM, PCA 64 | self.projection_dimensions = 0 65 | self.default_gradient_l2norm_bound = 0.0 66 | self.layer_parameters = [] # List of LayerParameters 67 | self.conv_parameters = [] # List of ConvParameters 68 | 69 | 70 | def GetTensorOpName(x): 71 | """Get the name of the op that created a tensor. 72 | Useful for naming related tensors, as ':' in name field of op is not permitted 73 | Args: 74 | x: the input tensor. 75 | Returns: 76 | the name of the op. 77 | """ 78 | 79 | t = x.name.rsplit(":", 1) 80 | if len(t) == 1: 81 | return x.name 82 | else: 83 | return t[0] 84 | 85 | 86 | def BuildNetwork(inputs, network_parameters): 87 | """Build a network using the given parameters. 88 | Args: 89 | inputs: a Tensor of floats containing the input data. 90 | network_parameters: NetworkParameters object 91 | that describes the parameters for the network. 92 | Returns: 93 | output, training_parameters: where the outputs (a tensor) is the output 94 | of the network, and training_parameters (a dictionary that maps the 95 | name of each variable to a dictionary of parameters) is the parameters 96 | used during training. 97 | """ 98 | 99 | training_parameters = {} 100 | num_inputs = network_parameters.input_size 101 | outputs = inputs 102 | projection = None 103 | 104 | # First apply convolutions, if needed 105 | for conv_param in network_parameters.conv_parameters: 106 | outputs = tf.reshape( 107 | outputs, 108 | [-1, conv_param.in_size, conv_param.in_size, 109 | conv_param.in_channels]) 110 | conv_weights_name = "%s_conv_weight" % (conv_param.name) 111 | conv_bias_name = "%s_conv_bias" % (conv_param.name) 112 | conv_std_dev = 1.0 / (conv_param.patch_size 113 | * math.sqrt(conv_param.in_channels)) 114 | conv_weights = tf.Variable( 115 | tf.truncated_normal([conv_param.patch_size, 116 | conv_param.patch_size, 117 | conv_param.in_channels, 118 | conv_param.out_channels], 119 | stddev=conv_std_dev), 120 | trainable=conv_param.trainable, 121 | name=conv_weights_name) 122 | conv_bias = tf.Variable( 123 | tf.truncated_normal([conv_param.out_channels], 124 | stddev=conv_param.bias_stddev), 125 | trainable=conv_param.trainable, 126 | name=conv_bias_name) 127 | training_parameters[conv_weights_name] = {} 128 | training_parameters[conv_bias_name] = {} 129 | conv = tf.nn.conv2d(outputs, conv_weights, 130 | strides=[1, conv_param.stride, 131 | conv_param.stride, 1], 132 | padding="SAME") 133 | relud = tf.nn.relu(conv + conv_bias) 134 | mpd = tf.nn.max_pool(relud, ksize=[1, 135 | conv_param.max_pool_size, 136 | conv_param.max_pool_size, 1], 137 | strides=[1, conv_param.max_pool_stride, 138 | conv_param.max_pool_stride, 1], 139 | padding="SAME") 140 | outputs = mpd 141 | num_inputs = conv_param.num_outputs 142 | # this should equal 143 | # in_size * in_size * out_channels / (stride * max_pool_stride) 144 | 145 | # once all the convs are done, reshape to make it flat 146 | outputs = tf.reshape(outputs, [-1, num_inputs]) 147 | 148 | # Now project, if needed 149 | if network_parameters.projection_type is not "NONE": 150 | projection = tf.Variable(tf.truncated_normal( 151 | [num_inputs, network_parameters.projection_dimensions], 152 | stddev=1.0 / math.sqrt(num_inputs)), trainable=False, name="projection") 153 | num_inputs = network_parameters.projection_dimensions 154 | outputs = tf.matmul(outputs, projection) 155 | 156 | # Now apply any other layers 157 | 158 | for layer_parameters in network_parameters.layer_parameters: 159 | num_units = layer_parameters.num_units 160 | hidden_weights_name = "%s_weight" % (layer_parameters.name) 161 | hidden_weights = tf.Variable( 162 | tf.truncated_normal([num_inputs, num_units], 163 | stddev=1.0 / math.sqrt(num_inputs)), 164 | name=hidden_weights_name, trainable=layer_parameters.trainable) 165 | training_parameters[hidden_weights_name] = {} 166 | if layer_parameters.gradient_l2norm_bound: 167 | training_parameters[hidden_weights_name]["gradient_l2norm_bound"] = ( 168 | layer_parameters.gradient_l2norm_bound) 169 | if layer_parameters.weight_decay: 170 | training_parameters[hidden_weights_name]["weight_decay"] = ( 171 | layer_parameters.weight_decay) 172 | 173 | outputs = tf.matmul(outputs, hidden_weights) 174 | if layer_parameters.with_bias: 175 | hidden_biases_name = "%s_bias" % (layer_parameters.name) 176 | hidden_biases = tf.Variable(tf.zeros([num_units]), 177 | name=hidden_biases_name) 178 | training_parameters[hidden_biases_name] = {} 179 | if layer_parameters.bias_gradient_l2norm_bound: 180 | training_parameters[hidden_biases_name][ 181 | "bias_gradient_l2norm_bound"] = ( 182 | layer_parameters.bias_gradient_l2norm_bound) 183 | 184 | outputs += hidden_biases 185 | if layer_parameters.relu: 186 | outputs = tf.nn.relu(outputs) 187 | # num_inputs for the next layer is num_units in the current layer. 188 | num_inputs = num_units 189 | 190 | return outputs, projection, training_parameters 191 | 192 | 193 | def VaryRate(start, end, saturate_epochs, epoch): 194 | """Compute a linearly varying number. 195 | Decrease linearly from start to end until epoch saturate_epochs. 196 | Args: 197 | start: the initial number. 198 | end: the end number. 199 | saturate_epochs: after this we do not reduce the number; if less than 200 | or equal to zero, just return start. 201 | epoch: the current learning epoch. 202 | Returns: 203 | the caculated number. 204 | """ 205 | if saturate_epochs <= 0: 206 | return start 207 | 208 | step = (start - end) / (saturate_epochs - 1) 209 | if epoch < saturate_epochs: 210 | return start - step * epoch 211 | else: 212 | return end 213 | 214 | 215 | def BatchClipByL2norm(t, upper_bound, name=None): 216 | """Clip an array of tensors by L2 norm. 217 | Shrink each dimension-0 slice of tensor (for matrix it is each row) such 218 | that the l2 norm is at most upper_bound. Here we clip each row as it 219 | corresponds to each example in the batch. 220 | Args: 221 | t: the input tensor. 222 | upper_bound: the upperbound of the L2 norm. 223 | name: optional name. 224 | Returns: 225 | the clipped tensor. 226 | """ 227 | 228 | assert upper_bound > 0 229 | with tf.name_scope(values=[t, upper_bound], name=name, 230 | default_name="batch_clip_by_l2norm") as name: 231 | saved_shape = tf.shape(t) 232 | batch_size = tf.slice(saved_shape, [0], [1]) 233 | t2 = tf.reshape(t, tf.concat(axis=0, values=[batch_size, [-1]])) 234 | upper_bound_inv = tf.fill(tf.slice(saved_shape, [0], [1]), 235 | tf.constant(1.0/upper_bound)) 236 | # Add a small number to avoid divide by 0 237 | l2norm_inv = tf.rsqrt(tf.reduce_sum(t2 * t2, [1]) + 0.000001) 238 | scale = tf.minimum(l2norm_inv, upper_bound_inv) * upper_bound 239 | clipped_t = tf.matmul(tf.diag(scale), t2) 240 | clipped_t = tf.reshape(clipped_t, saved_shape, name=name) 241 | return clipped_t 242 | 243 | def L2norm(t): 244 | saved_shape = tf.shape(t) 245 | batch_size = tf.slice(saved_shape, [0], [1]) 246 | t2 = tf.reshape(t, tf.concat(axis=0, values=[batch_size, [-1]])) 247 | return tf.sqrt(tf.reduce_sum(t2 * t2, [1])) 248 | 249 | def SoftThreshold(t, threshold_ratio, name=None): 250 | """Soft-threshold a tensor by the mean value. 251 | Softthreshold each dimension-0 vector (for matrix it is each column) by 252 | the mean of absolute value multiplied by the threshold_ratio factor. Here 253 | we soft threshold each column as it corresponds to each unit in a layer. 254 | Args: 255 | t: the input tensor. 256 | threshold_ratio: the threshold ratio. 257 | name: the optional name for the returned tensor. 258 | Returns: 259 | the thresholded tensor, where each entry is soft-thresholded by 260 | threshold_ratio times the mean of the aboslute value of each column. 261 | """ 262 | 263 | assert threshold_ratio >= 0 264 | with tf.name_scope(values=[t, threshold_ratio], name=name, 265 | default_name="soft_thresholding") as name: 266 | saved_shape = tf.shape(t) 267 | t2 = tf.reshape(t, tf.concat(axis=0, values=[tf.slice(saved_shape, [0], [1]), -1])) 268 | t_abs = tf.abs(t2) 269 | t_x = tf.sign(t2) * tf.nn.relu(t_abs - 270 | (tf.reduce_mean(t_abs, [0], 271 | keep_dims=True) * 272 | threshold_ratio)) 273 | return tf.reshape(t_x, saved_shape, name=name) 274 | 275 | 276 | def AddGaussianNoise(t, sigma, noise_rate, name=None): 277 | """Add i.i.d. Gaussian noise (0, sigma^2) to every entry of t. 278 | Args: 279 | t: the input tensor. 280 | sigma: the stddev of the Gaussian noise. 281 | name: optional name. 282 | Returns: 283 | the noisy tensor. 284 | """ 285 | 286 | with tf.name_scope(values=[t, sigma], name=name, 287 | default_name="add_gaussian_noise") as name: 288 | noisy_t = t + tf.scalar_mul(noise_rate, tf.random_normal(tf.shape(t), stddev=sigma)) 289 | return noisy_t 290 | 291 | 292 | def GenerateBinomialTable(m): 293 | """Generate binomial table. 294 | Args: 295 | m: the size of the table. 296 | Returns: 297 | A two dimensional array T where T[i][j] = (i choose j), 298 | for 0<= i, j <=m. 299 | """ 300 | 301 | table = numpy.zeros((m + 1, m + 1), dtype=numpy.float64) 302 | for i in range(m + 1): 303 | table[i, 0] = 1 304 | for i in range(1, m + 1): 305 | for j in range(1, m + 1): 306 | v = table[i - 1, j] + table[i - 1, j -1] 307 | assert not math.isnan(v) and not math.isinf(v) 308 | table[i, j] = v 309 | return tf.convert_to_tensor(table) --------------------------------------------------------------------------------