Custom Face Recognition Model Training (ArcFace, Metric Learning)

We design and deploy artificial intelligence systems: from prototype to production-ready solutions. Our team combines expertise in machine learning, data engineering and MLOps to make AI work not in the lab, but in real business.
Showing 1 of 1All 1566 services
Custom Face Recognition Model Training (ArcFace, Metric Learning)
Medium
~5 days
Frequently Asked Questions

AI Development Areas

AI Solution Development Stages

Latest works

  • image_website-b2b-advance_0.webp
    B2B ADVANCE company website development
    1320
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1226
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    927
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1161
  • image_logo-advance_0.webp
    B2B Advance company logo design
    622
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    897

Custom Face Recognition Model Training: From Dataset to Production

Imagine: your company is growing, new employees arrive daily, and key-card access is insecure. You decide to implement face recognition. But a standard Softmax classifier won't cut it — it requires retraining on every new employee. The solution is training a custom model with ArcFace loss, which can generalize to unseen identities. Our team — AI engineers with 5+ years of experience in computer vision, completed 30+ face recognition projects. We guarantee accuracy of 99.5% on LFW.

ArcFace is the industry standard: 99.5% accuracy on LFW, compact embeddings, noise robustness. It outperforms Softmax by up to 10% in open-set tasks, especially when new identities appear after deployment. Below we describe the training setup: backbone selection, margin tuning, dataset processing.

ArcFace Loss: Math and Implementation

ArcFace adds an additive angular margin m to the angle between the embedding and the corresponding class center:

import torch
import torch.nn as nn
import torch.nn.functional as F
import math

class ArcFaceLoss(nn.Module):
    def __init__(
        self,
        embedding_size: int = 512,
        num_classes: int = 10000,
        margin: float = 0.5,      # angular margin in radians (~28.6°)
        scale: float = 64.0       # logit scale
    ):
        super().__init__()
        self.margin = margin
        self.scale  = scale
        # Trainable class centers (normalized)
        self.weight = nn.Parameter(
            torch.FloatTensor(num_classes, embedding_size)
        )
        nn.init.xavier_uniform_(self.weight)

        self.cos_m = math.cos(margin)
        self.sin_m = math.sin(margin)
        self.th    = math.cos(math.pi - margin)   # threshold for numerical stability
        self.mm    = math.sin(math.pi - margin) * margin

    def forward(
        self,
        embeddings: torch.Tensor,   # (B, embedding_size), L2-normalized
        labels: torch.Tensor        # (B,)
    ) -> torch.Tensor:
        # L2-normalize weights
        W = F.normalize(self.weight, dim=1)

        # cos(θ) = emb · W^T
        cosine = F.linear(embeddings, W)      # (B, num_classes)
        sine   = torch.sqrt(1.0 - cosine.pow(2).clamp(0, 1))

        # cos(θ + m) = cos(θ)cos(m) - sin(θ)sin(m)
        phi = cosine * self.cos_m - sine * self.sin_m

        # Numerical stability: if θ > π - m, use cosine penalty
        phi = torch.where(cosine > self.th, phi, cosine - self.mm)

        # One-hot target mask
        one_hot = torch.zeros_like(cosine)
        one_hot.scatter_(1, labels.view(-1, 1), 1)

        # Replace logit only for the correct class
        output = one_hot * phi + (1.0 - one_hot) * cosine
        output *= self.scale

        return F.cross_entropy(output, labels)

Backbone and Embedding: Which to Choose?

InsightFace / ArcFace typically uses ResNet-50/100 or IResNet. For production on mobile devices — MobileFaceNet:

import timm

def build_face_recognition_model(
    backbone: str = 'resnet50',      # 'resnet100', 'mobilenetv3_small'
    embedding_size: int = 512,
    pretrained: bool = True
) -> nn.Module:

    class FaceEmbedder(nn.Module):
        def __init__(self):
            super().__init__()
            self.backbone = timm.create_model(
                backbone,
                pretrained=pretrained,
                num_classes=0,        # remove classifier head
                global_pool='avg'
            )
            feat_dim = self.backbone.num_features
            self.bn   = nn.BatchNorm1d(feat_dim)
            self.drop = nn.Dropout(p=0.4)
            self.fc   = nn.Linear(feat_dim, embedding_size, bias=False)
            self.bn2  = nn.BatchNorm1d(embedding_size)

        def forward(self, x: torch.Tensor) -> torch.Tensor:
            feat = self.backbone(x)
            feat = self.bn(feat)
            feat = self.drop(feat)
            emb  = self.fc(feat)
            emb  = self.bn2(emb)
            return F.normalize(emb, dim=1)   # L2-normalization

    return FaceEmbedder()

Threshold Selection for Open-Set Recognition

In production, the system encounters new people not seen in training. We use cosine similarity threshold:

import numpy as np
from scipy.spatial.distance import cosine

class FaceRecognitionSystem:
    def __init__(
        self,
        model: nn.Module,
        threshold: float = 0.4   # cosine distance; tuned via ROC
    ):
        self.model = model.eval()
        self.threshold = threshold
        self.gallery: dict[str, np.ndarray] = {}  # id → embedding

    def enroll(self, person_id: str, face_image: torch.Tensor) -> None:
        """Register a new face in the gallery"""
        with torch.no_grad():
            emb = self.model(face_image.unsqueeze(0))
        self.gallery[person_id] = emb.cpu().numpy().squeeze()

    def identify(
        self,
        face_image: torch.Tensor,
        top_k: int = 1
    ) -> list[dict]:
        """Search gallery — 1:N identification"""
        with torch.no_grad():
            query_emb = self.model(face_image.unsqueeze(0))
        query_np = query_emb.cpu().numpy().squeeze()

        distances = {
            person_id: cosine(query_np, gallery_emb)
            for person_id, gallery_emb in self.gallery.items()
        }
        sorted_matches = sorted(distances.items(), key=lambda x: x[1])

        results = []
        for person_id, dist in sorted_matches[:top_k]:
            results.append({
                'identity': person_id if dist < self.threshold else 'unknown',
                'distance': float(dist),
                'confidence': float(1 - dist)
            })
        return results

The cosine distance threshold is tuned via ROC curve on your test set. Optimal threshold is 0.35–0.45 for most enterprise scenarios. We use TAR@FAR=0.1% as the target metric.

Why ArcFace is the Industry Standard?

ArcFace produces compact clusters without the complex triplet mining of FaceNet. It maintains accuracy even on noisy datasets. More details on the loss can be found in the ArcFace paper.

Metrics and Comparison of Loss Functions

Metric Value Application
TAR@FAR=0.1% 98.5%+ Phone unlock
TAR@FAR=0.01% 95%+ Physical access
TAR@FAR=0.001% 90%+ Forensics
1:1 Verification AUC > 0.998 Document verification

Loss function comparison:

Loss LFW Acc IJB-C TAR@FAR=0.1% Complexity Application
Softmax 98.8% 91.3% Low Closed set
CosFace 99.3% 94.1% Low Standard
ArcFace 99.5% 95.6% Low Standard
AdaFace 99.6% 96.8% Medium Low quality photos
ElasticFace 99.6% 96.4% Medium General

Process and Timeline

We don't just train a model — we build a complete solution. The process includes:

  1. Dataset analysis: quality assessment, quantity, recommendations.
  2. Data preparation: face alignment, augmentation (flip, rotation, blur), train/val/test split.
  3. Backbone and loss selection: from MobileNet to ResNet-100 based on your dataset and hardware.
  4. Training: balanced sampling, monitoring via Weights & Biases.
  5. Validation: TAR@FAR on your test set, ROC analysis.
  6. Quantization and export: INT8/FP16 for edge, ONNX for CPU.
  7. Deployment: Docker + Triton/ONNX, REST/gRPC API, documentation.
Stage Result
Dataset analysis Report: quality, quantity, recommendations
Data preparation Face alignment, augmentation, split
Model training Loss selection, backbone, hyperparams
Validation TAR@FAR on your test set
Deployment Docker + Triton/ONNX, REST API
Documentation API docs, operation guide
Support 3 months warranty maintenance

What's Included in the Work?

The deliverable includes:

  • Trained model with chosen backbone and loss (ArcFace by default).
  • API documentation, deployment instructions.
  • Docker image with model and testing scripts.
  • 3 months warranty support and updates.

Timelines:

  • Fine-tuning ArcFace on corporate data — 3–5 weeks.
  • Full 1:N system with gallery — 5–8 weeks.
  • Custom pipeline (detection + alignment + recognition) — 8–14 weeks.

Cost is calculated based on your dataset and requirements. For example, fine-tuning a model on a dataset of 10,000 identities starts at $5,000. A full system including detection and gallery is typically under $20,000. Contact us to discuss the details.

Typical Mistakes When Training Face Recognition Models

  • Using Softmax for open-set tasks — accuracy drops by 5–10%.
  • Not applying L2 normalization to embeddings — metric learning does not converge.
  • Forgetting face alignment — accuracy drops by 3–5%.
  • Setting the threshold too low — avalanche of false positives.

If you want a consultation on your dataset, contact us. Order custom face recognition model training — we'll evaluate the project within 1 day.

Pricing: Fine-tuning starts from $5,000, full 1:N system from $15,000. Get a free estimate within 24 hours.