Custom Face Recognition Model Training (ArcFace, Metric Learning)

When your business grows and key-card access systems become insecure and inconvenient, face recognition comes to the rescue. We train custom ArcFace and FaceNet models that reliably distinguish employees and guests without retraining for each new person. Our team delivers turnkey projects—from dataset preparation to deployment and ongoing support—ensuring stable operation within your infrastructure.

AI Development Areas

Frequently Asked Questions

Latest works

  • Development of a web application for FEEDME
    Development of a web application for FEEDME
    1344
  • Development of an online store for the company FURNORO
    Development of an online store for the company FURNORO
    1307
  • B2B Advance company logo design
    B2B Advance company logo design
    754
  • Development of a web application for Enviok
    Development of a web application for Enviok
    1050
  • AIDER company logo development
    AIDER company logo development
    994
  • CRM development for Chasseurs
    CRM development for Chasseurs
    1099

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 an estimate within 24 hours.