├── LICENSE ├── README.md ├── bayesian_privacy_accountant.py ├── cifar10_experiments.ipynb ├── mnist_experiments.ipynb ├── scaled_renyi.py └── synthetic_experiments.ipynb /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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bayesian Differential Privacy 2 | Code for the paper "Bayesian Differential Privacy for Machine Learning" (https://arxiv.org/pdf/1901.09697.pdf). 3 | 4 | The main code for Bayesian accounant is located in ``bayesian_privacy_accounant.py``. 5 | File ``scaled_renyi.py`` contains a function to compute scaled Renyi divergence of two Gaussian distributions with equal variances. In a similar fashion, functions for other distributions can be added to accomodate other privacy mechanisms. 6 | IPython notebooks implement experiments from the paper. 7 | 8 | # Citation 9 | Please cite our paper if find the code helpful: 10 | 11 | ``` 12 | @inproceedings{triastcyn2020bayesian, 13 | author = {Triastcyn, Aleksei and Faltings, Boi}, 14 | title = {Bayesian Differential Privacy for Machine Learning}, 15 | booktitle = {International Conference on Machine Learning}, 16 | year = {2020} 17 | } 18 | ``` 19 | -------------------------------------------------------------------------------- /bayesian_privacy_accountant.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Author: Aleksei Triastcyn 3 | # Date: 12 August 2020 4 | 5 | import itertools 6 | import numpy as np 7 | import scipy as sp 8 | import torch 9 | import warnings 10 | 11 | from scipy.stats import t, binom 12 | from scipy.special import logsumexp 13 | 14 | from scaled_renyi import scaled_renyi_gaussian 15 | 16 | 17 | class BayesianPrivacyAccountant: 18 | """ 19 | Bayesian privacy accountant for Bayesian DP. 20 | See details in A. Triastcyn, B. Faltings (2019). Bayesian Differential Privacy for Machine Learning. 21 | https://arxiv.org/pdf/1901.09697.pdf 22 | """ 23 | 24 | def __init__(self, powers=32, total_steps=1, scaled_renyi_fn=None, conf=1-1e-16, bayesianDP=True): 25 | """ 26 | Creates BayesianPrivacyAccountant object. 27 | 28 | Parameters 29 | ---------- 30 | power : number, required 31 | Order of the Renyi divergence used in accounting. 32 | total_steps : number, optional 33 | Total number of privacy mechanism invocations envisioned. 34 | Only needed if bayesianDP==True. Can be an upper bound. 35 | scaled_renyi_fn : function, required 36 | Function pointer to compute scaled Renyi divergence (defines privacy mechanism). 37 | If None, sampled Gaussian mechanism is assumed. 38 | conf : number, required 39 | Required confidence level in Bayesian accounting (only used if bayesianDP==True). 40 | bayesianDP : boolean, optional 41 | Return Bayesian DP bound. If False, returns the classical DP bound. 42 | """ 43 | self.powers = powers 44 | self.holder_correction = total_steps 45 | self.scaled_renyi_fn = scaled_renyi_gaussian if scaled_renyi_fn is None else scaled_renyi_fn 46 | self.conf = conf 47 | self.bayesianDP = bayesianDP 48 | # total accumulated privacy costs (log of moment generating function) per Renyi order 49 | self.privacy_cost = np.zeros_like(self.powers, dtype=np.float_) 50 | # total accumulated confidence of Bayesian estimator (eventually, incorporated in delta) 51 | self.logconf = 0 52 | # history of minimum privacy cost per iteration 53 | self.history = [] 54 | self.steps_accounted = 0 55 | 56 | 57 | def get_privacy(self, target_eps=None, target_delta=None): 58 | """ 59 | Computes privacy parameters (eps, delta). 60 | 61 | Parameters 62 | ---------- 63 | target_eps : number, required 64 | Target epsilon. 65 | target_delta : number, required 66 | Target delta. 67 | 68 | Returns 69 | ------- 70 | out : tuple 71 | A pair (eps, delta) 72 | """ 73 | if (target_eps is None) and (target_delta is None): 74 | raise ValueError("Both parameters cannot be None") 75 | if (target_eps is not None) and (target_delta is not None): 76 | raise ValueError("One of the parameters has to be None") 77 | if self.steps_accounted > self.holder_correction: 78 | warnings.warn( 79 | f"Accountant invoked for {self.steps_accounted} steps, " 80 | f"but corrected only for {self.holder_correction}. " 81 | "Privacy may be underestimated due to incorrect parameters in Hölder's inequality. " 82 | "To fix the issue, specify the total number of times a privacy mechanism will be " 83 | "invoked in 'total_steps' when creating BayesianPrivacyAccountant." 84 | ) 85 | if target_eps is None: 86 | return np.min((self.privacy_cost - np.log(target_delta - (1 - np.exp(self.logconf)))) / self.powers), target_delta 87 | else: 88 | return target_eps, np.min(np.exp(self.privacy_cost - self.powers * target_eps)) 89 | 90 | 91 | def accumulate(self, ldistr, rdistr, q=1, steps=1): 92 | """ 93 | Accumulates privacy cost for a given number of steps. 94 | 95 | Parameters 96 | ---------- 97 | ldistr : tuple or array, required 98 | Parameters of the left distribution (i.e., imposed by D). 99 | rdistr : tuple or array, required 100 | Parameters of the right distribution (i.e., imposed by D'). 101 | q : number, required 102 | Subsampling probability (for subsampled mechanisms). 103 | steps : number, required 104 | Number of steps/invocations of the privacy mechanism. 105 | 106 | Returns 107 | ------- 108 | out : tuple 109 | Total privacy cost and log confidence. 110 | """ 111 | if np.isscalar(self.powers): 112 | bdp = self._compute_bdp(self.powers, self.scaled_renyi_fn, ldistr, rdistr, q, steps) 113 | else: 114 | bdp = np.array([self._compute_bdp(power, self.scaled_renyi_fn, ldistr, rdistr, q, steps) 115 | for power in self.powers]) 116 | self.privacy_cost += bdp 117 | self.history += [np.min(self.privacy_cost)] 118 | self.logconf += np.log(self.conf) if self.bayesianDP else 0 119 | self.steps_accounted += steps 120 | return self.history[-1], self.logconf 121 | 122 | 123 | def _compute_bdp(self, power, scaled_renyi_fn, ldistr, rdistr, q, steps): 124 | """ 125 | Compute privacy cost for a given number of steps. 126 | 127 | Parameters 128 | ---------- 129 | power : number, required 130 | Order of the Renyi divergence used in privacy cost computation. 131 | scaled_renyi_div_fn : function, required 132 | Function pointer to compute scaled Renyi divergence. 133 | ldistr : tuple or array, required 134 | Parameters of the left distribution (i.e., imposed by D). 135 | rdistr : tuple or array, required 136 | Parameters of the right distribution (i.e., imposed by D'). 137 | q : number, required 138 | Subsampling probability (for subsampled mechanisms). 139 | steps : number, required 140 | Number of steps/invocations of the privacy mechanism. 141 | 142 | Returns 143 | ------- 144 | out : tuple 145 | Total privacy cost and log confidence. 146 | """ 147 | c_L = self._log_binom_expect(power + 1, q, scaled_renyi_fn, ldistr, rdistr) 148 | c_R = self._log_binom_expect(power + 1, q, scaled_renyi_fn, rdistr, ldistr) 149 | logmgf_samples = self.holder_correction * steps * torch.max(c_L, c_R).cpu().numpy() 150 | n_samples = np.size(logmgf_samples) 151 | 152 | if not self.bayesianDP: 153 | return np.asscalar(logmgf_samples) / self.holder_correction 154 | 155 | if n_samples < 3: 156 | raise ValueError("Number of samples is too low for estimating privacy cost.") 157 | 158 | # What we want to compute (numerically unstable): 159 | # mgf_mean = np.mean(np.exp(logmgf)) 160 | # mgf_std = np.std(np.exp(logmgf)) 161 | # self.privacy_cost += np.log(mgf_mean + t.ppf(q=self.conf, df=n_samples-1) * mgf_std / np.sqrt(n_samples - 1)) 162 | 163 | # Numerically stable implementation 164 | max_logmgf = np.max(logmgf_samples) 165 | log_mgf_mean = -np.log(n_samples) + logsumexp(logmgf_samples) 166 | if np.std(logmgf_samples) < np.finfo(np.float).eps: 167 | warnings.warn("Variance of privacy cost samples is 0. Privacy estimate may not be reliable!") 168 | bdp = log_mgf_mean / self.holder_correction 169 | else: 170 | log_mgf_std = 0.5 * (2 * max_logmgf - np.log(n_samples) +\ 171 | np.log(np.sum(np.exp(2 * logmgf_samples - 2 * max_logmgf) -\ 172 | np.exp(2 * log_mgf_mean - 2 * max_logmgf)))) 173 | log_conf_pentalty = np.log(t.ppf(q=self.conf, df=n_samples-1)) + log_mgf_std - 0.5 * np.log(n_samples - 1) 174 | bdp = logsumexp([log_mgf_mean, log_conf_pentalty]) / self.holder_correction 175 | 176 | return bdp 177 | 178 | 179 | def _log_binom_expect(self, n, p, scaled_renyi_fn, ldistr, rdistr): 180 | """ 181 | Computes logarithm of expectation over binomial distribution with parameters (n, p). 182 | 183 | Parameters 184 | ---------- 185 | n : number, required 186 | Number of Bernoulli trials. 187 | p : number, required 188 | Probability of success. 189 | scaled_renyi_fn : function, required 190 | Function pointer to compute scaled Renyi divergence (inside Bernoulli expectation). 191 | ldistr : tuple or array, required 192 | Parameters of the left distribution (i.e., imposed by D). 193 | rdistr : tuple or array, required 194 | Parameters of the right distribution (i.e., imposed by D'). 195 | 196 | Returns 197 | ------- 198 | out : tuple 199 | Logarithm of expectation of scaled_renyi_fn over binomial distribution. 200 | """ 201 | k = torch.arange(n + 1, dtype=torch.float) 202 | log_binom_coefs = torch.tensor(binom.logpmf(k, n=n, p=p)) 203 | return torch.logsumexp(log_binom_coefs + scaled_renyi_fn(k, ldistr, rdistr), dim=1) 204 | -------------------------------------------------------------------------------- /cifar10_experiments.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "import argparse\n", 10 | "import itertools\n", 11 | "import numpy as np\n", 12 | "import pandas as pd\n", 13 | "import os\n", 14 | "import pickle\n", 15 | "import random\n", 16 | "import time\n", 17 | "\n", 18 | "import matplotlib.pyplot as plt\n", 19 | "%matplotlib inline" 20 | ] 21 | }, 22 | { 23 | "cell_type": "code", 24 | "execution_count": null, 25 | "metadata": {}, 26 | "outputs": [], 27 | "source": [ 28 | "import torch\n", 29 | "import torchvision\n", 30 | "import torchvision.transforms as transforms\n", 31 | "\n", 32 | "import torch.nn as nn\n", 33 | "import torch.nn.functional as func\n", 34 | "import torch.optim as optim\n", 35 | "import torch.backends.cudnn as cudnn\n", 36 | "\n", 37 | "from torch.optim.optimizer import required\n", 38 | "from torch.autograd import Variable\n", 39 | "from torch.autograd import Function\n", 40 | "\n", 41 | "from bayesian_privacy_accountant import BayesianPrivacyAccountant" 42 | ] 43 | }, 44 | { 45 | "cell_type": "code", 46 | "execution_count": null, 47 | "metadata": {}, 48 | "outputs": [], 49 | "source": [ 50 | "parser = argparse.ArgumentParser()\n", 51 | "parser.add_argument('--dataset', default='cifar10', help='mnist | cifar10 | svhn')\n", 52 | "parser.add_argument('--dataroot', default='data', help='path to dataset')\n", 53 | "parser.add_argument('--batchSize', type=int, default=512, help='input batch size')\n", 54 | "parser.add_argument('--imageSize', type=int, default=32, help='the height / width of the input image to network')\n", 55 | "parser.add_argument('--nClasses', type=int, default=10, help='number of labels (classes)')\n", 56 | "parser.add_argument('--nChannels', type=int, default=3, help='number of colour channels')\n", 57 | "parser.add_argument('--ndf', type=int, default=64)\n", 58 | "parser.add_argument('--filterSize', type=int, default=5)\n", 59 | "parser.add_argument('--n_epochs', type=int, default=2, help='number of epochs to train for')\n", 60 | "parser.add_argument('--lr', type=float, default=0.001, help='learning rate, default=0.0002')\n", 61 | "parser.add_argument('--C', type=float, default=1.0, help='embedding L2-norm bound, default=1.0')\n", 62 | "parser.add_argument('--sigma', type=float, default=0.8, help='noise variance, default=0.5')\n", 63 | "parser.add_argument('--cuda', action='store_true', help='enables cuda')\n", 64 | "parser.add_argument('--ngpu', type=int, default=1, help='number of GPUs to use')\n", 65 | "parser.add_argument('--outf', default='output', help='folder to output images and model checkpoints')\n", 66 | "parser.add_argument('--manualSeed', type=int, default=2560, help='manual seed for reproducibility')\n", 67 | "\n", 68 | "opt, unknown = parser.parse_known_args()\n", 69 | "\n", 70 | "try:\n", 71 | " os.makedirs(opt.outf)\n", 72 | "except OSError:\n", 73 | " pass\n", 74 | "\n", 75 | "if torch.cuda.is_available():\n", 76 | " opt.cuda = True\n", 77 | " opt.ngpu = 1\n", 78 | " gpu_id = 1\n", 79 | " print(\"Using CUDA: gpu_id = %d\" % gpu_id)\n", 80 | " \n", 81 | "if opt.manualSeed is None:\n", 82 | " opt.manualSeed = random.randint(1, 10000)\n", 83 | "print(\"Random Seed: \", opt.manualSeed)\n", 84 | "random.seed(opt.manualSeed)\n", 85 | "torch.manual_seed(opt.manualSeed)\n", 86 | "if opt.cuda:\n", 87 | " torch.cuda.manual_seed_all(opt.manualSeed)\n", 88 | "\n", 89 | "cudnn.benchmark = True" 90 | ] 91 | }, 92 | { 93 | "cell_type": "code", 94 | "execution_count": null, 95 | "metadata": {}, 96 | "outputs": [], 97 | "source": [ 98 | "class View(nn.Module):\n", 99 | " \"\"\"\n", 100 | " Implements a reshaping module.\n", 101 | " Allows to reshape a tensor between NN layers.\n", 102 | " \"\"\"\n", 103 | " \n", 104 | " def __init__(self, *shape):\n", 105 | " super(View, self).__init__()\n", 106 | " self.shape = shape\n", 107 | " \n", 108 | " def forward(self, input):\n", 109 | " return input.view(self.shape)" 110 | ] 111 | }, 112 | { 113 | "cell_type": "code", 114 | "execution_count": null, 115 | "metadata": {}, 116 | "outputs": [], 117 | "source": [ 118 | "import torch.nn.functional as F\n", 119 | "\n", 120 | "filterSize = 5\n", 121 | "w_out = 5\n", 122 | "h_out = 5\n", 123 | "\n", 124 | "def compute_dim_out(num_conv_layers, dim_in, kernel_size=opt.filterSize, stride=2, padding=0, dilation=1):\n", 125 | " dim_out = dim_in\n", 126 | " for i in range(num_conv_layers):\n", 127 | " dim_out = np.int((dim_out + 2 * padding - dilation * (kernel_size - 1) - 1) / stride + 1)\n", 128 | " return dim_out\n", 129 | "\n", 130 | "class SimpleConvNet(nn.Module):\n", 131 | " \n", 132 | " def __init__(self):\n", 133 | " super(SimpleConvNet, self).__init__()\n", 134 | " \n", 135 | " self.conv = nn.Sequential(\n", 136 | " nn.Conv2d(opt.nChannels, opt.ndf, filterSize),\n", 137 | " nn.ReLU(inplace=True),\n", 138 | " nn.MaxPool2d(2, 2),\n", 139 | " nn.BatchNorm2d(opt.ndf),\n", 140 | " nn.Conv2d(opt.ndf, opt.ndf, filterSize),\n", 141 | " nn.ReLU(inplace=True),\n", 142 | " nn.BatchNorm2d(opt.ndf),\n", 143 | " nn.MaxPool2d(2, 2),\n", 144 | " )\n", 145 | " self.classifier = nn.Sequential(\n", 146 | " View(-1, opt.ndf * w_out * h_out),\n", 147 | " #PrintLayer(\"View\"),\n", 148 | " nn.Linear(opt.ndf * w_out * h_out, 384),\n", 149 | " nn.ReLU(inplace=True),\n", 150 | " nn.Linear(384, 384),\n", 151 | " nn.ReLU(inplace=True),\n", 152 | " nn.Linear(384, opt.nClasses),\n", 153 | " #nn.Sigmoid()\n", 154 | " )\n", 155 | "\n", 156 | " def forward(self, x):\n", 157 | " x = self.conv(x)\n", 158 | " x = self.classifier(x)\n", 159 | " return x" 160 | ] 161 | }, 162 | { 163 | "cell_type": "code", 164 | "execution_count": null, 165 | "metadata": {}, 166 | "outputs": [], 167 | "source": [ 168 | "def validate(student, valset=None):\n", 169 | " \"\"\"\n", 170 | " Compute test accuracy.\n", 171 | " \"\"\"\n", 172 | " real_loss = 0\n", 173 | " real_val_batches = len(valset.dataset) / opt.batchSize + 1\n", 174 | " if valset is not None:\n", 175 | " real_loss = 0\n", 176 | " for images, labels in valset:\n", 177 | " if opt.cuda:\n", 178 | " images = images.cuda(gpu_id)\n", 179 | " labels = labels.cuda(gpu_id)\n", 180 | " \n", 181 | " images, labelv = Variable(images), Variable(labels)\n", 182 | " \n", 183 | " outputs = student(images)\n", 184 | " real_loss += torch.nn.functional.cross_entropy(outputs, labelv).data.cpu().numpy()\n", 185 | " real_loss = real_loss / real_val_batches\n", 186 | " \n", 187 | " return real_loss" 188 | ] 189 | }, 190 | { 191 | "cell_type": "code", 192 | "execution_count": null, 193 | "metadata": { 194 | "scrolled": true 195 | }, 196 | "outputs": [], 197 | "source": [ 198 | "def test(testloader, net):\n", 199 | " \"\"\"\n", 200 | " Compute test accuracy.\n", 201 | " \"\"\"\n", 202 | " correct = 0.0\n", 203 | " total = 0.0\n", 204 | " \n", 205 | " '''\n", 206 | " if opt.cuda:\n", 207 | " net = net.cuda()\n", 208 | " '''\n", 209 | " \n", 210 | " for data in testloader:\n", 211 | " images, labels = data\n", 212 | " \n", 213 | " if opt.cuda:\n", 214 | " images = images.cuda(gpu_id)\n", 215 | " labels = labels.cuda(gpu_id)\n", 216 | " \n", 217 | " outputs = net(Variable(images))\n", 218 | " _, predicted = torch.max(outputs.data, 1)\n", 219 | " total += labels.size(0)\n", 220 | " correct += (predicted == (labels.long().view(-1) % opt.nClasses)).sum()\n", 221 | "\n", 222 | " print('Accuracy of the network on test images: %f %%' % (100 * float(correct) / total))\n", 223 | " return 100 * float(correct) / total" 224 | ] 225 | }, 226 | { 227 | "cell_type": "code", 228 | "execution_count": null, 229 | "metadata": {}, 230 | "outputs": [], 231 | "source": [ 232 | "def sparsify_update(params, p, use_grad_field=True):\n", 233 | " init = True\n", 234 | " for param in params:\n", 235 | " if param is not None:\n", 236 | " if init:\n", 237 | " idx = torch.zeros_like(param, dtype=torch.bool)\n", 238 | " idx.bernoulli_(1 - p)\n", 239 | " if use_grad_field:\n", 240 | " if param.grad is not None:\n", 241 | " idx = torch.zeros_like(param, dtype=torch.bool)\n", 242 | " idx.bernoulli_(1 - p)\n", 243 | " param.grad.data[idx] = 0\n", 244 | " else:\n", 245 | " init = False\n", 246 | " param.data[idx] = 0\n", 247 | " return idx" 248 | ] 249 | }, 250 | { 251 | "cell_type": "code", 252 | "execution_count": null, 253 | "metadata": {}, 254 | "outputs": [], 255 | "source": [ 256 | "def train(trainloader, testloader, student, n_epochs=25, lr=0.0001, accountant=None):\n", 257 | " criterion = nn.CrossEntropyLoss(reduction='none')\n", 258 | " optimizer = optim.Adam(student.parameters(), lr=lr)\n", 259 | " #optimizer = optim.SGD(student.parameters(), lr=lr)\n", 260 | " \n", 261 | " if opt.cuda:\n", 262 | " student = student.cuda(gpu_id)\n", 263 | " criterion = criterion.cuda(gpu_id)\n", 264 | " \n", 265 | " accuracies = []\n", 266 | " \n", 267 | " num_batches = len(trainloader.dataset) / opt.batchSize + 1\n", 268 | " sampling_prob = 1.0\n", 269 | " \n", 270 | " max_grad_norm = opt.C\n", 271 | " sigma = opt.sigma * max_grad_norm\n", 272 | " \n", 273 | " for epoch in range(n_epochs): # loop over the dataset multiple times\n", 274 | " running_loss = 0.0\n", 275 | " for i, data in enumerate(trainloader, 0):\n", 276 | " # get the inputs\n", 277 | " inputs, labels = data\n", 278 | " \n", 279 | " if opt.cuda:\n", 280 | " inputs = inputs.cuda(gpu_id)\n", 281 | " labels = labels.cuda(gpu_id)\n", 282 | " \n", 283 | " batch_size = float(len(inputs))\n", 284 | " \n", 285 | " inputv = Variable(inputs)\n", 286 | " labelv = Variable(labels.long().view(-1) % opt.nClasses)\n", 287 | " \n", 288 | " # zero the parameter gradients\n", 289 | " optimizer.zero_grad()\n", 290 | " \n", 291 | " # forward + backward + optimize\n", 292 | " outputs = student(inputv)\n", 293 | " loss = criterion(outputs, labelv)\n", 294 | " \n", 295 | " if accountant:\n", 296 | " grads_est = []\n", 297 | " num_subbatch = 8\n", 298 | " for j in range(num_subbatch):\n", 299 | " grad_sample = torch.autograd.grad(loss[np.delete(range(int(batch_size)), j)].mean(), [p for p in student.parameters() if p.requires_grad], retain_graph=True)\n", 300 | " with torch.no_grad():\n", 301 | " grad_sample = torch.cat([g.view(-1) for g in grad_sample])\n", 302 | " #grad_sample /= max(1.0, grad_sample.norm().item() / max_grad_norm)\n", 303 | " grads_est += [grad_sample]\n", 304 | " with torch.no_grad():\n", 305 | " sparsify_update(grads_est, p=sampling_prob, use_grad_field=False)\n", 306 | " \n", 307 | " (loss.mean()).backward()\n", 308 | " running_loss += loss.mean().item()\n", 309 | " \n", 310 | " if accountant:\n", 311 | " #torch.nn.utils.clip_grad_norm_(student.parameters(), max_grad_norm)\n", 312 | " for group in optimizer.param_groups:\n", 313 | " for p in group['params']:\n", 314 | " if p.grad is not None:\n", 315 | " p.grad.data += torch.randn_like(p.grad) * sigma #* max_grad_norm\n", 316 | " sparsify_update(student.parameters(), p=sampling_prob)\n", 317 | " \n", 318 | " \n", 319 | " optimizer.step()\n", 320 | " \n", 321 | " if accountant:\n", 322 | " with torch.no_grad():\n", 323 | " q = batch_size / len(trainloader.dataset)\n", 324 | " # NOTE: \n", 325 | " # Using combinations within a set of gradients (like below)\n", 326 | " # does not actually produce samples from the correct distribution\n", 327 | " # (for that, we need to sample pairs of gradients independently).\n", 328 | " # However, the difference is not significant, and it speeds up computations.\n", 329 | " grad_pairs = list(zip(*itertools.combinations(grads_est, 2)))\n", 330 | " accountant.accumulate(\n", 331 | " ldistr=(torch.stack(grad_pairs[0]), opt.sigma*max_grad_norm),\n", 332 | " rdistr=(torch.stack(grad_pairs[1]), opt.sigma*max_grad_norm),\n", 333 | " q=q, \n", 334 | " steps=1,\n", 335 | " )\n", 336 | " running_eps = accountant.get_privacy(target_delta=1e-5)\n", 337 | " \n", 338 | " # print training stats every epoch\n", 339 | " running_eps = accountant.get_privacy(target_delta=1e-5) if accountant else None\n", 340 | " print(\"Epoch: %d/%d. Loss: %.3f. Privacy (𝜀,𝛿): %s\" %\n", 341 | " (epoch + 1, n_epochs, running_loss / len(trainloader), running_eps))\n", 342 | "\n", 343 | " student.eval()\n", 344 | " acc = test(testloader, student)\n", 345 | " accuracies += [acc]\n", 346 | " student.train()\n", 347 | " print(\"Test accuracy is %d %%\" % acc)\n", 348 | " save_step = 100\n", 349 | " if (epoch + 1) % save_step == 0:\n", 350 | " torch.save(student.state_dict(), '%s/private_net_epoch_%d.pth' % (opt.outf, epoch + 1))\n", 351 | " pickle.dump(accountant, open('%s/bayes_accountant_epoch_%d' % (opt.outf, epoch + 1), 'wb'))\n", 352 | "\n", 353 | " print('Finished Training')\n", 354 | " return student.cpu(), accuracies" 355 | ] 356 | }, 357 | { 358 | "cell_type": "code", 359 | "execution_count": null, 360 | "metadata": {}, 361 | "outputs": [], 362 | "source": [ 363 | "# transformations applied to data\n", 364 | "transform = transforms.Compose([transforms.Resize(size=(128, 128)),\n", 365 | " transforms.RandomHorizontalFlip(), \n", 366 | " transforms.ColorJitter(),\n", 367 | " transforms.ToTensor(),\n", 368 | " transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])])\n", 369 | "transform_test = transforms.Compose([transforms.Resize(size=(128, 128)),\n", 370 | " transforms.ToTensor(),\n", 371 | " transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])])\n", 372 | "\n", 373 | "# switch datasets\n", 374 | "if opt.dataset == 'mnist':\n", 375 | " trainset = torchvision.datasets.MNIST(root=opt.dataroot + os.sep + opt.dataset, train=True, download=True, transform=transform)\n", 376 | " testset = torchvision.datasets.MNIST(root=opt.dataroot + os.sep + opt.dataset, train=False, download=True, transform=transform)\n", 377 | "elif opt.dataset == 'cifar10':\n", 378 | " trainset = torchvision.datasets.CIFAR10(root=opt.dataroot + os.sep + opt.dataset, train=True, download=True, transform=transform)\n", 379 | " testset = torchvision.datasets.CIFAR10(root=opt.dataroot + os.sep + opt.dataset, train=False, download=True, transform=transform_test)\n", 380 | " trainset100 = torchvision.datasets.CIFAR100(root=opt.dataroot + os.sep + opt.dataset, train=True, download=True, transform=transform)\n", 381 | " testset100 = torchvision.datasets.CIFAR100(root=opt.dataroot + os.sep + opt.dataset, train=False, download=True, transform=transform_test)\n", 382 | "elif opt.dataset == 'svhn':\n", 383 | " trainset = torchvision.datasets.SVHN(root=opt.dataroot + os.sep + opt.dataset, split='train', download=True, transform=transform)\n", 384 | " testset = torchvision.datasets.SVHN(root=opt.dataroot + os.sep + opt.dataset, split='test', download=True, transform=transform)\n", 385 | " \n", 386 | "# initialise data loaders\n", 387 | "trainloader = torch.utils.data.DataLoader(trainset, batch_size=opt.batchSize, shuffle=True, num_workers=2, drop_last=True)\n", 388 | "testloader = torch.utils.data.DataLoader(testset, batch_size=opt.batchSize, shuffle=False, num_workers=2)\n", 389 | "if opt.dataset == 'cifar10':\n", 390 | " trainloader100 = torch.utils.data.DataLoader(trainset100, batch_size=opt.batchSize, shuffle=True, num_workers=2, drop_last=True)\n", 391 | " testloader100 = torch.utils.data.DataLoader(testset100, batch_size=opt.batchSize, shuffle=False, num_workers=2)" 392 | ] 393 | }, 394 | { 395 | "cell_type": "code", 396 | "execution_count": null, 397 | "metadata": { 398 | "scrolled": true 399 | }, 400 | "outputs": [], 401 | "source": [ 402 | "# train GAN and measure time\n", 403 | "start = time.time()\n", 404 | "\n", 405 | "netS = torchvision.models.vgg16(pretrained=True)\n", 406 | "netS.train()\n", 407 | "# Disable updates for feature extraction layers\n", 408 | "for p in netS.named_parameters():\n", 409 | " p[1].requires_grad = False\n", 410 | "last_layers = [p for p in netS.parameters()][-2:]\n", 411 | "netS.classifier[-1] = nn.Linear(4096, 10)\n", 412 | "last_layers[-1] = nn.Parameter(last_layers[-1][:10])\n", 413 | "last_layers[-2] = nn.Parameter(last_layers[-2][:10])\n", 414 | "for ll in last_layers:\n", 415 | " ll.requires_grad = True\n", 416 | "netS.aux_logits = False\n", 417 | "\n", 418 | "total_steps = opt.n_epochs * len(trainloader)\n", 419 | "bayes_accountant = BayesianPrivacyAccountant(powers=32, total_steps=total_steps)\n", 420 | "netS, accs = train(trainloader, testloader, netS, lr=0.001, n_epochs=opt.n_epochs, accountant=bayes_accountant)\n", 421 | "\n", 422 | "stop = time.time()\n", 423 | "print(\"Time elapsed: %f\" % (stop - start))" 424 | ] 425 | }, 426 | { 427 | "cell_type": "code", 428 | "execution_count": null, 429 | "metadata": {}, 430 | "outputs": [], 431 | "source": [ 432 | "print(\"Epsilon = \", bayes_accountant.get_privacy(target_delta=1e-10))" 433 | ] 434 | }, 435 | { 436 | "cell_type": "code", 437 | "execution_count": null, 438 | "metadata": {}, 439 | "outputs": [], 440 | "source": [ 441 | "from scipy.spatial.distance import pdist\n", 442 | "\n", 443 | "loss_fn = nn.CrossEntropyLoss(reduction='none')\n", 444 | "\n", 445 | "netS.cuda(gpu_id)\n", 446 | "loss_fn.cuda(gpu_id)\n", 447 | "\n", 448 | "dists_train = []\n", 449 | "dists_test = []\n", 450 | "\n", 451 | "i = 0\n", 452 | "for inputs, labels in trainloader:\n", 453 | " i += 1\n", 454 | " if i > 10:\n", 455 | " break\n", 456 | " inputs = inputs.cuda(gpu_id)\n", 457 | " labels = labels.cuda(gpu_id)\n", 458 | " netS.zero_grad()\n", 459 | " outputs = netS(inputs)\n", 460 | " loss = loss_fn(outputs, labels)\n", 461 | " \n", 462 | " grads_est = []\n", 463 | " num_subbatch = 100\n", 464 | " for j in range(num_subbatch):\n", 465 | " grad_sample = torch.autograd.grad(loss[np.delete(range(int(opt.batchSize)), j)].mean(), [p for p in netS.parameters() if p.requires_grad], retain_graph=True)\n", 466 | " with torch.no_grad():\n", 467 | " grad_sample = torch.cat([g.view(-1) for g in grad_sample])\n", 468 | " grad_sample /= max(1.0, grad_sample.norm().item() / opt.C)\n", 469 | " grads_est += [grad_sample]\n", 470 | " with torch.no_grad():\n", 471 | " grads_est = torch.stack(grads_est)\n", 472 | " #sparsify_update(grads_est, p=sampling_prob, use_grad_field=False)\n", 473 | " q = opt.batchSize / len(trainloader.dataset)\n", 474 | " dists_train += [pdist(grads_est.cpu())]\n", 475 | " \n", 476 | "i = 0\n", 477 | "for inputs, labels in testloader:\n", 478 | " i += 1\n", 479 | " if i > 10:\n", 480 | " break\n", 481 | " inputs = inputs.cuda(gpu_id)\n", 482 | " labels = labels.cuda(gpu_id)\n", 483 | " netS.zero_grad()\n", 484 | " outputs = netS(inputs)\n", 485 | " loss = loss_fn(outputs, labels)\n", 486 | " \n", 487 | " grads_est = []\n", 488 | " num_subbatch = 100\n", 489 | " for j in range(num_subbatch):\n", 490 | " grad_sample = torch.autograd.grad(loss[np.delete(range(int(opt.batchSize)), j)].mean(), [p for p in netS.parameters() if p.requires_grad], retain_graph=True)\n", 491 | " with torch.no_grad():\n", 492 | " grad_sample = torch.cat([g.view(-1) for g in grad_sample])\n", 493 | " grad_sample /= max(1.0, grad_sample.norm().item() / opt.C)\n", 494 | " grads_est += [grad_sample]\n", 495 | " with torch.no_grad():\n", 496 | " grads_est = torch.stack(grads_est)\n", 497 | " #sparsify_update(grads_est, p=sampling_prob, use_grad_field=False)\n", 498 | " q = opt.batchSize / len(trainloader.dataset)\n", 499 | " dists_test += [pdist(grads_est.cpu())]" 500 | ] 501 | }, 502 | { 503 | "cell_type": "code", 504 | "execution_count": null, 505 | "metadata": {}, 506 | "outputs": [], 507 | "source": [ 508 | "dists_train = np.stack(dists_train).squeeze()\n", 509 | "dists_test = np.stack(dists_test).squeeze()\n", 510 | "\n", 511 | "plt.hist(dists_train.flatten(), bins=np.arange(0, 0.2, 0.005), label='Train', alpha=0.5)\n", 512 | "plt.hist(dists_test.flatten(), bins=np.arange(0, 0.2, 0.005), label='Test', alpha=0.5)\n", 513 | "plt.legend()\n", 514 | "plt.xlabel(r'Distance')\n", 515 | "plt.ylabel(r'Number of samples')\n", 516 | "plt.title(r'Pairwise gradient distances distribution, CIFAR10')\n", 517 | "plt.savefig('grad_dist_histogram_cifar10.pdf', format='pdf', bbox_inches='tight')" 518 | ] 519 | }, 520 | { 521 | "cell_type": "code", 522 | "execution_count": null, 523 | "metadata": {}, 524 | "outputs": [], 525 | "source": [ 526 | "from scipy.stats import ttest_rel, ttest_ind, levene\n", 527 | "\n", 528 | "print(ttest_rel(dists_train.flatten(), dists_test.flatten()))\n", 529 | "print(ttest_ind(dists_train.flatten(), dists_test.flatten()))\n", 530 | "print(levene(dists_train.flatten(), dists_test.flatten()))" 531 | ] 532 | }, 533 | { 534 | "cell_type": "code", 535 | "execution_count": null, 536 | "metadata": {}, 537 | "outputs": [], 538 | "source": [] 539 | } 540 | ], 541 | "metadata": { 542 | "kernelspec": { 543 | "display_name": "Python 3", 544 | "language": "python", 545 | "name": "python3" 546 | }, 547 | "language_info": { 548 | "codemirror_mode": { 549 | "name": "ipython", 550 | "version": 3 551 | }, 552 | "file_extension": ".py", 553 | "mimetype": "text/x-python", 554 | "name": "python", 555 | "nbconvert_exporter": "python", 556 | "pygments_lexer": "ipython3", 557 | "version": "3.6.9" 558 | } 559 | }, 560 | "nbformat": 4, 561 | "nbformat_minor": 2 562 | } 563 | -------------------------------------------------------------------------------- /mnist_experiments.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "import argparse\n", 10 | "import itertools\n", 11 | "import numpy as np\n", 12 | "import pandas as pd\n", 13 | "import os\n", 14 | "import random\n", 15 | "import time\n", 16 | "\n", 17 | "import matplotlib.pyplot as plt\n", 18 | "%matplotlib inline" 19 | ] 20 | }, 21 | { 22 | "cell_type": "code", 23 | "execution_count": null, 24 | "metadata": {}, 25 | "outputs": [], 26 | "source": [ 27 | "import torch\n", 28 | "import torchvision\n", 29 | "import torchvision.transforms as transforms\n", 30 | "\n", 31 | "import torch.nn as nn\n", 32 | "import torch.nn.functional as func\n", 33 | "import torch.optim as optim\n", 34 | "import torch.backends.cudnn as cudnn\n", 35 | "\n", 36 | "from torch.optim.optimizer import required\n", 37 | "from torch.autograd import Variable\n", 38 | "from torch.autograd import Function\n", 39 | "\n", 40 | "from bayesian_privacy_accountant import BayesianPrivacyAccountant" 41 | ] 42 | }, 43 | { 44 | "cell_type": "code", 45 | "execution_count": null, 46 | "metadata": {}, 47 | "outputs": [], 48 | "source": [ 49 | "parser = argparse.ArgumentParser()\n", 50 | "parser.add_argument('--dataset', default='mnist', help='mnist | cifar10 | svhn')\n", 51 | "parser.add_argument('--dataroot', default='data', help='path to dataset')\n", 52 | "parser.add_argument('--batchSize', type=int, default=1024, help='input batch size')\n", 53 | "parser.add_argument('--imageSize', type=int, default=28, help='the height / width of the input image to network')\n", 54 | "parser.add_argument('--nClasses', type=int, default=10, help='number of labels (classes)')\n", 55 | "parser.add_argument('--nChannels', type=int, default=1, help='number of colour channels')\n", 56 | "parser.add_argument('--ndf', type=int, default=64, help='number of filters in CNN')\n", 57 | "parser.add_argument('--n_epochs', type=int, default=32, help='number of epochs to train for')\n", 58 | "parser.add_argument('--lr', type=float, default=0.001, help='learning rate, default=0.0002')\n", 59 | "parser.add_argument('--C', type=float, default=1.0, help='embedding L2-norm bound, default=1.0')\n", 60 | "parser.add_argument('--sigma', type=float, default=0.1, help='noise variance, default=0.5')\n", 61 | "parser.add_argument('--cuda', action='store_true', help='enables cuda')\n", 62 | "parser.add_argument('--ngpu', type=int, default=1, help='number of GPUs to use')\n", 63 | "parser.add_argument('--outf', default='output', help='folder to output images and model checkpoints')\n", 64 | "parser.add_argument('--manualSeed', type=int, default=8664, help='manual seed for reproducibility')\n", 65 | "\n", 66 | "opt, unknown = parser.parse_known_args()\n", 67 | "\n", 68 | "try:\n", 69 | " os.makedirs(opt.outf)\n", 70 | "except OSError:\n", 71 | " pass\n", 72 | "\n", 73 | "if torch.cuda.is_available():\n", 74 | " opt.cuda = True\n", 75 | " opt.ngpu = 1\n", 76 | " gpu_id = 0\n", 77 | " print(\"Using CUDA: gpu_id = %d\" % gpu_id)\n", 78 | " \n", 79 | "if opt.manualSeed is None:\n", 80 | " opt.manualSeed = random.randint(1, 10000)\n", 81 | "print(\"Random Seed: \", opt.manualSeed)\n", 82 | "random.seed(opt.manualSeed)\n", 83 | "torch.manual_seed(opt.manualSeed)\n", 84 | "if opt.cuda:\n", 85 | " torch.cuda.manual_seed_all(opt.manualSeed)" 86 | ] 87 | }, 88 | { 89 | "cell_type": "code", 90 | "execution_count": null, 91 | "metadata": {}, 92 | "outputs": [], 93 | "source": [ 94 | "class View(nn.Module):\n", 95 | " \"\"\"\n", 96 | " Implements a reshaping module.\n", 97 | " Allows to reshape a tensor between NN layers.\n", 98 | " \"\"\"\n", 99 | " \n", 100 | " def __init__(self, *shape):\n", 101 | " super(View, self).__init__()\n", 102 | " self.shape = shape\n", 103 | " \n", 104 | " def forward(self, input):\n", 105 | " return input.view(self.shape)" 106 | ] 107 | }, 108 | { 109 | "cell_type": "code", 110 | "execution_count": null, 111 | "metadata": {}, 112 | "outputs": [], 113 | "source": [ 114 | "filterSize = 5\n", 115 | "w_out = 4\n", 116 | "h_out = 4\n", 117 | "\n", 118 | "class SimpleConvNet(nn.Module):\n", 119 | " \n", 120 | " def __init__(self):\n", 121 | " super(SimpleConvNet, self).__init__()\n", 122 | " \n", 123 | " self.main = nn.Sequential(\n", 124 | " nn.Conv2d(opt.nChannels, opt.ndf, filterSize),\n", 125 | " nn.ReLU(inplace=True),\n", 126 | " nn.MaxPool2d(2, 2),\n", 127 | " nn.BatchNorm2d(opt.ndf),\n", 128 | " nn.Conv2d(opt.ndf, opt.ndf, filterSize),\n", 129 | " nn.ReLU(inplace=True),\n", 130 | " nn.BatchNorm2d(opt.ndf),\n", 131 | " nn.MaxPool2d(2, 2),\n", 132 | " View(-1, opt.ndf * w_out * h_out),\n", 133 | " #PrintLayer(\"View\"),\n", 134 | " #View(-1, 784),\n", 135 | " nn.Linear(opt.ndf * w_out * h_out, 384),\n", 136 | " nn.SELU(inplace=True),\n", 137 | " nn.Linear(384, 192),\n", 138 | " nn.SELU(inplace=True),\n", 139 | " nn.Linear(192, opt.nClasses),\n", 140 | " #nn.Sigmoid()\n", 141 | " )\n", 142 | "\n", 143 | " def forward(self, x):\n", 144 | " return self.main(x)" 145 | ] 146 | }, 147 | { 148 | "cell_type": "code", 149 | "execution_count": null, 150 | "metadata": { 151 | "scrolled": true 152 | }, 153 | "outputs": [], 154 | "source": [ 155 | "def test(testloader, net):\n", 156 | " \"\"\"\n", 157 | " Compute test accuracy.\n", 158 | " \"\"\"\n", 159 | " correct = 0.0\n", 160 | " total = 0.0\n", 161 | " \n", 162 | " '''\n", 163 | " if opt.cuda:\n", 164 | " net = net.cuda()\n", 165 | " '''\n", 166 | " \n", 167 | " for data in testloader:\n", 168 | " images, labels = data\n", 169 | " \n", 170 | " if opt.cuda:\n", 171 | " images = images.cuda(gpu_id)\n", 172 | " labels = labels.cuda(gpu_id)\n", 173 | " \n", 174 | " outputs = net(Variable(images))\n", 175 | " _, predicted = torch.max(outputs.data, 1)\n", 176 | " #print(predicted.cpu().numpy())\n", 177 | " #print(labels.cpu().numpy())\n", 178 | " total += labels.size(0)\n", 179 | " correct += (predicted == (labels.long().view(-1) % 10)).sum()\n", 180 | " #print torch.cat([predicted.view(-1, 1), (labels.long() % 10)], dim=1)\n", 181 | "\n", 182 | " print('Accuracy of the network on test images: %f %%' % (100 * float(correct) / total))\n", 183 | " return 100 * float(correct) / total" 184 | ] 185 | }, 186 | { 187 | "cell_type": "code", 188 | "execution_count": null, 189 | "metadata": {}, 190 | "outputs": [], 191 | "source": [ 192 | "def sparsify_update(params, p, use_grad_field=True):\n", 193 | " init = True\n", 194 | " for param in params:\n", 195 | " if param is not None:\n", 196 | " if init:\n", 197 | " idx = torch.zeros_like(param, dtype=torch.bool)\n", 198 | " idx.bernoulli_(1 - p)\n", 199 | " if use_grad_field:\n", 200 | " if param.grad is not None:\n", 201 | " idx = torch.zeros_like(param, dtype=torch.bool)\n", 202 | " idx.bernoulli_(1 - p)\n", 203 | " param.grad.data[idx] = 0\n", 204 | " else:\n", 205 | " init = False\n", 206 | " param.data[idx] = 0\n", 207 | " return idx" 208 | ] 209 | }, 210 | { 211 | "cell_type": "code", 212 | "execution_count": null, 213 | "metadata": {}, 214 | "outputs": [], 215 | "source": [ 216 | "def train(trainloader, student, n_epochs=25, lr=0.0001, accountant=None):\n", 217 | " criterion = nn.CrossEntropyLoss(reduction='none')\n", 218 | " #optimizer = optim.Adam(student.parameters(), lr=lr)\n", 219 | " optimizer = optim.SGD(student.parameters(), lr=lr)\n", 220 | " \n", 221 | " if opt.cuda:\n", 222 | " student = student.cuda(gpu_id)\n", 223 | " criterion = criterion.cuda(gpu_id)\n", 224 | " \n", 225 | " accuracies = []\n", 226 | " \n", 227 | " num_batches = len(trainloader.dataset) / opt.batchSize + 1\n", 228 | " sampling_prob = 0.1\n", 229 | " max_grad_norm = opt.C\n", 230 | " \n", 231 | " for epoch in range(n_epochs): # loop over the dataset multiple times\n", 232 | " running_loss = 0.0\n", 233 | " for i, data in enumerate(trainloader, 0):\n", 234 | " # get the inputs\n", 235 | " inputs, labels = data\n", 236 | " \n", 237 | " if opt.cuda:\n", 238 | " inputs = inputs.cuda(gpu_id)\n", 239 | " labels = labels.cuda(gpu_id)\n", 240 | " \n", 241 | " inputv = Variable(inputs)\n", 242 | " labelv = Variable(labels.long().view(-1) % 10)\n", 243 | " \n", 244 | " batch_size = float(len(inputs))\n", 245 | " \n", 246 | " # zero the parameter gradients\n", 247 | " optimizer.zero_grad()\n", 248 | " \n", 249 | " # forward + backward + optimize\n", 250 | " outputs = student(inputv)\n", 251 | " loss = criterion(outputs, labelv)\n", 252 | " \n", 253 | " #max_grad_norm = opt.C * 0.9**epoch\n", 254 | " if accountant:\n", 255 | " grads_est = []\n", 256 | " num_subbatch = 8\n", 257 | " for j in range(num_subbatch):\n", 258 | " grad_sample = torch.autograd.grad(\n", 259 | " loss[np.delete(range(int(batch_size)), j)].mean(), \n", 260 | " [p for p in student.parameters() if p.requires_grad], \n", 261 | " retain_graph=True\n", 262 | " )\n", 263 | " with torch.no_grad():\n", 264 | " grad_sample = torch.cat([g.view(-1) for g in grad_sample])\n", 265 | " grad_sample /= max(1.0, grad_sample.norm().item() / max_grad_norm)\n", 266 | " grads_est += [grad_sample]\n", 267 | " with torch.no_grad():\n", 268 | " grads_est = torch.stack(grads_est)\n", 269 | " sparsify_update(grads_est, p=sampling_prob, use_grad_field=False)\n", 270 | " \n", 271 | " (loss.mean()).backward()\n", 272 | " running_loss += loss.mean().item()\n", 273 | " \n", 274 | " if accountant:\n", 275 | " with torch.no_grad():\n", 276 | " torch.nn.utils.clip_grad_norm_(student.parameters(), max_grad_norm)\n", 277 | " for group in optimizer.param_groups:\n", 278 | " for p in group['params']:\n", 279 | " if p.grad is not None:\n", 280 | " p.grad += torch.randn_like(p.grad) * (opt.sigma * max_grad_norm)\n", 281 | " sparsify_update(student.parameters(), p=sampling_prob)\n", 282 | " \n", 283 | " optimizer.step()\n", 284 | " \n", 285 | " if accountant:\n", 286 | " with torch.no_grad():\n", 287 | " batch_size = float(len(inputs))\n", 288 | " q = batch_size / len(trainloader.dataset)\n", 289 | " # NOTE: \n", 290 | " # Using combinations within a set of gradients (like below)\n", 291 | " # does not actually produce samples from the correct distribution\n", 292 | " # (for that, we need to sample pairs of gradients independently).\n", 293 | " # However, the difference is not significant, and it speeds up computations.\n", 294 | " pairs = list(zip(*itertools.combinations(grads_est, 2)))\n", 295 | " accountant.accumulate(\n", 296 | " ldistr=(torch.stack(pairs[0]), opt.sigma*max_grad_norm),\n", 297 | " rdistr=(torch.stack(pairs[1]), opt.sigma*max_grad_norm),\n", 298 | " q=q, \n", 299 | " steps=1,\n", 300 | " )\n", 301 | " \n", 302 | " # print training stats every epoch\n", 303 | " running_eps = accountant.get_privacy(target_delta=1e-5) if accountant else None\n", 304 | " print(\"Epoch: %d/%d. Loss: %.3f. Privacy (𝜀,𝛿): %s\" %\n", 305 | " (epoch + 1, n_epochs, running_loss / len(trainloader), running_eps))\n", 306 | " \n", 307 | " acc = test(testloader, student)\n", 308 | " accuracies += [acc]\n", 309 | " print(\"Test accuracy is %d %%\" % acc)\n", 310 | "\n", 311 | " print('Finished Training')\n", 312 | " return student.cpu(), accuracies" 313 | ] 314 | }, 315 | { 316 | "cell_type": "code", 317 | "execution_count": null, 318 | "metadata": {}, 319 | "outputs": [], 320 | "source": [ 321 | "# transformations applied to data\n", 322 | "transform = transforms.Compose([transforms.ToTensor()])\n", 323 | "\n", 324 | "# switch datasets\n", 325 | "if opt.dataset == 'mnist':\n", 326 | " trainset = torchvision.datasets.MNIST(root=opt.dataroot + os.sep + opt.dataset, train=True, download=True, transform=transform)\n", 327 | " testset = torchvision.datasets.MNIST(root=opt.dataroot + os.sep + opt.dataset, train=False, download=True, transform=transform)\n", 328 | "elif opt.dataset == 'cifar10':\n", 329 | " trainset = torchvision.datasets.CIFAR10(root=opt.dataroot + os.sep + opt.dataset, train=True, download=True, transform=transform)\n", 330 | " testset = torchvision.datasets.CIFAR10(root=opt.dataroot + os.sep + opt.dataset, train=False, download=True, transform=transform)\n", 331 | "elif opt.dataset == 'svhn':\n", 332 | " trainset = torchvision.datasets.SVHN(root=opt.dataroot + os.sep + opt.dataset, split='train', download=True, transform=transform)\n", 333 | " testset = torchvision.datasets.SVHN(root=opt.dataroot + os.sep + opt.dataset, split='test', download=True, transform=transform)\n", 334 | "\n", 335 | "#val_size = 10000\n", 336 | "#valset = torch.utils.data.Subset(trainset, range(val_size))\n", 337 | "#trainset = torch.utils.data.Subset(trainset, range(val_size, len(trainset)))\n", 338 | " \n", 339 | "# initialise data loaders\n", 340 | "trainloader = torch.utils.data.DataLoader(trainset, batch_size=opt.batchSize, shuffle=True, num_workers=2, drop_last=True)\n", 341 | "testloader = torch.utils.data.DataLoader(testset, batch_size=opt.batchSize, shuffle=True, num_workers=2)\n", 342 | "\n", 343 | "# names of classes\n", 344 | "classes = tuple(np.arange(10).astype(str))" 345 | ] 346 | }, 347 | { 348 | "cell_type": "code", 349 | "execution_count": null, 350 | "metadata": {}, 351 | "outputs": [], 352 | "source": [ 353 | "# train and test\n", 354 | "netS = SimpleConvNet()\n", 355 | "total_steps = opt.n_epochs * len(trainloader)\n", 356 | "bayes_accountant = BayesianPrivacyAccountant(powers=[2, 4, 8, 16, 32], total_steps=total_steps)\n", 357 | "netS, accs = train(trainloader, netS, lr=0.02, n_epochs=opt.n_epochs, accountant=bayes_accountant)" 358 | ] 359 | }, 360 | { 361 | "cell_type": "code", 362 | "execution_count": null, 363 | "metadata": {}, 364 | "outputs": [], 365 | "source": [ 366 | "print(\"Bayesian DP (𝜀,𝛿): \", bayes_accountant.get_privacy(target_delta=1e-10))" 367 | ] 368 | }, 369 | { 370 | "cell_type": "code", 371 | "execution_count": null, 372 | "metadata": {}, 373 | "outputs": [], 374 | "source": [ 375 | "from scipy.spatial.distance import pdist\n", 376 | "\n", 377 | "sampling_prob = 0.1\n", 378 | "loss_fn = nn.CrossEntropyLoss(reduction='none')\n", 379 | "\n", 380 | "netS.cuda(gpu_id)\n", 381 | "loss_fn.cuda(gpu_id)\n", 382 | "\n", 383 | "dists_train = []\n", 384 | "dists_test = []\n", 385 | "\n", 386 | "i = 0\n", 387 | "for inputs, labels in trainloader:\n", 388 | " i += 1\n", 389 | " if i > 8:\n", 390 | " break\n", 391 | " inputs = inputs.cuda(gpu_id)\n", 392 | " labels = labels.cuda(gpu_id)\n", 393 | " netS.zero_grad()\n", 394 | " outputs = netS(inputs)\n", 395 | " loss = loss_fn(outputs, labels)\n", 396 | " \n", 397 | " grads_est = []\n", 398 | " num_subbatch = 100\n", 399 | " for j in range(num_subbatch):\n", 400 | " grad_sample = torch.autograd.grad(loss[np.delete(range(int(opt.batchSize)), j)].mean(), [p for p in netS.parameters() if p.requires_grad], retain_graph=True)\n", 401 | " with torch.no_grad():\n", 402 | " grad_sample = torch.cat([g.view(-1) for g in grad_sample])\n", 403 | " grad_sample /= max(1.0, grad_sample.norm().item() / opt.C)\n", 404 | " grads_est += [grad_sample]\n", 405 | " with torch.no_grad():\n", 406 | " grads_est = torch.stack(grads_est)\n", 407 | " sparsify_update(grads_est, p=sampling_prob, use_grad_field=False)\n", 408 | " q = opt.batchSize / len(trainloader.dataset)\n", 409 | " dists_train += [pdist(grads_est.cpu())]\n", 410 | " \n", 411 | "i = 0\n", 412 | "for inputs, labels in testloader:\n", 413 | " i += 1\n", 414 | " if i > 8:\n", 415 | " break\n", 416 | " inputs = inputs.cuda(gpu_id)\n", 417 | " labels = labels.cuda(gpu_id)\n", 418 | " netS.zero_grad()\n", 419 | " outputs = netS(inputs)\n", 420 | " loss = loss_fn(outputs, labels)\n", 421 | " \n", 422 | " grads_est = []\n", 423 | " num_subbatch = 100\n", 424 | " for j in range(num_subbatch):\n", 425 | " grad_sample = torch.autograd.grad(loss[np.delete(range(int(opt.batchSize)), j)].mean(), [p for p in netS.parameters() if p.requires_grad], retain_graph=True)\n", 426 | " with torch.no_grad():\n", 427 | " grad_sample = torch.cat([g.view(-1) for g in grad_sample])\n", 428 | " grad_sample /= max(1.0, grad_sample.norm().item() / opt.C)\n", 429 | " grads_est += [grad_sample]\n", 430 | " with torch.no_grad():\n", 431 | " grads_est = torch.stack(grads_est)\n", 432 | " sparsify_update(grads_est, p=sampling_prob, use_grad_field=False)\n", 433 | " q = opt.batchSize / len(trainloader.dataset)\n", 434 | " dists_test += [pdist(grads_est.cpu())]" 435 | ] 436 | }, 437 | { 438 | "cell_type": "code", 439 | "execution_count": null, 440 | "metadata": {}, 441 | "outputs": [], 442 | "source": [ 443 | "from scipy.stats import ks_2samp, ttest_rel\n", 444 | "\n", 445 | "dists_train = np.stack(dists_train).squeeze()\n", 446 | "dists_test = np.stack(dists_test).squeeze()\n", 447 | "\n", 448 | "plt.hist(dists_train.flatten(), bins=np.arange(0, 0.02, 0.0005), label='Train', alpha=0.5)\n", 449 | "plt.hist(dists_test.flatten(), bins=np.arange(0, 0.02, 0.0005), label='Test', alpha=0.5)\n", 450 | "plt.legend()\n", 451 | "plt.xlabel(r'Distance')\n", 452 | "plt.ylabel(r'Number of samples')\n", 453 | "plt.title(r'Pairwise gradient distances distribution, MNIST')\n", 454 | "plt.savefig('grad_dist_histogram_mnist_2.pdf', format='pdf', bbox_inches='tight')" 455 | ] 456 | }, 457 | { 458 | "cell_type": "code", 459 | "execution_count": null, 460 | "metadata": {}, 461 | "outputs": [], 462 | "source": [ 463 | "from scipy.stats import ttest_rel, ttest_ind, levene\n", 464 | "\n", 465 | "print(ttest_rel(dists_train.flatten(), dists_test.flatten()))\n", 466 | "print(ttest_ind(dists_train.flatten(), dists_test.flatten()))\n", 467 | "print(levene(dists_train.flatten(), dists_test.flatten()))" 468 | ] 469 | }, 470 | { 471 | "cell_type": "code", 472 | "execution_count": null, 473 | "metadata": {}, 474 | "outputs": [], 475 | "source": [] 476 | } 477 | ], 478 | "metadata": { 479 | "kernelspec": { 480 | "display_name": "Python 3", 481 | "language": "python", 482 | "name": "python3" 483 | }, 484 | "language_info": { 485 | "codemirror_mode": { 486 | "name": "ipython", 487 | "version": 3 488 | }, 489 | "file_extension": ".py", 490 | "mimetype": "text/x-python", 491 | "name": "python", 492 | "nbconvert_exporter": "python", 493 | "pygments_lexer": "ipython3", 494 | "version": "3.6.9" 495 | } 496 | }, 497 | "nbformat": 4, 498 | "nbformat_minor": 2 499 | } 500 | -------------------------------------------------------------------------------- /scaled_renyi.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Author: Aleksei Triastcyn 3 | # Date: 12 August 2020 4 | 5 | import itertools 6 | import numpy as np 7 | import scipy as sp 8 | import torch 9 | 10 | from scipy.stats import t, binom 11 | from scipy.special import logsumexp 12 | 13 | 14 | def scaled_renyi_gaussian(alphas, ldistr, rdistr): 15 | """ 16 | Computes scaled Renyi divergence D(p_left|p_right) between pairs of Gaussian distributions (with the same variance). 17 | 18 | 19 | Parameters 20 | ---------- 21 | alpha : number, required 22 | Order of the Renyi divergence. 23 | ldistr : tuple or array, required 24 | Parameters of the left Gaussians (mu_left [n_samples * n_features], sigma [scalar]). 25 | rdistr : tuple or array, required 26 | Parameters of the right Gaussians (mu_right [n_samples * n_features], sigma [scalar]). 27 | 28 | Returns 29 | ------- 30 | out : array 31 | Scaled Renyi divergences 32 | """ 33 | lmu, lsigma = ldistr 34 | rmu, rsigma = rdistr 35 | 36 | if not (np.isscalar(lsigma) and np.isscalar(rsigma)): 37 | raise NotImplementedError("Not implemented for Gaussians with diagonal or full covariances.") 38 | if lsigma != rsigma: 39 | raise NotImplementedError("Not implemented for Gaussians with different variances.") 40 | 41 | distances = lmu - rmu 42 | # ensure it is a tensor 43 | if np.isscalar(distances): 44 | distances = torch.tensor(distances) 45 | distances = torch.norm(distances, p=2, dim=-1).view(-1).to(alphas) 46 | return torch.ger(distances**2, alphas * (alphas - 1) / (2 * lsigma**2)) -------------------------------------------------------------------------------- /synthetic_experiments.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "import itertools\n", 10 | "import numpy as np\n", 11 | "import torch\n", 12 | "\n", 13 | "import matplotlib\n", 14 | "import matplotlib.pyplot as plt\n", 15 | "%matplotlib inline\n", 16 | "\n", 17 | "from bayesian_privacy_accountant import BayesianPrivacyAccountant" 18 | ] 19 | }, 20 | { 21 | "cell_type": "code", 22 | "execution_count": null, 23 | "metadata": {}, 24 | "outputs": [], 25 | "source": [ 26 | "ma_eps = []\n", 27 | "ba_eps = []\n", 28 | "\n", 29 | "quant = 0.05\n", 30 | "sigma = 1.0\n", 31 | "plot_range = np.arange(100)\n", 32 | "\n", 33 | "moment_accountant = BayesianPrivacyAccountant(powers=16, total_steps=plot_range[-1]+1, bayesianDP=False)\n", 34 | "bayes_accountant = BayesianPrivacyAccountant(powers=16, total_steps=plot_range[-1]+1)\n", 35 | "for i in plot_range:\n", 36 | " grads = np.random.weibull(0.5, [50, 1000])\n", 37 | " C = np.quantile(np.linalg.norm(grads, axis=1), quant)\n", 38 | " #grads /= np.maximum(1, np.linalg.norm(grads, axis=1, keepdims=True) / C)\n", 39 | " \n", 40 | " moment_accountant.accumulate(\n", 41 | " ldistr=(C*2, sigma * C * 2), # multiply by 2, because 2C is the actual max distance\n", 42 | " rdistr=(0, sigma * C * 2),\n", 43 | " q=1/1000, \n", 44 | " steps=1\n", 45 | " )\n", 46 | " ma_eps += [moment_accountant.get_privacy(target_delta=1e-5)[0]]\n", 47 | " pairs = list(zip(*itertools.combinations(torch.tensor(grads), 2)))\n", 48 | " bayes_accountant.accumulate(\n", 49 | " ldistr=(torch.stack(pairs[0]), sigma * C * 2), \n", 50 | " rdistr=(torch.stack(pairs[1]), sigma * C * 2), \n", 51 | " #ldistr=(torch.tensor(grads[:25]), sigma * C * 2), \n", 52 | " #rdistr=(torch.tensor(grads[25:]), sigma * C * 2), \n", 53 | " q=1/1000, \n", 54 | " steps=1\n", 55 | " ) \n", 56 | " ba_eps += [bayes_accountant.get_privacy(target_delta=1e-5)[0]]\n", 57 | "\n", 58 | "plt.plot(plot_range, ma_eps, label='DP')\n", 59 | "plt.plot(plot_range, ba_eps, label='BDP')\n", 60 | "plt.yscale('log')\n", 61 | "plt.legend()\n", 62 | "plt.xlabel(r'Step', fontsize=12)\n", 63 | "plt.ylabel(r'$\\varepsilon$', fontsize=12)\n", 64 | "plt.title(r'Privacy loss evolution, $C=0.05$-quantile, no clipping', fontsize=12)\n", 65 | "plt.savefig('eps_step_05q_noclip.pdf', format='pdf', bbox_inches='tight')" 66 | ] 67 | }, 68 | { 69 | "cell_type": "code", 70 | "execution_count": null, 71 | "metadata": {}, 72 | "outputs": [], 73 | "source": [ 74 | "grads = np.random.weibull(0.5, [50, 1000])\n", 75 | "\n", 76 | "quant = 0.99\n", 77 | "C = np.quantile(np.linalg.norm(grads, axis=1), quant)\n", 78 | "grads /= np.maximum(1, np.linalg.norm(grads, axis=1, keepdims=True) / C)\n", 79 | "\n", 80 | "ma_eps = []\n", 81 | "ba_eps = []\n", 82 | "sigmas = np.arange(0.55, 1.4, 0.05)\n", 83 | "for sigma in sigmas:\n", 84 | " moment_accountant = BayesianPrivacyAccountant(powers=24, total_steps=1, bayesianDP=False)\n", 85 | " moment_accountant.accumulate(ldistr=(C * 2, sigma * C * 2), rdistr=(0, sigma * C * 2), q=1/1000, steps=1)\n", 86 | " ma_eps += [moment_accountant.get_privacy(target_delta=1e-5)[0]]\n", 87 | " \n", 88 | " bayes_accountant = BayesianPrivacyAccountant(powers=24, total_steps=1)\n", 89 | " pairs = list(zip(*itertools.combinations(torch.tensor(grads), 2)))\n", 90 | " bayes_accountant.accumulate(\n", 91 | " ldistr=(torch.stack(pairs[0]), sigma * C * 2), \n", 92 | " rdistr=(torch.stack(pairs[1]), sigma * C * 2), \n", 93 | " q=1/1000, \n", 94 | " steps=1\n", 95 | " ) \n", 96 | " ba_eps += [bayes_accountant.get_privacy(target_delta=1e-5)[0]]\n", 97 | "\n", 98 | "plt.rc('axes', titlesize=14, labelsize=14)\n", 99 | "plt.plot(sigmas, ma_eps, '--', linewidth=2, label='DP')\n", 100 | "plt.plot(sigmas, ba_eps, linewidth=2, label='BDP')\n", 101 | "plt.legend()\n", 102 | "plt.xlabel(r'$\\sigma$')\n", 103 | "plt.ylabel(r'$\\varepsilon$')\n", 104 | "plt.title(r'$\\sigma$ vs $\\varepsilon$, $C=0.99$-quantile')\n", 105 | "plt.savefig('eps_sigma_99q.pdf', format='pdf', bbox_inches='tight')" 106 | ] 107 | }, 108 | { 109 | "cell_type": "code", 110 | "execution_count": null, 111 | "metadata": {}, 112 | "outputs": [], 113 | "source": [ 114 | "ba_eps = []\n", 115 | "\n", 116 | "sigma = 1.0\n", 117 | "plot_range = range(1, 50)\n", 118 | "\n", 119 | "ba_eps_c = []\n", 120 | "for C in [0.1, 0.7, 1.0]:\n", 121 | " grads = np.random.randn(50, 1000)\n", 122 | " grads /= np.maximum(1, np.linalg.norm(grads, axis=1, keepdims=True) / C)\n", 123 | " ba_eps = []\n", 124 | " for p in plot_range: \n", 125 | " bayes_accountant = BayesianPrivacyAccountant(powers=p, total_steps=10000, bayesianDP=False)\n", 126 | " bayes_accountant.accumulate(ldistr=(C, sigma), rdistr=(0, sigma), q=64/60000, steps=10000)\n", 127 | " ba_eps += [bayes_accountant.get_privacy(target_delta=1e-5)[0]]\n", 128 | " ba_eps_c += [ba_eps]\n", 129 | "\n", 130 | "plt.rc('axes', titlesize=14, labelsize=14)\n", 131 | "plt.plot(plot_range, ba_eps_c[0], ':', linewidth=2, label='C=0.1')\n", 132 | "plt.plot(plot_range, ba_eps_c[1], '--', linewidth=2, label='C=0.7')\n", 133 | "plt.plot(plot_range, ba_eps_c[2], '-', linewidth=2, label='C=1.0')\n", 134 | "plt.yscale('log')\n", 135 | "plt.legend()\n", 136 | "plt.xlabel(r'$\\lambda$')\n", 137 | "plt.ylabel(r'$\\varepsilon$')\n", 138 | "plt.title(r'$\\lambda$ vs $\\varepsilon$ for different C, $\\delta=10^{-5}$')\n", 139 | "plt.savefig('eps_lambda_C.pdf', format='pdf', bbox_inches='tight')" 140 | ] 141 | }, 142 | { 143 | "cell_type": "code", 144 | "execution_count": null, 145 | "metadata": {}, 146 | "outputs": [], 147 | "source": [] 148 | } 149 | ], 150 | "metadata": { 151 | "kernelspec": { 152 | "display_name": "Python 3", 153 | "language": "python", 154 | "name": "python3" 155 | }, 156 | "language_info": { 157 | "codemirror_mode": { 158 | "name": "ipython", 159 | "version": 3 160 | }, 161 | "file_extension": ".py", 162 | "mimetype": "text/x-python", 163 | "name": "python", 164 | "nbconvert_exporter": "python", 165 | "pygments_lexer": "ipython3", 166 | "version": "3.6.9" 167 | } 168 | }, 169 | "nbformat": 4, 170 | "nbformat_minor": 2 171 | } 172 | --------------------------------------------------------------------------------