本文章記錄最近看的一個孿生網絡實現人臉面部相似度的代碼實例,關於孿生網絡的定義,可以點擊這裏,該項目所使用的的網絡架構爲標準的卷積神經網絡架構,在每個卷積層之後使用批量歸一化(batch normolization),然後進行dropout。
孿生網絡架構的代碼片段:
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.BatchNorm2d(4)中參數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
在這個結構中,實際上就只有一個網絡。因爲孿生網絡中兩個網絡的權重是相同的,所以我們使用一個模型並連續輸入兩張圖像,並使用兩個圖像來計算損失值,然後反向傳播,更新參數。
在使用pairwise_distance計算完兩張圖片的歐式距離後,使用對比損失作爲目標損失函數
class ContrastiveLoss(torch.nn.Module):
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)
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
代碼中對比損失用數學公式表示爲
其中的DW是兩張圖片的歐式距離。觀察上述的contrastive loss表達式可以發現,這種損失函數可以很好的表達成對樣本的匹配程度。當y=0(即樣本相似)時,損失函數只剩下,此項越小越好。即原本相似的樣本,如果在特徵空間的歐式距離較大,則說明當前的模型不好。當y=1(即樣本不相似)時,損失函數剩下,此項越小越好,也就是說m-Dw越小越好,Dw越大越好。即當樣本不相似時,兩張圖片在特徵空間的的歐氏距離反而小的話,則要加大損失。
數據加載
class SiameseNetworkDataset(Dataset):
def __init__(self,imageFolderDataset,transform=None,should_invert=True):
self.imageFolderDataset = imageFolderDataset
self.transform = transform
self.should_invert = should_invert
def __getitem__(self,index):
img0_tuple = random.choice(self.imageFolderDataset.imgs)
#we need to make sure approx 50% of images are in the same class
should_get_same_class = random.randint(0,1)
if should_get_same_class:
while True:
#keep looping till the same class image is found
img1_tuple = random.choice(self.imageFolderDataset.imgs)
if img0_tuple[1]==img1_tuple[1]:
break
else:
while True:
#keep looping till a different class image is found
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)
網絡架構需要輸入一對圖片以及標籤(相似/不相似)。該實例創建了自定義的數據加載器來達到這個目的。在SiameseNetworkDataset這個類中生成一對圖像和相似度標籤。如果圖像來自同一個類,標籤爲0,否則爲1.
網絡訓練過程
1.向網絡傳送第一張圖片
2.向網絡傳送第二張圖片
3.利用1.2中的輸出特徵值計算損失
4.反向傳播計算梯度
5.使用Adam優化器來更新權重
net = SiameseNetwork()
criterion = ContrastiveLoss()
optimizer = torch.optim.Adam(net.parameters(),0.001,betas=(0.9, 0.99))
counter = []
loss_history = []
iteration_number= 0
for epoch in range(0,Config.train_number_epochs):
for i, data in enumerate(train_dataloader,0):
img0, img1 , label = data
#img0, img1 , label = img0.cpu, img1.cpu, label.cpu
optimizer.zero_grad()
output1,output2 = net(img0,img1)
loss_contrastive = criterion(output1,output2,label)
loss_contrastive.backward()
optimizer.step()
if i %10 == 0 :
print("Epoch number {}\n Current loss {}\n".format(epoch,loss_contrastive.item()))
iteration_number +=10
counter.append(iteration_number)
loss_history.append(loss_contrastive.item())
show_plot(counter,loss_history)
torch.save(net.state_dict(),'net_params.pkl')
最後用測試集判斷相似度
以上就是本人對這個項目的理解,希望對你有幫助,如有不對,懇請指出。
(數據集和代碼github可下:https://github.com/marsmarcin/Siamese-Nets-for-Face-Reco)