Обучение моделей распознавания лиц: от датасета до 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 # Numerical stability: если θ > π - 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 задаче — accuracy падает на 5–10%.
- Не делать L2-нормализацию embedding — metric learning не сходится.
- Забывать про face alignment — без него точность падает на 3–5%.
- Ставить слишком низкий threshold — лавина false positives.
Если хотите получить консультацию по вашему датасету, свяжитесь с нами. Закажите обучение модели распознавания лиц — мы оценим проект за 1 день.







