|
| 1 | + |
| 2 | +import numpy as np |
| 3 | +import matplotlib.pyplot as plt |
| 4 | + |
| 5 | +import torch |
| 6 | +import torch.nn.functional as F |
| 7 | + |
| 8 | +from .metrics import * |
| 9 | + |
| 10 | +# determine device to run network on (runs on gpu if available) |
| 11 | +device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") |
| 12 | + |
| 13 | + |
| 14 | +def train(net, data_loader, test_loader, optimizer, criterion, n_epochs, classes=None, verbose=False): |
| 15 | + losses = [] |
| 16 | + for epoch in range(n_epochs): |
| 17 | + net.train() |
| 18 | + for i, batch in enumerate(data_loader): |
| 19 | + |
| 20 | + imgs, labels = batch |
| 21 | + imgs, labels = imgs.to(device), labels.to(device) |
| 22 | + |
| 23 | + optimizer.zero_grad() |
| 24 | + |
| 25 | + outputs = net(imgs) |
| 26 | + |
| 27 | + loss = criterion(outputs, labels) |
| 28 | + loss.backward() |
| 29 | + optimizer.step() |
| 30 | + |
| 31 | + losses.append(loss.item()) |
| 32 | + |
| 33 | + if verbose: |
| 34 | + print("[%d/%d][%d/%d] loss = %f" % (epoch, n_epochs, i, len(data_loader), loss.item())) |
| 35 | + |
| 36 | + # evaluate performance on testset at the end of each epoch |
| 37 | + print("[%d/%d]" %(epoch, n_epochs)) |
| 38 | + print("Training:") |
| 39 | + eval_target_net(net, data_loader, classes=classes) |
| 40 | + print("Test:") |
| 41 | + eval_target_net(net, test_loader, classes=classes) |
| 42 | + #plt.plot(losses) |
| 43 | + #plt.show() |
| 44 | + |
| 45 | +def train_attacker(attack_net, shadow, shadow_train, shadow_out, optimizer, criterion, n_epochs, k): |
| 46 | + |
| 47 | + """ |
| 48 | + Trains attack model (classifies a sample as in or out of training set) using |
| 49 | + shadow model outputs (probabilities for sample class predictions). |
| 50 | + The type of shadow model used can vary. |
| 51 | + """ |
| 52 | + |
| 53 | + in_predicts=[] |
| 54 | + out_predicts=[] |
| 55 | + losses = [] |
| 56 | + |
| 57 | + if type(shadow) is not Pipeline: |
| 58 | + shadow_net=shadow |
| 59 | + shadow_net.eval() |
| 60 | + |
| 61 | + for epoch in range(n_epochs): |
| 62 | + |
| 63 | + total = 0 |
| 64 | + correct = 0 |
| 65 | + |
| 66 | + #train_top = np.array([]) |
| 67 | + #train_top = [] |
| 68 | + train_top = np.empty((0,2)) |
| 69 | + out_top = np.empty((0,2)) |
| 70 | + for i, ((train_imgs, _), (out_imgs, _)) in enumerate(zip(shadow_train, shadow_out)): |
| 71 | + |
| 72 | + #######out_imgs = torch.randn(out_imgs.shape) |
| 73 | + mini_batch_size = train_imgs.shape[0] |
| 74 | + |
| 75 | + if type(shadow) is not Pipeline: |
| 76 | + train_imgs, out_imgs = train_imgs.to(device), out_imgs.to(device) |
| 77 | + |
| 78 | + train_posteriors = F.softmax(shadow_net(train_imgs.detach()), dim=1) |
| 79 | + |
| 80 | + out_posteriors = F.softmax(shadow_net(out_imgs.detach()), dim=1) |
| 81 | + |
| 82 | + |
| 83 | + else: |
| 84 | + traininputs= train_imgs.view(train_imgs.shape[0],-1) |
| 85 | + outinputs=out_imgs.view(out_imgs.shape[0], -1) |
| 86 | + |
| 87 | + in_preds=shadow.predict_proba(traininputs) |
| 88 | + train_posteriors=torch.from_numpy(in_preds).float() |
| 89 | + #for p in in_preds: |
| 90 | + # in_predicts.append(p.max()) |
| 91 | + |
| 92 | + out_preds=shadow.predict_proba(outinputs) |
| 93 | + out_posteriors=torch.from_numpy(out_preds).float() |
| 94 | + #for p in out_preds: |
| 95 | + # out_predicts.append(p.max()) |
| 96 | + |
| 97 | + |
| 98 | + train_sort, _ = torch.sort(train_posteriors, descending=True) |
| 99 | + train_top_k = train_sort[:,:k].clone().to(device) |
| 100 | + for p in train_top_k: |
| 101 | + in_predicts.append((p.max()).item()) |
| 102 | + out_sort, _ = torch.sort(out_posteriors, descending=True) |
| 103 | + out_top_k = out_sort[:,:k].clone().to(device) |
| 104 | + for p in out_top_k: |
| 105 | + out_predicts.append((p.max()).item()) |
| 106 | + |
| 107 | + train_top = np.vstack((train_top,train_top_k[:,:2].cpu().detach().numpy())) |
| 108 | + out_top = np.vstack((out_top, out_top_k[:,:2].cpu().detach().numpy())) |
| 109 | + |
| 110 | + |
| 111 | + train_lbl = torch.ones(mini_batch_size).to(device) |
| 112 | + out_lbl = torch.zeros(mini_batch_size).to(device) |
| 113 | + |
| 114 | + optimizer.zero_grad() |
| 115 | + |
| 116 | + train_predictions = torch.squeeze(attack_net(train_top_k)) |
| 117 | + out_predictions = torch.squeeze(attack_net(out_top_k)) |
| 118 | + |
| 119 | + loss_train = criterion(train_predictions, train_lbl) |
| 120 | + loss_out = criterion(out_predictions, out_lbl) |
| 121 | + |
| 122 | + loss = (loss_train + loss_out) / 2 |
| 123 | + |
| 124 | + if type(shadow) is not Pipeline: |
| 125 | + loss.backward() |
| 126 | + optimizer.step() |
| 127 | + |
| 128 | + |
| 129 | + correct += (F.sigmoid(train_predictions)>=0.5).sum().item() |
| 130 | + correct += (F.sigmoid(out_predictions)<0.5).sum().item() |
| 131 | + total += train_predictions.size(0) + out_predictions.size(0) |
| 132 | + |
| 133 | + |
| 134 | + print("[%d/%d][%d/%d] loss = %.2f, accuracy = %.2f" % (epoch, n_epochs, i, len(shadow_train), loss.item(), 100 * correct / total)) |
| 135 | + |
| 136 | + #Plot distributions for target predictions in training set and out of training set |
| 137 | + """ |
| 138 | + fig, ax = plt.subplots(2,1) |
| 139 | + plt.subplot(2,1,1) |
| 140 | + plt.hist(in_predicts, bins='auto') |
| 141 | + plt.title('In') |
| 142 | + plt.subplot(2,1,2) |
| 143 | + plt.hist(out_predicts, bins='auto') |
| 144 | + plt.title('Out') |
| 145 | + """ |
| 146 | + |
| 147 | + ''' |
| 148 | + plt.scatter(out_top.T[0,:], out_top.T[1,:], c='b') |
| 149 | + plt.scatter(train_top.T[0,:], train_top.T[1,:], c='r') |
| 150 | + plt.show() |
| 151 | + ''' |
| 152 | + |
| 153 | +class softCrossEntropy(torch.nn.Module): |
| 154 | + def __init__(self, alpha = 0.95): |
| 155 | + """ |
| 156 | + :param alpha: Strength (0-1) of influence from soft labels in training |
| 157 | + """ |
| 158 | + super(softCrossEntropy, self).__init__() |
| 159 | + self.alpha = alpha |
| 160 | + return |
| 161 | + |
| 162 | + def forward(self, inputs, target, true_labels): |
| 163 | + """ |
| 164 | + :param inputs: predictions |
| 165 | + :param target: target (soft) labels |
| 166 | + :param true_labels: true (hard) labels |
| 167 | + :return: loss |
| 168 | + """ |
| 169 | + KD_loss = self.alpha*nn.KLDivLoss(size_average=False)(F.log_softmax(inputs, dim=1), |
| 170 | + F.softmax(target, dim=1)) |
| 171 | + + (1-self.alpha)*F.cross_entropy(inputs,true_labels) |
| 172 | + return KD_loss |
| 173 | + |
| 174 | +def distill_training(teacher, learner, data_loader, test_loader, optimizer, criterion, n_epochs, verbose = False): |
| 175 | + """ |
| 176 | + :param teacher: network to provide soft labels in training |
| 177 | + :param learner: network to distill knowledge into |
| 178 | + :param data_loader: data loader for training data set |
| 179 | + :param test_loaderL data loader for validation data |
| 180 | + :param optimizer: optimizer for training |
| 181 | + :param criterion: objective function, should allow for soft labels. We suggested softCrossEntropy |
| 182 | + :param n_epochs: epochs for training |
| 183 | + :param verbose: verbose == True will print loss at each batch |
| 184 | + :return: None, teacher model is trained in place |
| 185 | + """ |
| 186 | + losses = [] |
| 187 | + for epoch in range(n_epochs): |
| 188 | + teacher.eval() |
| 189 | + learner.train() |
| 190 | + for i, batch in enumerate(data_loader): |
| 191 | + with torch.set_grad_enabled(False): |
| 192 | + imgs, labels = batch |
| 193 | + imgs, labels = imgs.to(device), labels.to(device) |
| 194 | + soft_lables = teacher(imgs) |
| 195 | + |
| 196 | + with torch.set_grad_enabled(True): |
| 197 | + optimizer.zero_grad() |
| 198 | + outputs = learner(imgs) |
| 199 | + loss = criterion(outputs, soft_lables, labels) |
| 200 | + loss.backward() |
| 201 | + optimizer.step() |
| 202 | + losses.append(loss.item()) |
| 203 | + |
| 204 | + if verbose: |
| 205 | + print("[%d/%d][%d/%d] loss = %f" % (epoch, n_epochs, i, len(data_loader), loss.item())) |
| 206 | + # evaluate performance on testset at the end of each epoch |
| 207 | + print("[%d/%d]" %(epoch, n_epochs)) |
| 208 | + print("Training:") |
| 209 | + eval_target_net(learner, data_loader, classes=None) |
| 210 | + print("Test:") |
| 211 | + eval_target_net(learner, test_loader, classes=None) |
| 212 | +# plt.plot(losses) |
| 213 | +# plt.show() |
0 commit comments