pytorch孿生網絡識別面部相似度代碼解讀

本文章記錄最近看的一個孿生網絡實現人臉面部相似度的代碼實例,關於孿生網絡的定義,可以點擊這裏,該項目所使用的的網絡架構爲標準的卷積神經網絡架構,在每個卷積層之後使用批量歸一化(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

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章