논문 읽기/Face Recognition

[논문 읽기] PyTorch 구현 코드로 살펴보는 FaceNet(2015), A Unified Embedding for Face Recognition and Clustering

AI 꿈나무 2021. 6. 12. 22:38
반응형

 안녕하세요, 오늘 읽은 논문은 FaceNet, A Unified Embedding for Face Recognition and Clustering 입니다. 해당 논문은 Triplet Loss를 처음으로 제안한 논문이기 때문에, 호기심에 읽게 되었네요 ㅎㅎ PyTorch 구현 코드와 함께 살펴보도록 하겠습니다.

 

 

 FaceNet은 anchor(특정 사람 얼굴 사진), positive(동일한 사람의 얼굴 사진), negative(다른 사람의 얼굴 사진) 세 개의 사진을 CNN에 전달하여 152차원의 embedding을 생성합니다. (CNN 구조를 수정하면 152차원을 갖는 embedding을 출력할 수 있도록 할 수 있습니다. 아래에서 어떻게 수정했는지 PyTorch 코드로 살펴보겠습니다.)

 

 

 3개의 embedding vector를 갖게 되는데, anchor embedding, positive embedding 사이의 L2 distance와 anchor embedding, negative embedding 사이의 L2 distance를 측정합니다. 두 개의 L2 distance를 입력값으로 triplet loss를 계산한 후에 이를 최소화 하는 방향으로 학습을 진행합니다. triplet loss를 최소화하는 방향으로 학습을 진행한다면, anchor와 positive embedding 사이의 거리는 가깝게, anchor와 negative embedding 사이의 거리는 멀게 학습이 진행됩니다.

 

 CNN으로 embedding을 생성하는 FaceNet의 장점은 pose에 invariance합니다. 또한 sota 성능을 달성합니다.

 

 

Model Architecture

 얼굴 사진을 CNN에 입력하면 152차원의 embedding vector을 출력합니다. 어떻게 embedding vector를 생성하도록 CNN을 구현하는지 PyTorch 코드로 살펴보겠습니다. 해당 논문에서는 ZFNet 또는 Inception Net을 사용하지만, 저는 편의를 위해 ResNet18을 사용하겠습니다.

 

  
import torch.nn as nn
from torch.nn import functional as F
from .utils_resnet import resnet18

class FaceNet_ResNet18(nn.Module):
    def __init__(self, embedding_dimension=128, pretrained=False):
        super().__init__()
        self.model = resnet18(pretrained=pretrained)
        
        # embedding
        input_features_fc_layer = self.model.fc.in_features # fc layer 채널 수 얻기
        self.model.fc = nn.Linear(input_features_fc_layer, embedding_dimension, bias=False) # fc layer 수정
        
    def forward(self, images):
        embedding = self.model(images) # embedding 생성
        embedding = F.normalize(embedding, p=2, dim=1) # normalize
        return embedding

 

 위 모델의 출력값은 152 차원을 가진 embedding을 출력합니다. 즉, 얼굴 사진을 입력하면 얼굴 사진을 152 차원의 벡터로 표현하는 것입니다. 무조건 152 차원을 가진 embedding을 출력하도록 설정하지 않아도 됩니다. 논문에서는 152 차원이 가장 성능이 좋다고 말합니다.

 

Triplet Loss

 이번 논문에서 핵심 내용은 triplet loss를 살펴보겠습니다.

 

 triplet loss는 다음과 같이 정의됩니다.

 

 x^a는 anchor, x^p는 positive, x^n은 negative image를 의미합니다. 알파는 margin을 의미합니다. 위 손실함수를 최소화 하는 것은 아래 식을 만족하도록 학습합니다.

 

 

 즉, anchor와 positive embedding 사이의 거리 + margin이 anchor와 negative embedding 사이의 거리보다 작아지도록 학습하는 것입니다. anchor와 positive는 가까워지고, anchor와 negative는 멀어지도록 학습합니다.

 

 Pytorch code로는 다음과 같이 구현합니다. anchor, positive, negative의 embedding와 margin을 입력으로 받습니다.

import torch
from torch.autograd import Function
from torch.nn.modules.distance import PairwiseDistance


class TripletLoss(Function):

    def __init__(self, margin):
        super(TripletLoss, self).__init__()
        self.margin = margin
        self.pdist = PairwiseDistance(p=2)

    def forward(self, anchor, positive, negative):
        pos_dist = self.pdist.forward(anchor, positive)
        neg_dist = self.pdist.forward(anchor, negative)

        hinge_dist = torch.clamp(self.margin + pos_dist - neg_dist, min=0.0)
        loss = torch.mean(hinge_dist)

        return loss

 

Triplet selection

 논문에서는 hard negative, hard positive method를 제안합니다. 학습하기 어려운 positive와 negative sample을 추출하여 모델이 어려운 환경에서 학습되도록 합니다. 학습 초기에는 hard example 방법이 모델의 불안정함을 유발하므로 초기에는 all positive sample로 학습을 진행합니다.

 

 hard positive sample은 다음의 조건을 만족하는 sample을 의미합니다.

 위 수식은 anchor와 positive 사이의 embedding 거리가 최대인 smaple을 의미합니다. 일정 임계점 거리 이상이면 negative로 분류되는데, negative로 분류되기 전까지의 거리가 최대인 sample을 의미합니다.

 

 hard negative sample은 다음의 조건을 만족합니다.

 negative sample이 되는 임계점에 가까운 negative sample은 hard negative sample입니다. 모델이 positive인지 negative인지 판별하기 어려운 sample로 학습한다면 더 좋은 성능을 나타냅니다. 이러한 방법은 RetinaNet, word2vec등 여러 task에서 활용하고 있습니다.

 


참고자료

[1] https://arxiv.org/pdf/1503.03832.pdf

 

코드 출처

[1] https://github.com/tamerthamoqa/facenet-pytorch-glint360k

반응형