├── .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 |
10 |
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 | [](https://api.reuse.software/info/github.com/SAP-samples/machine-learning-diff-private-federated-learning)
3 | [](#python) [](https://badge.fury.io/py/tensorflow) [](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)
--------------------------------------------------------------------------------