Skip to content

Commit 9915092

Browse files
committed
Formatted all modules according to pep8
1 parent 1146854 commit 9915092

11 files changed

+393
-216
lines changed

adversarial_attack.py

+39-25
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
This module contains classes for adversarial attacks.
1313
"""
1414

15+
1516
class FGSM:
1617
"""
1718
Class for the fast gradient sign method (FGSM).
@@ -23,10 +24,12 @@ def __init__(self, model, loss_criterion, norm, batch_size=128):
2324
self.pytorch_model = wrapModel(model, loss_criterion)
2425
self.norm = norm
2526
self.batch_size = batch_size
26-
self.attack = FastGradientMethod(self.pytorch_model, batch_size=batch_size)
27-
27+
self.attack = FastGradientMethod(
28+
self.pytorch_model, batch_size=batch_size)
29+
2830
# Use GPU for computation if it is available
29-
self.device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
31+
self.device = torch.device(
32+
"cuda:0" if torch.cuda.is_available() else "cpu")
3033

3134
def generatePerturbation(self, data, budget, minimal=False):
3235
"""
@@ -46,12 +49,14 @@ def generatePerturbation(self, data, budget, minimal=False):
4649
"""
4750

4851
images, _ = data
49-
images_adv = self.attack.generate(x=images.cpu().numpy(), norm=self.norm, eps=budget, minimal=minimal, eps_step=budget / 50, eps_max=budget, batch_size=self.batch_size)
52+
images_adv = self.attack.generate(x=images.cpu().numpy(
53+
), norm=self.norm, eps=budget, minimal=minimal, eps_step=budget / 50, eps_max=budget, batch_size=self.batch_size)
5054
images_adv = torch.from_numpy(images_adv)
5155

52-
# The output to be returned should be loaded on an appropriate device.
56+
# The output to be returned should be loaded on an appropriate device.
5357
return images_adv.to(self.device)
5458

59+
5560
class FGSMNative:
5661
"""
5762
Class for manually implemented FGSM, unlike the above FGSM class in this
@@ -65,9 +70,10 @@ def __init__(self, model, loss_criterion, norm=np.inf, batch_size=128):
6570
self.loss_criterion = loss_criterion
6671
self.norm = norm
6772
self.batch_size = batch_size
68-
73+
6974
# Use GPU for computation if it is available
70-
self.device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
75+
self.device = torch.device(
76+
"cuda:0" if torch.cuda.is_available() else "cpu")
7177

7278
def generatePerturbation(self, data, budget, minimal=False):
7379
"""
@@ -91,7 +97,7 @@ def generatePerturbation(self, data, budget, minimal=False):
9197

9298
images, labels = data
9399
images_adv = images.clone().detach().to(self.device)
94-
# We will never need to compute a gradient with respect to images_adv.
100+
# We will never need to compute a gradient with respect to images_adv.
95101
images_adv.requires_grad_(False)
96102

97103
images.requires_grad_(True)
@@ -104,31 +110,35 @@ def generatePerturbation(self, data, budget, minimal=False):
104110
direction = images.grad.data.sign()
105111
elif self.norm == 2:
106112
flattened_images = images_adv.view(-1, img_rows * img_cols)
107-
direction = F.normalize(flattened_images, p=2, dim=1).view(images.size())
113+
direction = F.normalize(
114+
flattened_images, p=2, dim=1).view(images.size())
108115
else:
109116
raise ValueError("The norm is not valid.")
110-
117+
111118
if minimal:
112119
iterations = 50
113120
incremental_size = budget / iterations
114121
minimal_perturbations = torch.zeros(images.size())
115122
for i in range(iterations):
116-
outputs = self.model((images_adv + minimal_perturbations).clamp(0, 1))
123+
outputs = self.model(
124+
(images_adv + minimal_perturbations).clamp(0, 1))
117125
_, predicted = torch.max(outputs.data, 1)
118126
for j in range(labels.size()[0]):
119127
# If the current adversarial exampels are correctly
120-
# classified, increase the size of the perturbations.
128+
# classified, increase the size of the perturbations.
121129
if predicted[j] == labels[j]:
122-
minimal_perturbations[j].add_(incremental_size * direction[j])
130+
minimal_perturbations[j].add_(
131+
incremental_size * direction[j])
123132
images_adv.add_(minimal_perturbations)
124133
else:
125134
images_adv.add_(budget * direction)
126-
127-
images_adv.clamp_(0,1)
128135

129-
# The output to be returned should be loaded on an appropriate device.
136+
images_adv.clamp_(0, 1)
137+
138+
# The output to be returned should be loaded on an appropriate device.
130139
return images_adv
131140

141+
132142
class PGD:
133143
"""
134144
Module for adversarial attacks based on projected gradient descent (PGD).
@@ -144,30 +154,34 @@ def __init__(self, model, loss_criterion, norm=np.inf, batch_size=128):
144154
self.pytorch_model = wrapModel(model, loss_criterion)
145155
self.norm = norm
146156
self.batch_size = batch_size
147-
self.attack = ProjectedGradientDescent(self.pytorch_model, norm=norm, random_init=False, batch_size=batch_size)
157+
self.attack = ProjectedGradientDescent(
158+
self.pytorch_model, norm=norm, random_init=False, batch_size=batch_size)
148159

149160
# Use GPU for computation if it is available
150-
self.device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
161+
self.device = torch.device(
162+
"cuda:0" if torch.cuda.is_available() else "cpu")
151163

152164
def generatePerturbation(self, data, budget, max_iter=15):
153165
images, _ = data
154-
155-
# eps_step is not allowed to be larger than budget according to the
156-
# documentation of ART.
166+
167+
# eps_step is not allowed to be larger than budget according to the
168+
# documentation of ART.
157169
eps_step = budget / 5
158-
images_adv = self.attack.generate(x=images.cpu().numpy(), norm=self.norm, eps=budget, eps_step=eps_step, max_iter=max_iter, batch_size=self.batch_size)
170+
images_adv = self.attack.generate(x=images.cpu().numpy(
171+
), norm=self.norm, eps=budget, eps_step=eps_step, max_iter=max_iter, batch_size=self.batch_size)
159172
images_adv = torch.from_numpy(images_adv)
160173

161-
# The output to be returned should be loaded on an appropriate device.
174+
# The output to be returned should be loaded on an appropriate device.
162175
return images_adv.to(self.device)
163176

177+
164178
if __name__ == "__main__":
165179
# Load a simple neural network
166180
model = SimpleNeuralNet()
167181
loadModel(model, "./ERM_models/SimpleModel.pt")
168182

169183
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
170-
model.to(device) # Load the neural network on GPU if it is available
184+
model.to(device) # Load the neural network on GPU if it is available
171185
print("The neural network is now loaded on {}.".format(device))
172186

173187
# Create an object for PGD
@@ -189,7 +203,7 @@ def generatePerturbation(self, data, budget, max_iter=15):
189203
# images_adv is already loaded on GPU by generatePerturbation
190204
images_adv = pgd.generatePerturbation(data, epsilon)
191205
with torch.no_grad():
192-
outputs = model(images_adv)
206+
outputs = model(images_adv)
193207
_, predicted = torch.max(outputs.data, 1)
194208
total += labels.size(0)
195209
correct += (predicted == labels).sum().item()

adversarial_attack_DRO.py

+53-33
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from util_model import SimpleNeuralNet, MNISTClassifier
88
from adversarial_training import AdversarialTraining, ProjectedGradientTraining
99

10+
1011
class ProjetcedDRO(AdversarialTraining):
1112
"""
1213
Execute distributionally robust optimization (DRO) using the Euclidean
@@ -25,13 +26,13 @@ def attack(self, budget, data, steps=15):
2526
images_adv = images.clone().detach().to(self.device)
2627
images_adv.requires_grad_(True)
2728

28-
# images.size()[0] corresponds to the batch size.
29+
# images.size()[0] corresponds to the batch size.
2930
desirable_distance = budget * math.sqrt(images.size()[0])
3031

3132
# Choose a random strating point where the constraint for perturbations
3233
# is tight. Without randomly choosing a starting point, the adversarial
3334
# attack fails most of the time because the loss function is flat near
34-
# the training input, which was used in training the neural network.
35+
# the training input, which was used in training the neural network.
3536
randomStart(images_adv, budget)
3637
for i in range(steps):
3738
if images_adv.grad is not None:
@@ -45,13 +46,15 @@ def attack(self, budget, data, steps=15):
4546
distance = torch.norm(diff_tensor, p=2).item()
4647

4748
# Inside this conditional statement, we can be certain that
48-
# distance > 0, provided that budget > 0.
49-
# Hence, there is no risk of division by 0.
49+
# distance > 0, provided that budget > 0.
50+
# Hence, there is no risk of division by 0.
5051
if distance > desirable_distance:
51-
images_adv.data.add_((1 - (desirable_distance / distance)) * diff_tensor)
52-
images_adv.data.clamp_(0, 1)
52+
images_adv.data.add_(
53+
(1 - (desirable_distance / distance)) * diff_tensor)
54+
images_adv.data.clamp_(0, 1)
5355
return images_adv, labels
5456

57+
5558
class LagrangianDRO(AdversarialTraining):
5659
"""
5760
Execute DRO using the Lagrangian relaxation of the original theoretical
@@ -78,21 +81,23 @@ def attack(self, budget, data, steps=15):
7881
budget: gamma in the original paper. Note that this parameter is
7982
different from the budget parameter in other DRO classes.
8083
"""
81-
84+
8285
images, labels = data
8386
images_adv = images.clone().detach().to(self.device)
8487
images_adv.requires_grad_(True)
85-
88+
8689
for i in range(steps):
8790
if images_adv.grad is not None:
8891
images_adv.grad.data.zero_()
8992
outputs = self.model(images_adv)
90-
loss = self.loss_criterion(outputs, labels) - budget * self.cost_function(images, images_adv)
93+
loss = self.loss_criterion(
94+
outputs, labels) - budget * self.cost_function(images, images_adv)
9195
loss.backward()
9296
images_adv.data.add_(1 / math.sqrt(i+1) * images_adv.grad)
9397
images_adv.data.clamp_(0, 1)
9498
return images_adv, labels
9599

100+
96101
class FrankWolfeDRO(AdversarialTraining):
97102
"""
98103
Execute DRO using the Frank-Wolfe method together with the stochastic
@@ -124,19 +129,21 @@ def attack(self, budget, data, steps=15):
124129
images, labels = data
125130
images_adv = images.clone().detach().to(self.device)
126131
images_adv.requires_grad_(True)
127-
132+
128133
for i in range(steps):
129134
if images_adv.grad is not None:
130135
images_adv.grad.zero_()
131136
outputs = self.model(images_adv)
132137
loss = self.loss_criterion(outputs, labels)
133138
loss.backward()
134139

135-
# desitnation corresponds to y_t in the paper by Bubeck.
136-
destination = images_adv.data + self.getOptimalDirection(budget=budget, data=images_adv.grad)
140+
# desitnation corresponds to y_t in the paper by Bubeck.
141+
destination = images_adv.data + \
142+
self.getOptimalDirection(budget=budget, data=images_adv.grad)
137143
destination = destination.to(self.device)
138144
gamma = 2 / (i + 2)
139-
images_adv.data = (1 - gamma) * images_adv.data + gamma * destination
145+
images_adv.data = (1 - gamma) * \
146+
images_adv.data + gamma * destination
140147
images_adv.data.clamp_(0, 1)
141148
return images_adv, labels
142149

@@ -154,15 +161,15 @@ def getOptimalDirection(self, budget, data):
154161
data: gradient of the total loss with respect to the current
155162
batch of adversarial examples. This corresponds to C in
156163
Appendix B of the paper by Staib et al.
157-
164+
158165
Returns:
159166
X in Appendix B of Staib et al.'s paper
160167
"""
161168

162169
# The number of samples
163170
batch_size = data.size()[0]
164171

165-
# 'directions' corresponds to v's in Staib et al.'s paper.
172+
# 'directions' corresponds to v's in Staib et al.'s paper.
166173
directions = data.clone().detach().view((batch_size, -1))
167174
directions = directions.to(self.device)
168175

@@ -173,17 +180,17 @@ def getOptimalDirection(self, budget, data):
173180
directions.pow_(normalize_dim)
174181
directions = F.normalize(directions, p=self.q, dim=1)
175182
else:
176-
raise ValueError("The value of q must be larger than 1.")
177-
178-
# This corresponds to a's in the original paper.
183+
raise ValueError("The value of q must be larger than 1.")
184+
185+
# This corresponds to a's in the original paper.
179186
products = []
180187
for i, direction in enumerate(directions):
181188
sample = data[i].view(-1)
182189
products.append(torch.dot(direction, sample))
183190
products = torch.stack(products)
184191
products = products.to(self.device)
185192

186-
# This corresponds to epsilons in the original paper.
193+
# This corresponds to epsilons in the original paper.
187194
size_factors = products.clone().detach()
188195
size_factors = size_factors.to(self.device)
189196
if self.p == np.inf:
@@ -192,16 +199,17 @@ def getOptimalDirection(self, budget, data):
192199
normalize_dim = 1 / (self.p - 1)
193200
size_factors.pow_(normalize_dim)
194201
distance = torch.norm(size_factors, p=self.p).item()
195-
size_factors = size_factors / distance # This is now normalized.
202+
size_factors = size_factors / distance # This is now normalized.
196203
else:
197-
raise ValueError("The value of p must be larger than 1.")
198-
204+
raise ValueError("The value of p must be larger than 1.")
205+
199206
outputs = []
200207
for i, size_factor in enumerate(size_factors):
201208
outputs.append(directions[i] * size_factor * budget)
202209
result = torch.stack(outputs).view(data.size())
203210
return result.to(self.device)
204211

212+
205213
def trainDROModel(dro_type, epochs, steps_adv, budget, activation, batch_size, loss_criterion, cost_function=None):
206214
"""
207215
Train a neural network using one of the following DRO methods:
@@ -210,7 +218,7 @@ def trainDROModel(dro_type, epochs, steps_adv, budget, activation, batch_size, l
210218
This is also called WRM.
211219
- the Frank-Wolfe method based approach developed by Staib et al.
212220
"""
213-
221+
214222
model = MNISTClassifier(activation=activation)
215223
if dro_type == 'PGD':
216224
train_module = ProjetcedDRO(model, loss_criterion)
@@ -222,11 +230,16 @@ def trainDROModel(dro_type, epochs, steps_adv, budget, activation, batch_size, l
222230
else:
223231
raise ValueError("The type of DRO is not valid.")
224232

225-
train_module.train(budget=budget, batch_size=batch_size, epochs=epochs, steps_adv=steps_adv)
233+
train_module.train(budget=budget, batch_size=batch_size,
234+
epochs=epochs, steps_adv=steps_adv)
226235
folderpath = "./DRO_models/"
227-
filepath = folderpath + "{}_DRO_activation={}_epsilon={}.pt".format(dro_type, activation, budget)
236+
filepath = folderpath + \
237+
"{}_DRO_activation={}_epsilon={}.pt".format(
238+
dro_type, activation, budget)
228239
torch.save(model.state_dict(), filepath)
229-
print("A neural network adversarially trained using {} is now saved at {}.".format(dro_type, filepath))
240+
print("A neural network adversarially trained using {} is now saved at {}.".format(
241+
dro_type, filepath))
242+
230243

231244
if __name__ == "__main__":
232245
epochs = 25
@@ -235,14 +248,21 @@ def trainDROModel(dro_type, epochs, steps_adv, budget, activation, batch_size, l
235248
gammas = [0.0001, 0.0003, 0.001, 0.003, 0.01, 0.03, 0.1, 0.3, 1.0, 3.0]
236249
batch_size = 128
237250
loss_criterion = nn.CrossEntropyLoss()
238-
cost_function = lambda x, y: torch.dist(x, y, p=2) ** 2
239251

240-
trainDROModel('PGD', epochs, steps_adv, epsilon, 'relu', batch_size, loss_criterion)
241-
trainDROModel('FW', epochs, steps_adv, epsilon, 'relu', batch_size, loss_criterion)
252+
def cost_function(x, y): return torch.dist(x, y, p=2) ** 2
253+
254+
trainDROModel('PGD', epochs, steps_adv, epsilon,
255+
'relu', batch_size, loss_criterion)
256+
trainDROModel('FW', epochs, steps_adv, epsilon,
257+
'relu', batch_size, loss_criterion)
242258

243-
trainDROModel('PGD', epochs, steps_adv, epsilon, 'elu', batch_size, loss_criterion)
244-
trainDROModel('FW', epochs, steps_adv, epsilon, 'elu', batch_size, loss_criterion)
259+
trainDROModel('PGD', epochs, steps_adv, epsilon,
260+
'elu', batch_size, loss_criterion)
261+
trainDROModel('FW', epochs, steps_adv, epsilon,
262+
'elu', batch_size, loss_criterion)
245263

246264
for gamma in gammas:
247-
trainDROModel('Lag', epochs, steps_adv, gamma, 'relu', batch_size, loss_criterion, cost_function=cost_function)
248-
trainDROModel('Lag', epochs, steps_adv, gamma, 'elu', batch_size, loss_criterion, cost_function=cost_function)
265+
trainDROModel('Lag', epochs, steps_adv, gamma, 'relu',
266+
batch_size, loss_criterion, cost_function=cost_function)
267+
trainDROModel('Lag', epochs, steps_adv, gamma, 'elu',
268+
batch_size, loss_criterion, cost_function=cost_function)

0 commit comments

Comments
 (0)