Навчання моделей розпізнавання облич: від набору даних до production
Уявіть: ваша компанія зростає, щодня приходять нові співробітники, а система доступу за ключ-картами небезпечна. Ви вирішуєте впровадити розпізнавання облич. Але звичайний Softmax-класифікатор не справляється — він вимагає перенавчання на кожному новому співробітнику. Рішення — навчання кастомної моделі з ArcFace loss, яка вміє узагальнювати на незнайомі identity. Наша команда — AI-інженери з 5+ роками досвіду в computer vision, виконали 30+ проєктів з розпізнавання облич. Гарантуємо точність до 99.5% LFW.
ArcFace — стандарт індустрії: точність 99.5% на LFW, компактні ембендінги, стійкість до шуму. Але щоб отримати таку якість, потрібно правильно налаштувати навчання: вибрати backbone, підібрати margin, обробити набір даних. Нижче — як ми це робимо.
ArcFace loss: математика та реалізація
ArcFace додає адитивний кутовий margin m до кута між embedding та відповідним центром класу:
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, # кутовий margin в радіанах (~28.6°)
scale: float = 64.0 # масштаб логітів
):
super().__init__()
self.margin = margin
self.scale = scale
# Навчальні центри класів (нормалізовані)
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) # поріг для числової стабільності
self.mm = math.sin(math.pi - margin) * margin
def forward(
self,
embeddings: torch.Tensor, # (B, embedding_size), L2-нормалізовані
labels: torch.Tensor # (B,)
) -> torch.Tensor:
# L2-нормалізація ваг
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
# Числова стабільність: якщо θ > π - m, використовуємо косинусний 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)
# Замінюємо logit лише для правильного класу
output = one_hot * phi + (1.0 - one_hot) * cosine
output *= self.scale
return F.cross_entropy(output, labels)
Backbone та embedding: який обрати?
InsightFace / ArcFace зазвичай використовує ResNet-50/100 або IResNet. Для production на мобільних пристроях — 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, # прибираємо 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-нормалізація
return FaceEmbedder()
Як вибрати threshold для open-set recognition?
У production система зустрічає нових людей, яких не було в train. Використовуємо 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; підбирається за 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:
"""Реєстрація нового обличчя в галереї"""
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]:
"""Пошук по галереї — 1:N ідентифікація"""
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
Поріг cosine distance підбирається за ROC-кривою на вашому тесті. Оптимальний threshold — 0.35–0.45 для більшості корпоративних сценаріїв. Ми використовуємо метрику TAR@FAR=0.1% як цільову.
Чому ArcFace — стандарт індустрії?
ArcFace дає компактні кластери без складної добірки триплетів, як у FaceNet. Він стабільно тримає точність навіть на наборах даних із шумом. Детальніше про loss можна прочитати в статті.
Метрики та порівняння методів loss
| Метрика | Значення | Застосування |
|---|---|---|
| TAR@FAR=0.1% | 98.5%+ | Розблокування телефону |
| TAR@FAR=0.01% | 95%+ | Фізичний доступ |
| TAR@FAR=0.001% | 90%+ | Криміналістика |
| 1:1 Verification AUC | > 0.998 | Верифікація документів |
Порівняння loss-функцій:
| Loss | LFW Acc | IJB-C TAR@FAR=0.1% | Складність | Застосування |
|---|---|---|---|---|
| Softmax | 98.8% | 91.3% | Низька | Закрите множество |
| CosFace | 99.3% | 94.1% | Низька | Стандарт |
| ArcFace | 99.5% | 95.6% | Низька | Стандарт |
| AdaFace | 99.6% | 96.8% | Середня | Низька якість фото |
| ElasticFace | 99.6% | 96.4% | Середня | Загальний випадок |
Процес роботи та терміни
Ми не просто тренуємо модель — ми будуємо завершене рішення. Процес включає етапи:
- Аналіз набору даних: оцінка якості, кількості, рекомендації.
- Підготовка даних: face alignment, аугментація (flip, rotation, blur), розбиття на train/val/test.
- Вибір backbone та loss: під ваш набір даних і залізо — від MobileNet до ResNet-100.
- Навчання: balance-семплінг, моніторинг через Weights & Biases.
- Валідація: TAR@FAR на вашому тесті, ROC-аналіз.
- Квантування та експорт: INT8/FP16 для edge, ONNX для CPU.
- Розгортання: Docker + Triton/ONNX, REST/gRPC API, документація.
| Етап | Результат |
|---|---|
| Аналіз набору даних | Звіт: якість, кількість, рекомендації |
| Підготовка даних | Face alignment, аугментація, спліт |
| Навчання моделі | Вибір loss, backbone, hyperparams |
| Валідація | TAR@FAR на вашому тесті |
| Розгортання | Docker + Triton/ONNX, REST API |
| Документація | API docs, інструкція з експлуатації |
| Підтримка | 3 місяці гарантійного супроводу |
Що входить у роботу?
У результат входить:
- Навчена модель з обраним backbone та loss (ArcFace за замовчуванням).
- Документація API, інструкція з розгортання.
- Docker-образ з моделлю та скриптами тестування.
- 3 місяці гарантійної підтримки та оновлень.
Терміни:
- Fine-tuning ArcFace на корпоративні дані — 3–5 тижнів.
- Повна система 1:N з галереєю — 5–8 тижнів.
- Кастомний пайплайн (detection + alignment + recognition) — 8–14 тижнів.
Вартість навчання розраховується під ваш набір даних та вимоги. Замовте консультацію — ми оцінимо проєкт за 1 день. Зв'яжіться з нами, щоб обговорити деталі.
Типові помилки при навчанні моделей розпізнавання облич
- Використовувати Softmax на open-set задачі — точність падає на 5–10%.
- Не робити L2-нормалізацію embedding — metric learning не сходиться.
- Забувати про face alignment — без нього точність падає на 3–5%.
- Ставити надто низький threshold — лавина false positives.
Якщо хочете отримати консультацію по вашому набору даних, зв'яжіться з нами. Замовте навчання моделі розпізнавання облич — ми оцінимо проєкт за 1 день.







