256 KiB
256 KiB
!pip install -q torch_snippets
from torch_snippets import *
!wget -q https://www.dropbox.com/s/ua1rr8btkmpqjxh/face-detection.zip
!unzip -q face-detection.zip
device = 'cuda' if torch.cuda.is_available() else 'cpu'
class SiameseNetworkDataset(Dataset):
def __init__(self, folder, transform=None, should_invert=True):
self.folder = folder
self.items = Glob(f'{self.folder}/*/*')
self.transform = transform
def __getitem__(self, ix):
itemA = self.items[ix]
person = fname(parent(itemA))
same_person = randint(2)
if same_person:
itemB = choose(Glob(f'{self.folder}/{person}/*', silent=True))
else:
while True:
itemB = choose(self.items)
if person != fname(parent(itemB)):
break
imgA = read(itemA)
imgB = read(itemB)
if self.transform:
imgA = self.transform(imgA)
imgB = self.transform(imgB)
return imgA, imgB, np.array([1-same_person])
def __len__(self):
return len(self.items)
from torchvision import transforms
trn_tfms = transforms.Compose([
transforms.ToPILImage(),
transforms.RandomHorizontalFlip(),
transforms.RandomAffine(5, (0.01,0.2),
scale=(0.9,1.1)),
transforms.Resize((100,100)),
transforms.ToTensor(),
transforms.Normalize((0.5), (0.5))
])
val_tfms = transforms.Compose([
transforms.ToPILImage(),
transforms.Resize((100,100)),
transforms.ToTensor(),
transforms.Normalize((0.5), (0.5))
])
trn_ds = SiameseNetworkDataset(folder="./data/faces/training/", transform=trn_tfms)
val_ds = SiameseNetworkDataset(folder="./data/faces/testing/", transform=val_tfms)
trn_dl = DataLoader(trn_ds, shuffle=True, batch_size=64)
val_dl = DataLoader(val_ds, shuffle=False, batch_size=64)
2020-10-05 17:18:49.095 | INFO | torch_snippets.loader:Glob:161 - 370 files found at ./data/faces/training//*/* 2020-10-05 17:18:49.097 | INFO | torch_snippets.loader:Glob:161 - 30 files found at ./data/faces/testing//*/*
def convBlock(ni, no):
return nn.Sequential(
nn.Dropout(0.2),
nn.Conv2d(ni, no, kernel_size=3, padding=1, padding_mode='reflect'),
nn.ReLU(inplace=True),
nn.BatchNorm2d(no),
)
class SiameseNetwork(nn.Module):
def __init__(self):
super(SiameseNetwork, self).__init__()
self.features = nn.Sequential(
convBlock(1,4),
convBlock(4,8),
convBlock(8,8),
nn.Flatten(),
nn.Linear(8*100*100, 500), nn.ReLU(inplace=True),
nn.Linear(500, 500), nn.ReLU(inplace=True),
nn.Linear(500, 5)
)
def forward(self, input1, input2):
output1 = self.features(input1)
output2 = self.features(input2)
return output1, output2
class ContrastiveLoss(torch.nn.Module):
"""
Contrastive loss function.
Based on: http://yann.lecun.com/exdb/publis/pdf/hadsell-chopra-lecun-06.pdf
"""
def __init__(self, margin=2.0):
super(ContrastiveLoss, self).__init__()
self.margin = margin
def forward(self, output1, output2, label):
euclidean_distance = F.pairwise_distance(output1, output2, keepdim = True)
loss_contrastive = torch.mean((1-label) * torch.pow(euclidean_distance, 2) +
(label) * torch.pow(torch.clamp(self.margin - euclidean_distance, min=0.0), 2))
acc = ((euclidean_distance > 0.6) == label).float().mean()
return loss_contrastive, acc
def train_batch(model, data, optimizer, criterion):
imgsA, imgsB, labels = [t.to(device) for t in data]
optimizer.zero_grad()
codesA, codesB = model(imgsA, imgsB)
loss, acc = criterion(codesA, codesB, labels)
loss.backward()
optimizer.step()
return loss.item(), acc.item()
@torch.no_grad()
def validate_batch(model, data, criterion):
imgsA, imgsB, labels = [t.to(device) for t in data]
codesA, codesB = model(imgsA, imgsB)
loss, acc = criterion(codesA, codesB, labels)
return loss.item(), acc.item()
model = SiameseNetwork().to(device)
criterion = ContrastiveLoss()
optimizer = optim.Adam(model.parameters(),lr = 0.001)
n_epochs = 200
log = Report(n_epochs)
for epoch in range(n_epochs):
N = len(trn_dl)
for i, data in enumerate(trn_dl):
loss, acc = train_batch(model, data, optimizer, criterion)
log.record(epoch+(1+i)/N, trn_loss=loss, trn_acc=acc, end='\r')
N = len(val_dl)
for i, data in enumerate(val_dl):
loss, acc = validate_batch(model, data, criterion)
log.record(epoch+(1+i)/N, val_loss=loss, val_acc=acc, end='\r')
if (epoch+1)%20==0: log.report_avgs(epoch+1)
if epoch==10: optimizer = optim.Adam(model.parameters(), lr=0.0005)
EPOCH: 20.000 trn_loss: 0.663 trn_acc: 0.660 val_loss: 0.602 val_acc: 0.600 (16.50s - 148.47s remaining) EPOCH: 40.000 trn_loss: 0.430 trn_acc: 0.762 val_loss: 0.465 val_acc: 0.667 (32.57s - 130.28s remaining) EPOCH: 60.000 trn_loss: 0.395 trn_acc: 0.746 val_loss: 0.371 val_acc: 0.700 (48.54s - 113.26s remaining) EPOCH: 80.000 trn_loss: 0.255 trn_acc: 0.851 val_loss: 0.220 val_acc: 0.867 (64.58s - 96.87s remaining) EPOCH: 100.000 trn_loss: 0.252 trn_acc: 0.852 val_loss: 0.381 val_acc: 0.767 (80.24s - 80.24s remaining) EPOCH: 120.000 trn_loss: 0.212 trn_acc: 0.900 val_loss: 0.214 val_acc: 0.800 (95.98s - 63.99s remaining) EPOCH: 140.000 trn_loss: 0.251 trn_acc: 0.879 val_loss: 0.385 val_acc: 0.833 (111.54s - 47.80s remaining) EPOCH: 160.000 trn_loss: 0.201 trn_acc: 0.891 val_loss: 0.257 val_acc: 0.833 (127.11s - 31.78s remaining) EPOCH: 180.000 trn_loss: 0.156 trn_acc: 0.920 val_loss: 0.400 val_acc: 0.733 (142.95s - 15.88s remaining) EPOCH: 200.000 trn_loss: 0.174 trn_acc: 0.909 val_loss: 0.377 val_acc: 0.767 (158.47s - 0.00s remaining)
log.plot_epochs(['trn_loss', 'val_loss'], log=True, title='Variation in training and validation loss')
log.plot_epochs(['trn_acc', 'val_acc'], title='Variation in training and validation accuracy')
0%| | 0/200 [00:00<?, ?it/s]/usr/local/lib/python3.6/dist-packages/numpy/core/fromnumeric.py:3335: RuntimeWarning: Mean of empty slice. out=out, **kwargs) /usr/local/lib/python3.6/dist-packages/numpy/core/_methods.py:161: RuntimeWarning: invalid value encountered in double_scalars ret = ret.dtype.type(ret / rcount) 100%|██████████| 200/200 [00:00<00:00, 5808.36it/s]
100%|██████████| 200/200 [00:00<00:00, 5617.91it/s]
model.eval()
val_dl = DataLoader(val_ds,num_workers=6,batch_size=1,shuffle=True)
dataiter = iter(val_dl)
x0, _, _ = next(dataiter)
for i in range(2):
_, x1, label2 = next(dataiter)
concatenated = torch.cat((x0*0.5+0.5, x1*0.5+0.5),0)
output1,output2 = model(x0.cuda(),x1.cuda())
euclidean_distance = F.pairwise_distance(output1, output2)
output = 'Same Face' if euclidean_distance.item() < 0.6 else 'Different'
show(torchvision.utils.make_grid(concatenated),
title='Dissimilarity: {:.2f}\n{}'.format(euclidean_distance.item(), output))
plt.show()