Siamese Network(孪生网络)
?
模型結(jié)構(gòu)
上圖是孿生網(wǎng)絡(luò)的簡單模型結(jié)構(gòu),思路很簡單,就是輸入兩個樣本到同樣的網(wǎng)絡(luò)(參數(shù)結(jié)構(gòu)相同),最后計算兩個網(wǎng)絡(luò)輸出的距離,如果距離較近就認(rèn)為是同一類,較遠(yuǎn)就認(rèn)為是不同的類別,在這里,我們可以使用兩個同樣參數(shù)的CNN,利用CNN從圖像中提取特征。注意這里必須是同樣的CNN,不然兩個不同的CNN,即使輸入相同,輸出也可能認(rèn)為兩者不同。
損失函數(shù)
-
Constrastive loss
-
Triplet loss
-
Softmax loss
-
其他損失函數(shù):比如cosine loss,歐式距離等。
模型偽代碼
模型代碼
class SiameseNetwork(nn.Module):def __init__(self):super(SiameseNetwork, self).__init__()self.cnn1 = nn.Sequential(nn.ReflectionPad2d(1),nn.Conv2d(1, 4, kernel_size=3),nn.ReLU(inplace=True),nn.BatchNorm2d(4),nn.Dropout2d(p=.2),nn.ReflectionPad2d(1),nn.Conv2d(4, 8, kernel_size=3),nn.ReLU(inplace=True),nn.BatchNorm2d(8),nn.Dropout2d(p=.2),nn.ReflectionPad2d(1),nn.Conv2d(8, 8, kernel_size=3),nn.ReLU(inplace=True),nn.BatchNorm2d(8),nn.Dropout2d(p=.2),)self.fc1 = nn.Sequential(nn.Linear(8*100*100, 500),nn.ReLU(inplace=True),nn.Linear(500, 500),nn.ReLU(inplace=True),nn.Linear(500, 5))def forward_once(self, x):output = self.cnn1(x)output = output.view(output.size()[0], -1)output = self.fc1(output)return outputdef forward(self, input1, input2):output1 = self.forward_once(input1)output2 = self.forward_once(input2)return output1, output2 ?損失函數(shù)
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 = margindef forward(self, output1, output2, label):euclidean_distance = F.pairwise_distance(output1, output2)loss_contrastive = torch.mean((1-label) * torch.pow(euclidean_distance, 2) ?(label) * torch.pow(torch.clamp(self.margin - euclidean_distance, min=0.0), 2))return loss_contrastive ?真實案例
基于ORL人臉數(shù)據(jù)集,利用孿生網(wǎng)絡(luò)來進(jìn)行人臉驗證。
數(shù)據(jù)介紹
ORL人臉數(shù)據(jù)集共包含40個不同人的400張圖像,此數(shù)據(jù)集下包含40個目錄,每個目錄下有10張圖像,每個目錄表示一個不同的人。所有的圖像是以PGM格式存儲,灰度圖,圖像大小寬度92,高度為112。對每一個目錄下,這些圖像實在不同的時間、不同的光照、不同的面部表情和面部細(xì)節(jié)環(huán)境下采集的。
可以從http://www.cl.cam.ac.uk/research/dtg/attarchive/facedatabase.html下載此人臉數(shù)據(jù)集。
程序包導(dǎo)入
import torch import torchvision import torch.nn as nn from torch import optim import torch.nn.functional as F import torchvision.transforms as transforms from torch.utils.data import DataLoader,Dataset import matplotlib.pyplot as plt import torchvision.utils import numpy as np import random from PIL import Image import PIL.ImageOps print(torch.__version__) #1.1.0 print(torchvision.__version__) #0.3.0 ? ? #定義一些超參 train_batch_size = 32 ? ? ? #訓(xùn)練時batch_size train_number_epochs = 50 ? ? #訓(xùn)練的epoch ? def imshow(img,text=None,should_save=False): #展示一幅tensor圖像,輸入是(C,H,W)npimg = img.numpy() #將tensor轉(zhuǎn)為ndarrayplt.axis("off")if text:plt.text(75, 8, text, style='italic',fontweight='bold',bbox={'facecolor':'white', 'alpha':0.8, 'pad':10})plt.imshow(np.transpose(npimg, (1, 2, 0))) #轉(zhuǎn)換為(H,W,C)plt.show() ? ? ? def show_plot(iteration,loss):#繪制損失變化圖plt.plot(iteration,loss)plt.show()自定義Dataset和Dataloader
自定義的Dataset需要實現(xiàn) __ getitem __ 和 __ len __ 函數(shù)。每次讀取一對圖像,標(biāo)簽表示差異度,0表示同一個人,1表示不是同一人。
#自定義Dataset類,__getitem__(self,index)每次返回(img1, img2, 0/1) class SiameseNetworkDataset(Dataset):def __init__(self,imageFolderDataset,transform=None,should_invert=True):self.imageFolderDataset = imageFolderDataset ? ?self.transform = transformself.should_invert = should_invertdef __getitem__(self,index):img0_tuple = random.choice(self.imageFolderDataset.imgs) #37個類別中任選一個should_get_same_class = random.randint(0,1) #保證同類樣本約占一半if should_get_same_class:while True:#直到找到同一類別img1_tuple = random.choice(self.imageFolderDataset.imgs) if img0_tuple[1]==img1_tuple[1]:breakelse:while True:#直到找到非同一類別img1_tuple = random.choice(self.imageFolderDataset.imgs) if img0_tuple[1] !=img1_tuple[1]:break ?img0 = Image.open(img0_tuple[0])img1 = Image.open(img1_tuple[0])img0 = img0.convert("L")img1 = img1.convert("L")if self.should_invert:img0 = PIL.ImageOps.invert(img0)img1 = PIL.ImageOps.invert(img1) ?if self.transform is not None:img0 = self.transform(img0)img1 = self.transform(img1)return img0, img1, torch.from_numpy(np.array([int(img1_tuple[1]!=img0_tuple[1])],dtype=np.float32))def __len__(self):return len(self.imageFolderDataset.imgs)#定義文件dataset training_dir = "./data/faces/training/" #訓(xùn)練集地址 folder_dataset = torchvision.datasets.ImageFolder(root=training_dir) ? #定義圖像dataset transform = transforms.Compose([transforms.Resize((100,100)), #有坑,傳入int和tuple有區(qū)別transforms.ToTensor()]) siamese_dataset = SiameseNetworkDataset(imageFolderDataset=folder_dataset,transform=transform,should_invert=False) ? #定義圖像dataloader train_dataloader = DataLoader(siamese_dataset,shuffle=True,batch_size=train_batch_size)可視化數(shù)據(jù)集
vis_dataloader = DataLoader(siamese_dataset,shuffle=True,batch_size=8) example_batch = next(iter(vis_dataloader)) #生成一批圖像 #其中example_batch[0] 維度為torch.Size([8, 1, 100, 100]) concatenated = torch.cat((example_batch[0],example_batch[1]),0) imshow(torchvision.utils.make_grid(concatenated, nrow=8)) print(example_batch[2].numpy())注意torchvision.utils.make_grid用法:將若干幅圖像拼成一幅圖像。內(nèi)部機制是鋪成網(wǎng)格狀的tensor,其中輸入tensor必須是四維(B,C,H,W)。后續(xù)還需要調(diào)用numpy()和transpose(),再用plt顯示。
# https://pytorch.org/docs/stable/_modules/torchvision/utils.html#make_grid torchvision.utils.make_grid(tensor, nrow=8, padding=2, normalize=False, range=None, scale_each=False, pad_value=0) ? #示例 t = torchvision.utils.make_grid(concatenated, nrow=8) concatenated.size() #torch.Size([16, 1, 100, 100]) t.size() #torch.Size([3, 206, 818]) 對于(batch,1,H,W)的tensor,重復(fù)三個channel,詳見官網(wǎng)文檔源碼準(zhǔn)備模型
自定義模型和損失函數(shù)
#搭建模型 class SiameseNetwork(nn.Module):def __init__(self):super().__init__()self.cnn1 = nn.Sequential(nn.ReflectionPad2d(1),nn.Conv2d(1, 4, kernel_size=3),nn.ReLU(inplace=True),nn.BatchNorm2d(4),nn.ReflectionPad2d(1),nn.Conv2d(4, 8, kernel_size=3),nn.ReLU(inplace=True),nn.BatchNorm2d(8), ?nn.ReflectionPad2d(1),nn.Conv2d(8, 8, kernel_size=3),nn.ReLU(inplace=True),nn.BatchNorm2d(8),) ?self.fc1 = nn.Sequential(nn.Linear(8*100*100, 500),nn.ReLU(inplace=True), ?nn.Linear(500, 500),nn.ReLU(inplace=True), ?nn.Linear(500, 5)) ?def forward_once(self, x):output = self.cnn1(x)output = output.view(output.size()[0], -1)output = self.fc1(output)return output ?def forward(self, input1, input2):output1 = self.forward_once(input1)output2 = self.forward_once(input2)return output1, output2#自定義ContrastiveLoss 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)) ?return loss_contrastive訓(xùn)練
net = SiameseNetwork().cuda() #定義模型且移至GPU criterion = ContrastiveLoss() #定義損失函數(shù) optimizer = optim.Adam(net.parameters(), lr = 0.0005) #定義優(yōu)化器 ? counter = [] loss_history = [] iteration_number = 0 ? ? #開始訓(xùn)練 for epoch in range(0, train_number_epochs):for i, data in enumerate(train_dataloader, 0):img0, img1 , label = data#img0維度為torch.Size([32, 1, 100, 100]),32是batch,label為torch.Size([32, 1])img0, img1 , label = img0.cuda(), img1.cuda(), label.cuda() #數(shù)據(jù)移至GPUoptimizer.zero_grad()output1,output2 = net(img0, img1)loss_contrastive = criterion(output1, output2, label)loss_contrastive.backward()optimizer.step()if i % 10 == 0 :iteration_number +=10counter.append(iteration_number)loss_history.append(loss_contrastive.item())print("Epoch number: {} , Current loss: {:.4f}\n".format(epoch,loss_contrastive.item()))show_plot(counter, loss_history)測試
現(xiàn)在用testing文件夾中3個任務(wù)的圖像進(jìn)行測試,注意:模型從未見過這3個人的圖像。
#定義測試的dataset和dataloader ? #定義文件dataset testing_dir = "./data/faces/testing/" #測試集地址 folder_dataset_test = torchvision.datasets.ImageFolder(root=testing_dir) ? #定義圖像dataset transform_test = transforms.Compose([transforms.Resize((100,100)), transforms.ToTensor()]) siamese_dataset_test = SiameseNetworkDataset(imageFolderDataset=folder_dataset_test,transform=transform_test,should_invert=False) ? #定義圖像dataloader test_dataloader = DataLoader(siamese_dataset_test,shuffle=True,batch_size=1) ? ? #生成對比圖像 dataiter = iter(test_dataloader) x0,_,_ = next(dataiter) ? for i in range(10):_,x1,label2 = next(dataiter)concatenated = torch.cat((x0,x1),0)output1,output2 = net(x0.cuda(),x1.cuda())euclidean_distance = F.pairwise_distance(output1, output2)imshow(torchvision.utils.make_grid(concatenated),'Dissimilarity: {:.2f}'.format(euclidean_distance.item()))參考
-
https://github.com/harveyslash/Facial-Similarity-with-Siamese-Networks-in-Pytorch
-
ORL Faces Database介紹_網(wǎng)絡(luò)資源是無限的-CSDN博客_orl數(shù)據(jù)集
-
https://github.com/adambielski/siamese-triplet
總結(jié)
以上是生活随笔為你收集整理的Siamese Network(孪生网络)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 初步使用计算机说课,初步认识计算机说课稿
- 下一篇: CListCtrl::InsertCol