Face100K-Edge
A ~100K-parameter depthwise-separable CNN face encoder trained from scratch (no distillation, no pretrained teacher) as a 10-class closed-set classifier on a small private family dataset plus 12,000 public negative faces (CASIA-WebFace, CelebA, DigiFace-1M, FFHQ-128 samples relabeled as background). This release publishes the encoder only (classification head removed): 112x112 aligned BGR crop in, 192-dimensional embedding out.
Sibling releases: TinyFaceNet-1M (distilled, 1.03M params) and TinyFaceNet-300K-Smallplain (distilled, 232K params). Face100K-Edge is the smallest and the only one trained without a teacher.
No personal images, real identity names, identity mapping, enrollment embeddings, centroids or classification weights are included. The encoder needs a separately supplied, consented enrollment gallery to identify anyone.
✨ Highlights
- 3x smaller than Smallplain and 10x smaller than the 1M sibling — and the only one trained without a teacher.
- ~5 ms per face on a Raspberry Pi 4 at 4 threads (201.8 images/s) — 5.9x faster than Smallplain, 13.5x faster than 1M v2.
- 93.88% private family accuracy (46/49), within one face of a model 3x its size, measured on an untouched test set.
- 99.45% stranger rejection in the closed-set classifier variant — carried by 12K public negatives, not architecture.
Results
Same protocols as the Smallplain release: LFW all 6,000 official pairs, 10 contiguous folds, threshold chosen on the other 5,400 pairs; private set is 59 crops (49 family + 10 background), nearest-training-centroid argmax.
| Model | Params | LFW BGR mean ± fold SD | Legacy RGB LFW | Private family (49) | Private overall (59) |
|---|---|---|---|---|---|
| 1M v2 | 1.03M | 73.22% ± 1.84 | 63.62% | 95.92% | 89.83% |
| Smallplain | 299K | 73.60% ± 1.47 | 63.17% | 95.92% | 91.53% |
| Face100K-Edge | 100K | 70.87% ± 1.97 | 63.17% | 93.88% | 81.36% |
✨ Highlight: at 100K params (10x smaller than 1M v2), family accuracy drops only ~2 points (93.88% vs 95.92%) — and this row is the only one measured on an untouched test set.
Two protocol differences in Face100K-Edge's favor to disclose, and one against:
- The private numbers here are untouched-test: the final epoch was taken with no checkpoint selection. The 1M/Smallplain private numbers reused the holdout for best-checkpoint selection (disclosed in those model cards), so this row is conservatively measured relative to the others.
- The low private-overall number comes from the background class: trained on 12K diverse public negatives, the encoder's background centroid does not match the 10 specific background crops in the holdout (2/10 via centroid argmax). Family accuracy (46/49) is within one face of Smallplain (47/49).
- Face100K-Edge saw 12K public faces as negatives during training; the distilled models saw only the private dataset (but inherited a teacher trained on millions of faces).
Stranger rejection (closed-set classifier variant, not included in this release)
The same architecture trained with its classification head rejects unseen DigiFace-1M faces as background at 99.45% (2,000 random synthetic strangers, 11 false accepts). The variant trained without public negatives managed only 61.2% — the negatives, not the architecture, carry open-set robustness.
Speed (Raspberry Pi 4 Model B, 4 GB)
| CPU threads | Model | Median ms | p90 ms | Images/s |
|---|---|---|---|---|
| 1 | Face100K-Edge | 12.35 | 13.55 | 81.0 |
| 1 | Smallplain 300K | 51.82 | 53.36 | 19.3 |
| 1 | 1M v2 | 166.48 | 170.48 | 6.0 |
| 4 | Face100K-Edge | 4.95 | 5.32 | 201.8 |
| 4 | Smallplain 300K | 29.20 | 43.63 | 34.2 |
| 4 | 1M v2 | 66.78 | 113.65 | 15.0 |
✨ Highlight: ~5 ms per face at 4 threads (201.8 images/s) — 5.9x faster than Smallplain, 13.5x faster than 1M v2 on the same board.
Measured 2026-10-12 on Raspberry Pi 4 Model B Rev 1.4 (Cortex-A72 @ 1.8 GHz), Raspberry Pi OS Bookworm (aarch64), Python 3.11, ONNX Runtime 1.31.0, FP32 opset-17 exports verified against PyTorch (< 1e-6 max abs diff). 10 warmup + 50 measured single-image calls; synthetic input; excludes detection, alignment, I/O and gallery matching. The board was running its normal resident services (~2.6 GB RAM in use), SoC at 52 °C, no throttling. The Face100K-Edge timing above is the full classifier (100,138 params); the published encoder is marginally cheaper.
Usage
pip install torch safetensors numpy opencv-python
import cv2, torch
import torch.nn.functional as F
from safetensors.torch import load_file
from model import Face100kEncoder
model = Face100kEncoder().eval()
model.load_state_dict(load_file('model.safetensors'), strict=True)
crop = cv2.imread('aligned_crop.png') # BGR, five-point aligned, 112x112
x = torch.from_numpy(crop).permute(2, 0, 1).unsqueeze(0).float() / 127.5 - 1
with torch.inference_mode():
emb = F.normalize(model(x), dim=1) # (1, 192)
model.onnx is the same encoder (input x: 1x3x112x112 float32, output emb: 1x192, not L2-normalized — normalize before cosine). Do not convert the BGR input to RGB.
Training
From scratch, single seed, CPU (~25 minutes). 1,852 private crops (same train split as the Smallplain release, split SHA-256 a0a8a755fd5c63009e8b00c6715db3f40fd358bce90c1659104e1c46d693b21c) + 12,000 public negatives. AdamW lr 3e-3, weight decay 1e-4, cosine annealing, 30 epochs, batch 64, class-weighted cross-entropy (weights capped at 15x), random flip / ±8 px shift / brightness / contrast augmentation. train_reference.py is the exact training script; it cannot be rerun without the withheld private dataset.
Limitations
Trained to separate 9 specific people from everyone else; LFW 70.87% confirms it is not a general face recognizer. One seed, one training run, 59-crop private holdout (±2 faces ≈ ±4%). No liveness or spoof resistance. The public negative datasets carry their own licenses/terms (CASIA-WebFace and DigiFace-1M are research-use); their images are not redistributed here. Not validated for security-critical identification.
- Downloads last month
- 21