Face Verification - ResNet50 + ArcFace
512-D L2-normalised face embeddings. ResNet50 (ImageNet-initialised) fine-tuned with a hand-implemented ArcFace additive-angular-margin loss plus batch-hard triplet loss on LFW. No face-recognition library is used - the only pretrained component is the ImageNet backbone.
Embeddings are unit-norm, so cosine similarity is a plain dot product.
Results
Measured on 120 LFW identities held out of training (identity-disjoint split, so no test person appears anywhere in training).
| Metric | Value |
|---|---|
| ROC-AUC | 0.9587 |
| Equal Error Rate | 9.61% |
| Verification accuracy | 90.60% @ cosine threshold 0.1838 |
| Rank-1 / Rank-5 | 64.00% / 85.62% |
| TAR @ FAR = 1% | 72.76% |
| TAR @ FAR = 0.1% | 43.76% |
| Open-set DIR @ FPIR = 1% | 28.62% |
The threshold is fitted on the validation split and applied unchanged to test.
Scoring all 530,965 test pairs rather than a 10,000-pair sample gives AUC 0.9638 and EER 8.81% - within 0.005 AUC of the sampled protocol, so the sampling is representative and what changes is resolution at low FAR.
Masked faces (MLFW)
Same checkpoint, same threshold, same pairing protocol, both datasets through the same detection and alignment pipeline.
| Dataset | ROC-AUC | EER | TAR @ FAR 1% |
|---|---|---|---|
| LFW (unmasked) | 0.9587 | 9.61% | 72.76% |
| MLFW (masked) | 0.8388 | 24.58% | 35.36% |
MLFW is restricted to the 1,779 of its 2,996 identities absent from training; the rest overlap, because MLFW derives from CALFW which derives from LFW. This model never saw a masked face during training, and the gap is a real degradation rather than a protocol artefact.
Usage
import torch, numpy as np
from huggingface_hub import hf_hub_download
from model import load_model, embed # from the GitHub repo below
path = hf_hub_download("yashMaini/face-verification-resnet50", "best_model.pth")
model = load_model(path, "cpu")
z = embed(model, ["a.jpg", "b.jpg"], "cpu") # (2, 512), L2-normalised
print(float(z[0] @ z[1]) > 0.1838) # same person?
Faces must be detected and aligned first - dataset_preparation.py in the
GitHub repo does this with YuNet 5-point landmarks mapped onto the canonical
ArcFace template. Feeding raw uncropped photographs will degrade results.
Details
- Input 224x224 RGB, ImageNet mean/std normalisation
- ResNet50 -> 2048 pool -> dropout 0.4 -> Linear(512, no bias) -> BatchNorm -> L2 norm
- ArcFace scale 30, margin 0.5; batch-hard triplet margin 0.3 on cosine distance
- P x K sampling, 16 identities x 4 images per batch
- Trained on 1,500 identities / ~7,000 images
- Embeddings use horizontal-flip test-time augmentation
Checkpoint sha256: db30180907e023cc0e3e5cb7e4d1d763305523a80f9997572b50f54e27947366
Limitations
Trained on ~7,000 images - orders of magnitude smaller than the corpora behind production face recognition, so absolute accuracy sits below published LFW figures. LFW skews heavily towards light-skinned adult males in frontal poses, so these results are not evidence of fairness across demographics. Open-set rejection is measured rather than assumed, and it is weak: at 1% FPIR only 28.62% of enrolled probes are correctly identified, meaning most unenrolled faces are not confidently rejected. Masked-face performance degrades sharply.
Not suitable for surveillance, law enforcement, or any deployment where a false match carries consequences for the person misidentified.
Code, evaluation pipeline and full report: https://github.com/Yashmaini30/face-verification-resnet50