accountant.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411
  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. """Defines Accountant class for keeping track of privacy spending.
  16. A privacy accountant keeps track of privacy spendings. It has methods
  17. accumulate_privacy_spending and get_privacy_spent. Here we only define
  18. AmortizedAccountant which tracks the privacy spending in the amortized
  19. way. It uses privacy amplication via sampling to compute the privacy
  20. spending for each batch and strong composition (specialized for Gaussian
  21. noise) for accumulate the privacy spending.
  22. """
  23. from __future__ import division
  24. import abc
  25. import collections
  26. import math
  27. import sys
  28. import numpy
  29. import tensorflow as tf
  30. from differential_privacy.dp_sgd.dp_optimizer import utils
  31. EpsDelta = collections.namedtuple("EpsDelta", ["spent_eps", "spent_delta"])
  32. # TODO(liqzhang) To ensure the same API for AmortizedAccountant and
  33. # MomentsAccountant, we pass the union of arguments to both, so we
  34. # have unused_sigma for AmortizedAccountant and unused_eps_delta for
  35. # MomentsAccountant. Consider to revise the API to avoid the unused
  36. # arguments. It would be good to use @abc.abstractmethod, etc, to
  37. # define the common interface as a base class.
  38. class AmortizedAccountant(object):
  39. """Keep track of privacy spending in an amortized way.
  40. AmortizedAccountant accumulates the privacy spending by assuming
  41. all the examples are processed uniformly at random so the spending is
  42. amortized among all the examples. And we assume that we use Gaussian noise
  43. so the accumulation is on eps^2 and delta, using advanced composition.
  44. """
  45. def __init__(self, total_examples):
  46. """Initialization. Currently only support amortized tracking.
  47. Args:
  48. total_examples: total number of examples.
  49. """
  50. assert total_examples > 0
  51. self._total_examples = total_examples
  52. self._eps_squared_sum = tf.Variable(tf.zeros([1]), trainable=False,
  53. name="eps_squared_sum")
  54. self._delta_sum = tf.Variable(tf.zeros([1]), trainable=False,
  55. name="delta_sum")
  56. def accumulate_privacy_spending(self, eps_delta, unused_sigma,
  57. num_examples):
  58. """Accumulate the privacy spending.
  59. Currently only support approximate privacy. Here we assume we use Gaussian
  60. noise on randomly sampled batch so we get better composition: 1. the per
  61. batch privacy is computed using privacy amplication via sampling bound;
  62. 2. the composition is done using the composition with Gaussian noise.
  63. TODO(liqzhang) Add a link to a document that describes the bounds used.
  64. Args:
  65. eps_delta: EpsDelta pair which can be tensors.
  66. unused_sigma: the noise sigma. Unused for this accountant.
  67. num_examples: the number of examples involved.
  68. Returns:
  69. a TensorFlow operation for updating the privacy spending.
  70. """
  71. eps, delta = eps_delta
  72. with tf.control_dependencies(
  73. [tf.Assert(tf.greater(delta, 0),
  74. ["delta needs to be greater than 0"])]):
  75. amortize_ratio = (tf.cast(num_examples, tf.float32) * 1.0 /
  76. self._total_examples)
  77. # Use privacy amplification via sampling bound.
  78. # See Lemma 2.2 in http://arxiv.org/pdf/1405.7085v2.pdf
  79. # TODO(liqzhang) Add a link to a document with formal statement
  80. # and proof.
  81. amortize_eps = tf.reshape(tf.log(1.0 + amortize_ratio * (
  82. tf.exp(eps) - 1.0)), [1])
  83. amortize_delta = tf.reshape(amortize_ratio * delta, [1])
  84. return tf.group(*[tf.assign_add(self._eps_squared_sum,
  85. tf.square(amortize_eps)),
  86. tf.assign_add(self._delta_sum, amortize_delta)])
  87. def get_privacy_spent(self, sess, target_eps=None):
  88. """Report the spending so far.
  89. Args:
  90. sess: the session to run the tensor.
  91. target_eps: the target epsilon. Unused.
  92. Returns:
  93. the list containing a single EpsDelta, with values as Python floats (as
  94. opposed to numpy.float64). This is to be consistent with
  95. MomentAccountant which can return a list of (eps, delta) pair.
  96. """
  97. # pylint: disable=unused-argument
  98. unused_target_eps = target_eps
  99. eps_squared_sum, delta_sum = sess.run([self._eps_squared_sum,
  100. self._delta_sum])
  101. return [EpsDelta(math.sqrt(eps_squared_sum), float(delta_sum))]
  102. class MomentsAccountant(object):
  103. """Privacy accountant which keeps track of moments of privacy loss.
  104. Note: The constructor of this class creates tf.Variables that must
  105. be initialized with tf.global_variables_initializer() or similar calls.
  106. MomentsAccountant accumulates the high moments of the privacy loss. It
  107. requires a method for computing differenital moments of the noise (See
  108. below for the definition). So every specific accountant should subclass
  109. this class by implementing _differential_moments method.
  110. Denote by X_i the random variable of privacy loss at the i-th step.
  111. Consider two databases D, D' which differ by one item. X_i takes value
  112. log Pr[M(D')==x]/Pr[M(D)==x] with probability Pr[M(D)==x].
  113. In MomentsAccountant, we keep track of y_i(L) = log E[exp(L X_i)] for some
  114. large enough L. To compute the final privacy spending, we apply Chernoff
  115. bound (assuming the random noise added at each step is independent) to
  116. bound the total privacy loss Z = sum X_i as follows:
  117. Pr[Z > e] = Pr[exp(L Z) > exp(L e)]
  118. < E[exp(L Z)] / exp(L e)
  119. = Prod_i E[exp(L X_i)] / exp(L e)
  120. = exp(sum_i log E[exp(L X_i)]) / exp(L e)
  121. = exp(sum_i y_i(L) - L e)
  122. Hence the mechanism is (e, d)-differentially private for
  123. d = exp(sum_i y_i(L) - L e).
  124. We require d < 1, i.e. e > sum_i y_i(L) / L. We maintain y_i(L) for several
  125. L to compute the best d for any give e (normally should be the lowest L
  126. such that 2 * sum_i y_i(L) / L < e.
  127. We further assume that at each step, the mechanism operates on a random
  128. sample with sampling probability q = batch_size / total_examples. Then
  129. E[exp(L X)] = E[(Pr[M(D)==x / Pr[M(D')==x])^L]
  130. By distinguishing two cases of whether D < D' or D' < D, we have
  131. that
  132. E[exp(L X)] <= max (I1, I2)
  133. where
  134. I1 = (1-q) E ((1-q) + q P(X+1) / P(X))^L + q E ((1-q) + q P(X) / P(X-1))^L
  135. I2 = E (P(X) / ((1-q) + q P(X+1)))^L
  136. In order to compute I1 and I2, one can consider to
  137. 1. use an asymptotic bound, which recovers the advance composition theorem;
  138. 2. use the closed formula (like GaussianMomentsAccountant);
  139. 3. use numerical integration or random sample estimation.
  140. Dependent on the distribution, we can often obtain a tigher estimation on
  141. the moments and hence a more accurate estimation of the privacy loss than
  142. obtained using generic composition theorems.
  143. """
  144. __metaclass__ = abc.ABCMeta
  145. def __init__(self, total_examples, moment_orders=32):
  146. """Initialize a MomentsAccountant.
  147. Args:
  148. total_examples: total number of examples.
  149. moment_orders: the order of moments to keep.
  150. """
  151. assert total_examples > 0
  152. self._total_examples = total_examples
  153. self._moment_orders = (moment_orders
  154. if isinstance(moment_orders, (list, tuple))
  155. else range(1, moment_orders + 1))
  156. self._max_moment_order = max(self._moment_orders)
  157. assert self._max_moment_order < 100, "The moment order is too large."
  158. self._log_moments = [tf.Variable(numpy.float64(0.0),
  159. trainable=False,
  160. name=("log_moments-%d" % moment_order))
  161. for moment_order in self._moment_orders]
  162. @abc.abstractmethod
  163. def _compute_log_moment(self, sigma, q, moment_order):
  164. """Compute high moment of privacy loss.
  165. Args:
  166. sigma: the noise sigma, in the multiples of the sensitivity.
  167. q: the sampling ratio.
  168. moment_order: the order of moment.
  169. Returns:
  170. log E[exp(moment_order * X)]
  171. """
  172. pass
  173. def accumulate_privacy_spending(self, unused_eps_delta,
  174. sigma, num_examples):
  175. """Accumulate privacy spending.
  176. In particular, accounts for privacy spending when we assume there
  177. are num_examples, and we are releasing the vector
  178. (sum_{i=1}^{num_examples} x_i) + Normal(0, stddev=l2norm_bound*sigma)
  179. where l2norm_bound is the maximum l2_norm of each example x_i, and
  180. the num_examples have been randomly selected out of a pool of
  181. self.total_examples.
  182. Args:
  183. unused_eps_delta: EpsDelta pair which can be tensors. Unused
  184. in this accountant.
  185. sigma: the noise sigma, in the multiples of the sensitivity (that is,
  186. if the l2norm sensitivity is k, then the caller must have added
  187. Gaussian noise with stddev=k*sigma to the result of the query).
  188. num_examples: the number of examples involved.
  189. Returns:
  190. a TensorFlow operation for updating the privacy spending.
  191. """
  192. q = tf.cast(num_examples, tf.float64) * 1.0 / self._total_examples
  193. moments_accum_ops = []
  194. for i in range(len(self._log_moments)):
  195. moment = self._compute_log_moment(sigma, q, self._moment_orders[i])
  196. moments_accum_ops.append(tf.assign_add(self._log_moments[i], moment))
  197. return tf.group(*moments_accum_ops)
  198. def _compute_delta(self, log_moments, eps):
  199. """Compute delta for given log_moments and eps.
  200. Args:
  201. log_moments: the log moments of privacy loss, in the form of pairs
  202. of (moment_order, log_moment)
  203. eps: the target epsilon.
  204. Returns:
  205. delta
  206. """
  207. min_delta = 1.0
  208. for moment_order, log_moment in log_moments:
  209. if math.isinf(log_moment) or math.isnan(log_moment):
  210. sys.stderr.write("The %d-th order is inf or Nan\n" % moment_order)
  211. continue
  212. if log_moment < moment_order * eps:
  213. min_delta = min(min_delta,
  214. math.exp(log_moment - moment_order * eps))
  215. return min_delta
  216. def _compute_eps(self, log_moments, delta):
  217. min_eps = float("inf")
  218. for moment_order, log_moment in log_moments:
  219. if math.isinf(log_moment) or math.isnan(log_moment):
  220. sys.stderr.write("The %d-th order is inf or Nan\n" % moment_order)
  221. continue
  222. min_eps = min(min_eps, (log_moment - math.log(delta)) / moment_order)
  223. return min_eps
  224. def get_privacy_spent(self, sess, target_eps=None, target_deltas=None):
  225. """Compute privacy spending in (e, d)-DP form for a single or list of eps.
  226. Args:
  227. sess: the session to run the tensor.
  228. target_eps: a list of target epsilon's for which we would like to
  229. compute corresponding delta value.
  230. target_deltas: a list of target deltas for which we would like to
  231. compute the corresponding eps value. Caller must specify
  232. either target_eps or target_delta.
  233. Returns:
  234. A list of EpsDelta pairs.
  235. """
  236. assert (target_eps is None) ^ (target_deltas is None)
  237. eps_deltas = []
  238. log_moments = sess.run(self._log_moments)
  239. log_moments_with_order = zip(self._moment_orders, log_moments)
  240. if target_eps is not None:
  241. for eps in target_eps:
  242. eps_deltas.append(
  243. EpsDelta(eps, self._compute_delta(log_moments_with_order, eps)))
  244. else:
  245. assert target_deltas
  246. for delta in target_deltas:
  247. eps_deltas.append(
  248. EpsDelta(self._compute_eps(log_moments_with_order, delta), delta))
  249. return eps_deltas
  250. class GaussianMomentsAccountant(MomentsAccountant):
  251. """MomentsAccountant which assumes Gaussian noise.
  252. GaussianMomentsAccountant assumes the noise added is centered Gaussian
  253. noise N(0, sigma^2 I). In this case, we can compute the differential moments
  254. accurately using a formula.
  255. For asymptotic bound, for Gaussian noise with variance sigma^2, we can show
  256. for L < sigma^2, q L < sigma,
  257. log E[exp(L X)] = O(q^2 L^2 / sigma^2).
  258. Using this we derive that for training T epoches, with batch ratio q,
  259. the Gaussian mechanism with variance sigma^2 (with q < 1/sigma) is (e, d)
  260. private for d = exp(T/q q^2 L^2 / sigma^2 - L e). Setting L = sigma^2,
  261. Tq = e/2, the mechanism is (e, exp(-e sigma^2/2))-DP. Equivalently, the
  262. mechanism is (e, d)-DP if sigma = sqrt{2 log(1/d)}/e, q < 1/sigma,
  263. and T < e/(2q). This bound is better than the bound obtained using general
  264. composition theorems, by an Omega(sqrt{log k}) factor on epsilon, if we run
  265. k steps. Since we use direct estimate, the obtained privacy bound has tight
  266. constant.
  267. For GaussianMomentAccountant, it suffices to compute I1, as I1 >= I2,
  268. which reduce to computing E(P(x+s)/P(x+s-1) - 1)^i for s = 0 and 1. In the
  269. companion gaussian_moments.py file, we supply procedure for computing both
  270. I1 and I2 (the computation of I2 is through multi-precision integration
  271. package). It can be verified that indeed I1 >= I2 for wide range of parameters
  272. we have tried, though at the moment we are unable to prove this claim.
  273. We recommend that when using this accountant, users independently verify
  274. using gaussian_moments.py that for their parameters, I1 is indeed larger
  275. than I2. This can be done by following the instructions in
  276. gaussian_moments.py.
  277. """
  278. def __init__(self, total_examples, moment_orders=32):
  279. """Initialization.
  280. Args:
  281. total_examples: total number of examples.
  282. moment_orders: the order of moments to keep.
  283. """
  284. super(self.__class__, self).__init__(total_examples, moment_orders)
  285. self._binomial_table = utils.GenerateBinomialTable(self._max_moment_order)
  286. def _differential_moments(self, sigma, s, t):
  287. """Compute 0 to t-th differential moments for Gaussian variable.
  288. E[(P(x+s)/P(x+s-1)-1)^t]
  289. = sum_{i=0}^t (t choose i) (-1)^{t-i} E[(P(x+s)/P(x+s-1))^i]
  290. = sum_{i=0}^t (t choose i) (-1)^{t-i} E[exp(-i*(2*x+2*s-1)/(2*sigma^2))]
  291. = sum_{i=0}^t (t choose i) (-1)^{t-i} exp(i(i+1-2*s)/(2 sigma^2))
  292. Args:
  293. sigma: the noise sigma, in the multiples of the sensitivity.
  294. s: the shift.
  295. t: 0 to t-th moment.
  296. Returns:
  297. 0 to t-th moment as a tensor of shape [t+1].
  298. """
  299. assert t <= self._max_moment_order, ("The order of %d is out "
  300. "of the upper bound %d."
  301. % (t, self._max_moment_order))
  302. binomial = tf.slice(self._binomial_table, [0, 0],
  303. [t + 1, t + 1])
  304. signs = numpy.zeros((t + 1, t + 1), dtype=numpy.float64)
  305. for i in range(t + 1):
  306. for j in range(t + 1):
  307. signs[i, j] = 1.0 - 2 * ((i - j) % 2)
  308. exponents = tf.constant([j * (j + 1.0 - 2.0 * s) / (2.0 * sigma * sigma)
  309. for j in range(t + 1)], dtype=tf.float64)
  310. # x[i, j] = binomial[i, j] * signs[i, j] = (i choose j) * (-1)^{i-j}
  311. x = tf.multiply(binomial, signs)
  312. # y[i, j] = x[i, j] * exp(exponents[j])
  313. # = (i choose j) * (-1)^{i-j} * exp(j(j-1)/(2 sigma^2))
  314. # Note: this computation is done by broadcasting pointwise multiplication
  315. # between [t+1, t+1] tensor and [t+1] tensor.
  316. y = tf.multiply(x, tf.exp(exponents))
  317. # z[i] = sum_j y[i, j]
  318. # = sum_j (i choose j) * (-1)^{i-j} * exp(j(j-1)/(2 sigma^2))
  319. z = tf.reduce_sum(y, 1)
  320. return z
  321. def _compute_log_moment(self, sigma, q, moment_order):
  322. """Compute high moment of privacy loss.
  323. Args:
  324. sigma: the noise sigma, in the multiples of the sensitivity.
  325. q: the sampling ratio.
  326. moment_order: the order of moment.
  327. Returns:
  328. log E[exp(moment_order * X)]
  329. """
  330. assert moment_order <= self._max_moment_order, ("The order of %d is out "
  331. "of the upper bound %d."
  332. % (moment_order,
  333. self._max_moment_order))
  334. binomial_table = tf.slice(self._binomial_table, [moment_order, 0],
  335. [1, moment_order + 1])
  336. # qs = [1 q q^2 ... q^L] = exp([0 1 2 ... L] * log(q))
  337. qs = tf.exp(tf.constant([i * 1.0 for i in range(moment_order + 1)],
  338. dtype=tf.float64) * tf.cast(
  339. tf.log(q), dtype=tf.float64))
  340. moments0 = self._differential_moments(sigma, 0.0, moment_order)
  341. term0 = tf.reduce_sum(binomial_table * qs * moments0)
  342. moments1 = self._differential_moments(sigma, 1.0, moment_order)
  343. term1 = tf.reduce_sum(binomial_table * qs * moments1)
  344. return tf.squeeze(tf.log(tf.cast(q * term0 + (1.0 - q) * term1,
  345. tf.float64)))
  346. class DummyAccountant(object):
  347. """An accountant that does no accounting."""
  348. def accumulate_privacy_spending(self, *unused_args):
  349. return tf.no_op()
  350. def get_privacy_spent(self, unused_sess, **unused_kwargs):
  351. return [EpsDelta(numpy.inf, 1.0)]