Фаза 04 · урок 20
Поиск изображений и метрическое обучение
Цель урока: Поиск повсюду в промышленных системах компьютерного зрения: обнаружение дубликатов, обратный поиск изображений, визуальный поиск («найти похожие товары»), повторная идентификация лиц, повторная идентификация людей для наблюдения,…
Текущий релиз AlexBred.com: первые 100 уроков русскоязычной программы.
Содержание урока
- Цели обучения
- Проблема
- Концепция
- Поиск в общих чертах
- Четыре семейства функций потерь
- Триплетная функция потерь формально
- Косинусное сходство и L2
- Recall@K
- FAISS в одном абзаце
- Поиск на уровне экземпляров и на уровне категорий
- Соберите это
- Шаг 1: триплетная функция потерь
- Шаг 2: поиск полусложных примеров
- Шаг 3: Recall@K
- Шаг 4: собираем всё вместе
- Используйте это
- Внедрите это
- Упражнения
- Ключевые термины
- Дополнительное чтение
Система поиска ранжирует кандидатов по расстоянию в пространстве эмбеддингов. Метрическое обучение — дисциплина, формирующая это пространство так, чтобы расстояния означали именно то, что нужно вам.
Тип: Сборка Языки: Python Предварительные требования: Фаза 4, урок 14 (ViT), фаза 4, урок 18 (CLIP) Время: ~45 минут
Цели обучения
- Объяснять триплетные, контрастивные и прокси-основанные функции потерь метрического обучения и выбирать подходящую для заданного датасета
- Корректно реализовывать L2-нормализацию и косинусное сходство, а также проверять различие между поиском «того же объекта» и «того же класса»
- Строить индекс FAISS, выполнять запросы текстом и изображением и сообщать recall@K на отложенном наборе запросов
- Использовать DINOv2, CLIP и SigLIP как готовые базовые модели для эмбеддингов и понимать, когда выигрывает каждая из них
Проблема
Поиск повсюду в промышленных системах компьютерного зрения: обнаружение дубликатов, обратный поиск изображений, визуальный поиск («найти похожие товары»), повторная идентификация лиц, повторная идентификация людей для наблюдения, сопоставление экземпляров для электронной коммерции. Вопрос продукта всегда один и тот же: «по этому изображению-запросу ранжируй мой каталог».
Всю систему определяют два проектных решения. Эмбеддинг — то, какая модель создаёт векторы. Индекс — то, как находить ближайших соседей в масштабе. Оба к 2026 году стали типовыми компонентами (DINOv2 для эмбеддинга, FAISS для индекса), и это повышает планку: трудная часть состоит в том, чтобы определить, что считается похожим для вашего приложения, а затем сформировать пространство эмбеддингов так, чтобы расстояния этому соответствовали.
Именно этим формированием занимается метрическое обучение. Это небольшая, но очень высокоэффективная дисциплина.
Концепция
Поиск в общих чертах
Четыре семейства функций потерь
| Функция потерь | Требует | Плюсы | Минусы |
|---|---|---|---|
| Контрастивная | (якорь, положительный пример) + отрицательные примеры | Простая, работает с любой меткой пары | Медленно сходится без множества отрицательных примеров |
| Триплетная | (якорь, положительный, отрицательный) | Интуитивна; непосредственное управление отступом | Поиск сложных триплетов дорог |
| NT-Xent / InfoNCE | Пары + отрицательные примеры, найденные в батче | Масштабируется на большие батчи | Нужен большой батч или очередь momentum |
| Прокси-основанная (ProxyNCA) | Только метки классов | Быстрая, стабильная, не требует поиска пар | Может переобучаться на прокси на маленьких датасетах |
Для большинства промышленных случаев начните с предобученной базовой модели и добавляйте тонкую настройку с метрическим обучением, только если готовые эмбеддинги показывают слабый результат на вашем тестовом наборе.
Триплетная функция потерь формально
L = max(0, ||f(a) - f(p)||^2 - ||f(a) - f(n)||^2 + margin)
Притягивайте якорь a к положительному примеру p, отталкивайте его от отрицательного примера n, оставляя margin, который гарантирует зазор. Структура из трёх изображений обобщается на любое упорядочивание по сходству.
Поиск примеров важен: лёгкие триплеты (n уже далеко от a) дают нулевую потерю; сеть обучают только сложные триплеты. Поиск полусложных примеров (n дальше, чем p, но находится в пределах отступа) — это рецепт FaceNet 2016 года, и он всё ещё преобладает.
Косинусное сходство и L2
Две метрики, два соглашения:
- Косинусная: угол между векторами. Требует L2-нормализованных эмбеддингов.
- L2: евклидово расстояние. Работает с необработанными или нормализованными эмбеддингами, но обычно сочетается с L2-нормализацией и квадратом L2.
Для большинства современных сетей эти две метрики эквивалентны: ||a - b||^2 = 2 - 2 cos(a, b), когда ||a|| = ||b|| = 1. Выберите соглашение, соответствующее обучению ваших эмбеддингов; их смешение незаметно меняет смысл «ближайшего».
Recall@K
Стандартная метрика поиска:
recall@K = fraction of queries where at least one correct match is in the top K results
Сообщайте recall@1, @5 и @10 рядом друг с другом. Recall@10 выше 0,95 при recall@1 ниже 0,5 означает, что пространство эмбеддингов имеет правильную структуру, но ранжирование шумное — попробуйте более длительную тонкую настройку или шаг повторного ранжирования.
Для обнаружения дубликатов важнее precision@K, поскольку каждый ложноположительный результат заметен пользователю. Для визуального поиска продуктовым сигналом является recall@K.
FAISS в одном абзаце
Facebook AI Similarity Search. Фактический стандарт библиотек для поиска ближайших соседей. Три варианта индекса:
IndexFlatIP/IndexFlatL2— полный перебор, точный поиск, обучение не нужно. Используйте до ~1M векторов.IndexIVFFlat— разбивает пространство на K ячеек и ищет только в нескольких ближайших. Приближённый, быстрый, требует обучающих данных.IndexHNSW— основанный на графе, самый быстрый для множества запросов, большой размер индекса.
Для 100k векторов вам, вероятно, нужен IndexFlatIP с косинусным сходством. Для 10M нужен IndexIVFFlat. Для 100M+ — в сочетании с квантованием произведения (IndexIVFPQ).
Поиск на уровне экземпляров и на уровне категорий
Две очень разные задачи с одним и тем же названием:
- На уровне категорий — «найди кошек в моём каталоге». Сходство, зависящее от класса; готовые эмбеддинги CLIP / DINOv2 работают хорошо.
- На уровне экземпляров — «найди именно этот товар в моём каталоге». Нужна детальная дискриминация между визуально похожими объектами одного класса; готовые эмбеддинги показывают слабый результат; важна тонкая настройка с метрическим обучением.
Всегда выясняйте, какую из этих задач вы решаете, до выбора модели.
Соберите это
Шаг 1: триплетная функция потерь
import torch
import torch.nn.functional as F
def triplet_loss(anchor, positive, negative, margin=0.2):
d_ap = F.pairwise_distance(anchor, positive, p=2)
d_an = F.pairwise_distance(anchor, negative, p=2)
return F.relu(d_ap - d_an + margin).mean()
Одна строка. Работает с L2-нормализованными и необработанными эмбеддингами.
Шаг 2: поиск полусложных примеров
Для батча эмбеддингов и меток найдите самый сложный полусложный отрицательный пример для каждого якоря.
def semi_hard_negatives(emb, labels, margin=0.2):
dist = torch.cdist(emb, emb)
same_class = labels[:, None] == labels[None, :]
diff_class = ~same_class
N = emb.size(0)
positives = dist.clone()
positives[~same_class] = float("-inf")
positives.fill_diagonal_(float("-inf"))
pos_idx = positives.argmax(dim=1)
semi_hard = dist.clone()
semi_hard[same_class] = float("inf")
d_ap = dist[torch.arange(N), pos_idx].unsqueeze(1)
semi_hard[dist <= d_ap] = float("inf")
neg_idx = semi_hard.argmin(dim=1)
fallback_mask = semi_hard[torch.arange(N), neg_idx] == float("inf")
if fallback_mask.any():
hardest = dist.clone()
hardest[same_class] = float("inf")
neg_idx = torch.where(fallback_mask, hardest.argmin(dim=1), neg_idx)
return pos_idx, neg_idx
Каждому якорю назначается самый сложный положительный пример своего класса и полусложный отрицательный пример, который дальше положительного, но находится в пределах отступа.
Шаг 3: Recall@K
def recall_at_k(query_emb, gallery_emb, query_labels, gallery_labels, k=1):
sim = query_emb @ gallery_emb.T
_, top_k = sim.topk(k, dim=-1)
matches = (gallery_labels[top_k] == query_labels[:, None]).any(dim=-1)
return matches.float().mean().item()
Топ-k по скалярному произведению на L2-нормализованных эмбеддингах равен топ-k по косинусному сходству. Сообщайте среднюю долю запросов, для которых среди соседей есть хотя бы один правильный.
Шаг 4: собираем всё вместе
import torch
import torch.nn as nn
from torch.optim import Adam
class Encoder(nn.Module):
def __init__(self, in_dim=128, emb_dim=64):
super().__init__()
self.net = nn.Sequential(
nn.Linear(in_dim, 128), nn.ReLU(),
nn.Linear(128, emb_dim),
)
def forward(self, x):
return F.normalize(self.net(x), dim=-1)
torch.manual_seed(0)
num_classes = 6
protos = F.normalize(torch.randn(num_classes, 128), dim=-1)
def sample_batch(bs=32):
labels = torch.randint(0, num_classes, (bs,))
x = protos[labels] + 0.15 * torch.randn(bs, 128)
return x, labels
enc = Encoder()
opt = Adam(enc.parameters(), lr=3e-3)
for step in range(200):
x, y = sample_batch(32)
emb = enc(x)
pos_idx, neg_idx = semi_hard_negatives(emb, y)
loss = triplet_loss(emb, emb[pos_idx], emb[neg_idx])
opt.zero_grad(); loss.backward(); opt.step()
После нескольких сотен шагов эмбеддинги образуют по одному кластеру на класс.
Используйте это
Промышленные стеки в 2026 году:
- DINOv2 + FAISS — универсальный визуальный поиск. Работает без дополнительной настройки.
- CLIP + FAISS — когда запросы текстовые.
- Тонко настроенный DINOv2 + FAISS — поиск на уровне экземпляров, повторная идентификация лиц, мода, электронная коммерция.
- Milvus / Weaviate / Qdrant — управляемые векторные БД-обёртки вокруг FAISS или HNSW.
Для SOTA-поиска экземпляров рецепт таков: базовая модель DINOv2, добавление головы эмбеддинга, тонкая настройка с триплетной или InfoNCE-функцией потерь на парах с метками экземпляров, индексация в FAISS.
Внедрите это
Этот урок создаёт:
outputs/prompt-retrieval-loss-picker.md— промпт, который выбирает triplet / InfoNCE / ProxyNCA для заданной задачи поиска.outputs/skill-recall-at-k-runner.md— навык, который пишет чистую среду оценки recall@K с разделениями train/val/gallery и корректным контрактом данных.
Упражнения
- (Легко) Запустите приведённый выше игрушечный пример. Постройте эмбеддинги с PCA до и после обучения, чтобы увидеть формирование шести кластеров.
- (Средне) Добавьте реализацию функции потерь ProxyNCA: один обучаемый «прокси» на класс, стандартная кросс-энтропия по косинусному сходству. Сравните скорость сходимости с триплетной функцией потерь на игрушечных данных.
- (Сложно) Возьмите 1 000 валидационных изображений ImageNet, получите их эмбеддинги DINOv2 через HuggingFace, постройте плоский индекс FAISS и сообщите recall@{1, 5, 10} для тех же изображений как запросов (должно быть 1.0) и для отложенного разбиения с метками ImageNet в качестве истинных ответов.
Ключевые термины
| Термин | Как говорят | Что это на самом деле означает |
|---|---|---|
| Метрическое обучение | «Сформировать пространство» | Обучение кодировщика так, чтобы расстояния в его выходном пространстве отражали целевое сходство |
| Триплетная функция потерь | «Притягивай и отталкивай» | L = max(0, d(a, p) - d(a, n) + margin); каноническая функция потерь метрического обучения |
| Поиск полусложных примеров | «Полезные отрицательные примеры» | Отрицательные примеры дальше от якоря, чем положительный, но в пределах отступа; эмпирически наиболее информативны |
| Прокси-основанная функция потерь | «Прототипы классов» | Один обучаемый прокси на класс; кросс-энтропия по сходству с прокси; поиск пар не нужен |
| Recall@K | «Доля попаданий в Top-K» | Доля запросов, у которых хотя бы один правильный результат находится среди первых K |
| Поиск экземпляров | «Найти именно эту вещь» | Детальное сопоставление; готовые признаки обычно показывают слабый результат |
| FAISS | «Библиотека NN» | Библиотека Facebook для ближайших соседей; поддерживает точные и приближённые индексы |
| HNSW | «Графовый индекс» | Иерархический navigable small world; быстрый приближённый NN с малым накладным расходом памяти |
Дополнительное чтение
- FaceNet: A Unified Embedding for Face Recognition (Schroff et al., 2015) — статья о триплетной функции потерь / поиске полусложных примеров
- In Defense of the Triplet Loss for Person Re-Identification (Hermans et al., 2017) — практическое руководство по триплетной тонкой настройке
- Документация FAISS — каждый индекс, каждый компромисс
- SMoT: Metric Learning Taxonomy (Kim et al., 2021) — обзор современных функций потерь и их связей
Источник: оригинальная статья · Ревизия: c8b9b9244f3210b840776675175662dacf208264
← 04.19 — OCR и понимание документов · К оглавлению фазы · 04.21 — Обнаружение ключевых точек и оценка позы →