Skip to content
This repository was archived by the owner on Aug 21, 2020. It is now read-only.

Commit 7eb0227

Browse files
authored
Merge pull request #47 from zhampel/master
First standalone runtime script + webdoc structure
2 parents abdf643 + 6e1fe78 commit 7eb0227

19 files changed

Lines changed: 916 additions & 15 deletions

cyphercat/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
from .__version__ import __version__
44

55
from .utils import *
6+
from .train import *
67
from .models import *
78
from .metrics import *
89
from .load_data import *
10+
from .definitions import *

cyphercat/definitions.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import os
2+
3+
CYCAT_DIR = os.path.dirname(os.path.abspath(__file__))
4+
REPO_DIR = os.path.split(CYCAT_DIR)[0]

cyphercat/load_data.py

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ def custom_preprocessor(out_dir=''):
1616
----------
1717
out_dir : string
1818
directory of unpacked data set
19+
1920
"""
2021

2122
# Get name of data set from output directory
@@ -56,23 +57,18 @@ def custom_preprocessor(out_dir=''):
5657
# For LFW
5758
if 'lfw' in data_name.lower():
5859

59-
os.rename(os.path.join(out_dir, 'lfw/'), os.path.join(out_dir, 'lfw_original/'))
60-
61-
lfw_dir = os.path.join(out_dir, 'lfw_original/')
60+
lfw_dir = out_dir + '_original/'
61+
os.rename(out_dir, lfw_dir)
62+
6263
people_dir = os.listdir(lfw_dir)
6364

6465
num_per_class = 20
6566

66-
new_dir = os.path.join(out_dir, 'lfw_' + str(num_per_class))
67-
68-
if not os.path.isdir(new_dir):
69-
os.makedirs(new_dir)
70-
7167
for p in people_dir:
7268
imgs = os.listdir(os.path.join(lfw_dir, p))
7369
if len(imgs) >= num_per_class:
74-
shutil.copytree(os.path.join(lfw_dir, p), os.path.join(new_dir, p))
75-
70+
shutil.copytree(os.path.join(lfw_dir, p), os.path.join(out_dir, p))
71+
7672
print('{} successfully downloaded and preprocessed.'.format(data_name))
7773

7874

cyphercat/train.py

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
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()

cyphercat/utils/config_utils.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import sys
55
import yaml
66
from .utils import set_to_string, keys_to_string, color_mode_dict
7+
from cyphercat.definitions import REPO_DIR
78

89

910

@@ -76,7 +77,7 @@ def __init__(self, dataset=None):
7677
" Required fields: {}\nExiting...\n".format(set_to_string(self.reqs)))
7778

7879
self.name = dataset.get('name')
79-
self.data_path = dataset.get('datapath')
80+
self.data_path = self.test_abs_path(dataset.get('datapath'))
8081
self.data_type = dataset.get('datatype').lower()
8182
self.url = dataset.get('url', '')
8283
self.save_path = os.path.join(self.data_path, self.name)
@@ -109,7 +110,14 @@ def __init__(self, dataset=None):
109110
elif dtype_ind == 1:
110111
self.length = float(dataset.get('length'))
111112
self.seconds = float(dataset.get('seconds'))
112-
113+
114+
# Test if path is absolute or relative
115+
def test_abs_path(self, path=''):
116+
if path.startswith('/'):
117+
return path
118+
else:
119+
return os.path.join(REPO_DIR, path)
120+
113121
# Consecutive integers default data labels
114122
def default_labels(self):
115123
return str(list(range(0, self.n_classes))).strip('[]')

cyphercat/utils/file_utils.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,8 @@ def unpacker(compressed_file_name='', out_directory=''):
8383
# Unpack gzipfile
8484
elif 'gz' in file_ext:
8585
with tarfile.open(compressed_file_name) as tar:
86-
tar.extractall(path=out_directory)
86+
tar.extractall(os.path.split(out_directory)[0])
87+
#tar.extractall(path=out_directory)
8788
else:
8889
print('File extension {} not recognized for unpacking.\nExiting...')
8990
sys.exit()

docs/checklist.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ Everything that must be done to make it happen!
3838
- [ ] fini! (complete)
3939

4040
### Documentation
41-
- [ ] contributing guide
41+
- [x] contributing guide
4242
- [ ] gh-pages
4343
- [ ] detailed descriptions of models/attacks
4444
- [x] pas fini (incomplete)
11 KB
Loading
17.6 KB
Loading
39.2 KB
Loading

0 commit comments

Comments
 (0)