Посмотреть как Markdown

API ансамбля

LibreEnsemble прогоняет несколько детекторов по одному и тому же изображению и сливает их детекции в один Results. Слияние происходит после собственной постобработки каждого участника, поэтому участники сохраняют свой размер входа, нормализацию и подавление.

LibreEnsemble

python
LibreEnsemble(
    members,
    *,
    weights=None,
    fusion="wbf",
    fusion_iou=0.55,
    min_votes=1,
)
АргументПо умолчаниюЗначение
membersДва или более детектора
weightsNoneКоэффициенты доверия по участникам; без указания все равны 1.0
fusion"wbf""wbf", "wbf_seeded", "nms" или вызываемый объект
fusion_iou0.55Порог IoU для кластеризации при слиянии
min_votes1Оставлять только рамки, подтверждённые как минимум таким числом участников

Участником может быть путь к весам, который разрешает фабрика LibreYOLO(), уже созданная модель, экспортированный бэкенд или ExternalDetector. Каждый участник должен быть моделью с задачей детекции.

Два участника, слияние по умолчанию
from libreyolo import LibreEnsemble, SAMPLE_IMAGE ens = LibreEnsemble(["LibreYOLO9t.pt", "LibreYOLO9s.pt"]) # Один источник-изображение возвращает один Results, а не список.result = ens(SAMPLE_IMAGE, conf=0.25) print(result.boxes.xyxy)print(result.speed)
Консенсус и пороги по участникам
from libreyolo import LibreEnsemble, SAMPLE_IMAGE ens = LibreEnsemble(    ["LibreYOLO9t.pt", "LibreYOLO9s.pt"],    weights=[1.0, 2.0],    fusion="wbf",    fusion_iou=0.55,    min_votes=2,)result = ens(SAMPLE_IMAGE, conf=[0.25, 0.4])print(len(result))

Создание завершается ошибкой, если участников меньше двух, у списка weights неверная длина, какой-то вес неположителен, min_votes — не целое положительное число или min_votes больше числа участников. Сочетание fusion="nms" с min_votes > 1 тоже вызывает ошибку, потому что NMS отбрасывает принадлежность к кластеру и не может считать голоса.

weights масштабирует доверие к каждому участнику. Больший вес тянет итоговые координаты и оценки в сторону этого участника. По соглашению их задают пропорционально mAP на валидации.

Пространства классов

Участники с одинаковым names проходят насквозь. Иначе пространства классов объединяются по именам, id классов участников переводятся через таблицы соответствия, а итоговый Results.names — это объединение. Рамки сливаются только внутри одного объединённого класса, поэтому класс, который знает лишь один участник, проходит без слияния. При несовпадении на этапе создания в лог пишется предупреждение.

min_votes по каждому классу ограничивается сверху числом участников, в чьих пространствах меток этот класс есть, — так консенсус остаётся осмысленным при частично общих словарях.

Вызов ансамбля

python
ens(
    source=None,
    *,
    conf=0.25,
    iou=0.45,
    imgsz=None,
    device=None,
    classes=None,
    max_det=300,
    augment=False,
    save=False,
    output_path=None,
    color_format="auto",
    batch=1,
    stream=False,
    stream_buffer=False,
    vid_stride=1,
    show=False,
    **kwargs,
)

predict — псевдоним для __call__. Возвращается обычный Results, у которого speed расписывает затраты по участникам и добавляет запись fusion. Один источник-изображение возвращает один такой объект, список или папка — список, а stream=True — генератор.

conf, iou и device применяются ко всем участникам, но принимают и по одному значению на участника, поэтому conf=[0.25, 0.4] даёт участнику 0 порог 0.25, а участнику 1 — порог 0.4. imgsz применяется ко всем, когда это int или кортеж, и читается по участникам, только когда это список, поэтому imgsz=(480, 640) — один прямоугольный размер для всех, а imgsz=[480, 640] — 480 для участника 0 и 640 для участника 1. Каждое значение должно быть допустимым для семейства своего участника.

augment применяется к тем участникам, которые поддерживают аугментацию на инференсе, а экспортированные бэкенды его игнорируют. classes принимает id объединённых классов, а max_det применяется к итоговому результату, поэтому участники работают с запасом, а ансамбль обрезает результат один раз. batch принимается для совместимости API; изображения обрабатываются последовательно.

val() и export() вызывают NotImplementedError. Валидировать и экспортировать участников нужно по отдельности.

ExternalDetector

python
ExternalDetector(fn: Callable, names: dict[int, str])

Превращает любой вызываемый детектор в участника. fn принимает PIL-изображение и возвращает (boxes, scores, labels), где рамки заданы как xyxy в пикселях исходного изображения, а метки — id классов, допустимые в names. Подходят тензоры, массивы и вложенные списки. LibreYOLO ничего не импортирует из внешнего кода.

Адаптер проверяет возвращаемое значение: это должен быть кортеж из трёх элементов, рамки должны иметь форму (N, 4), все три массива — одинаковую длину, а каждый id класса должен быть в names. Детекции с уверенностью не выше conf отбрасываются до слияния.

Операции слияния

Примитивы слияния — самостоятельные torch-операции в libreyolo.ops. Они не привязаны к моделям, и импортировать их можно независимо от ансамбля — поэтому они и экспортируются отдельно.

Операция слияния без модели
import torchfrom libreyolo.ops import weighted_boxes_fusion boxes = torch.tensor([[10.0, 10.0, 50.0, 50.0], [12.0, 11.0, 51.0, 49.0]])scores = torch.tensor([0.9, 0.8])labels = torch.tensor([0, 0])model_ids = torch.tensor([0, 1]) fused = weighted_boxes_fusion(    boxes, scores, labels, model_ids, num_models=2, iou_thr=0.55)print(fused)

Все три принимают одни и те же позиционные аргументы — boxes, scores, labels, model_ids — и возвращают (boxes, scores, labels).

ОперацияКлюч в реестреПоведение
weighted_boxes_fusionwbfПоследовательный weighted boxes fusion, точно по статье
wbf_seededwbf_seededПараллельный вариант той же свёртки в один проход
nms_fusionnmsСклеивает всё и применяет NMS с учётом классов

FUSIONS сопоставляет три ключа реестра вызываемым объектам, и LibreEnsemble ищет в нём значение fusion=.

python
weighted_boxes_fusion(
    boxes, scores, labels, model_ids,
    *,
    weights=None,
    num_models=None,
    iou_thr=0.55,
    skip_box_thr=0.0,
    conf_type="avg",
    min_votes=1,
    models_per_label=None,
    label_weights=None,
)

У wbf_seeded сигнатура точно такая же. nms_fusion принимает те же аргументы, кроме conf_type, и вызывает ValueError, когда min_votes > 1.

В weighted_boxes_fusion детекции обходятся в порядке убывания уверенности, домноженной на вес. Каждая либо попадает в тот существующий кластер, с чьей текущей слитой рамкой она пересекается лучше всего — при IoU выше iou_thr и с той же меткой, — либо открывает новый кластер. Слитая рамка кластера — это среднее координат его участников, взвешенное по уверенности, а её оценка — взвешенное среднее или максимум их уверенностей, пересчитанные так, чтобы рамки, подтверждённые меньшим числом моделей, получали меньшую оценку.

wbf_seeded выбирает затравки кластеров с помощью NMS с учётом классов при iou_thr, приписывает каждую детекцию к затравке с той же меткой и наибольшим IoU, а затем сворачивает каждый кластер так же. Формы кластеров не меняются посреди прохода, поэтому вся операция — тензорная арифметика с фиксированными формами. Два варианта совпадают, когда кластеры однозначны, и могут слегка расходиться на цепочках перекрывающихся кластеров.

nms_fusion оставляет рамку с наибольшей уверенностью в каждой перекрывающейся группе, без изменений. Веса weights по моделям масштабируют уверенности только для ранжирования при подавлении, а выжившие рамки сохраняют исходные оценки.

Своё слияние

fusion= принимает и вызываемый объект с той же сигнатурой, что у операций выше. Его имя записывается в ens.fusion, а если имени нет — "custom". Возвращаемое значение проверяется: это должна быть тройка (boxes, scores, labels) с согласованными формами.

Сигнатуры и значения по умолчанию прочитаны из libreyolo/ensemble/model.py и libreyolo/ops/fusion.py на версии v1.5.0. Замысел из docs/adr/0004-model-ensembling.md.