Ансамблювання детекторів
LibreEnsemble запускає два або більше детекторів на тому самому декодованому зображенні та зливає їхні рамки в один об'єкт Results. Учасники зберігають власні ваги, пороги, пристрої та списки класів.
Що таке ансамбль
LibreEnsemble приймає два або більше детекторів, запускає кожен на тому самому
зображенні та зливає їхні рамки в один Results. Це конструкція для етапу
передбачення: навчати нічого не потрібно, а учасники залишаються незалежними
моделями, які можна окремо валідувати й експортувати.
Підтримується лише завдання виявлення об'єктів. Учасник з будь-яким іншим
завданням спричиняє ValueError під час створення із зазначенням індексу
учасника та його завдання.
Обидві назви імпортуються ліниво, тому не потребують ресурсів до використання:
from libreyolo import LibreEnsemble, ExternalDetectorСтворення ансамблю
from libreyolo import LibreEnsemble, SAMPLE_IMAGE # Учасниками можуть бути шляхи до контрольних точок або вже завантажені моделі.ensemble = LibreEnsemble(["LibreYOLO9s.pt", "LibreRFDETRs.pt"]) result = ensemble(SAMPLE_IMAGE)for xyxy, conf, cls in zip( result.boxes.xyxy.tolist(), result.boxes.conf.tolist(), result.boxes.cls.tolist(),): print(result.names[int(cls)], round(float(conf), 3), xyxy)from libreyolo import LibreEnsemble, SAMPLE_IMAGE ensemble = LibreEnsemble( ["LibreYOLO9s.pt", "LibreRFDETRs.pt"], weights=[1.0, 1.3], # за домовленістю, пропорційно до валідаційного mAP fusion="wbf", fusion_iou=0.55, min_votes=2, # залишати лише рамки, знайдені обома учасниками) result = ensemble(SAMPLE_IMAGE)print(len(result.boxes), "agreed detections")from libreyolo import LibreEnsemble, SAMPLE_IMAGE ensemble = LibreEnsemble(["LibreYOLO9s.pt", "LibreRFDETRs.pt"]) # Скаляр застосовується до кожного учасника; список зчитується окремо для кожного.result = ensemble(SAMPLE_IMAGE, conf=[0.3, 0.5], iou=0.5)print(len(result.boxes))LibreEnsemble(
members,
*,
weights=None,
fusion="wbf",
fusion_iou=0.55,
min_votes=1,
)members є послідовністю з двох або більше елементів. Елемент типу str або
Path завантажується через LibreYOLO(); будь-який інший має бути викликаним
і надавати словник names. Менше двох елементів спричиняє ValueError, а
передавання окремого рядка спричиняє TypeError замість ітерування його символів.
Типове значення weights дорівнює None, що означає рівномірне зважування.
Потрібно надати по одній строго додатній вазі для кожного учасника, тому нульова
вага спричиняє помилку, а не непомітне вилучення учасника. Документована
домовленість полягає в установленні ваг пропорційно до валідаційного mAP кожного учасника.
Типове значення fusion_iou дорівнює 0.55; це IoU, за якого рамки різних
учасників об'єднуються в кластер. Цей поріг відрізняється від iou окремого
виклику, що задає власний параметр NMS кожного учасника.
Типове значення min_votes дорівнює 1, тобто рамку може підтримати будь-який
один учасник. Збільшення значення залишає лише кластери, підтверджені відповідною
кількістю різних учасників. Це має бути додатне ціле число, не більше за кількість
учасників. Для кожного класу значення обмежується кількістю учасників, які фактично
знають цей клас, тому клас, на якому навчено лише одного учасника, не зникає непомітно.
Методи злиття
Приймаються три назви, а також довільний викликаний об'єкт.
fusion | Поведінка |
|---|---|
"wbf" | Послідовне зважене злиття рамок, що точно відповідає статті. Типовий варіант |
"wbf_seeded" | Однопрохідне зважене злиття рамок; NMS з урахуванням класів вибирає початкові елементи кластерів |
"nms" | Об'єднання рамок усіх учасників із подальшим NMS з урахуванням класів |
Зважене злиття рамок усереднює координати кластера з вагами на основі
впевненості, утворюючи рамку, якої не запропонував жоден окремий учасник. Два
зважені варіанти збігаються, коли кластери однозначні, і можуть трохи відрізнятися
для ланцюжків перекривних кластерів. "nms" вибирає одну рамку замість усереднення,
тому рамки, що залишилися, зберігають початкові оцінки, а ваги впливають лише на
переможця. Оскільки цей метод вибирає, а не кластеризує, він не може підраховувати
голоси: поєднання fusion="nms" із min_votes, більшим за 1, спричиняє ValueError.
Зважене злиття рамок масштабує оцінку кластера за часткою ваг учасників, які його
підтримали. За двох однаково зважених учасників рамка, яку знайшов лише один із
них, зберігає половину оцінки: 0.9 стає 0.45. Тому злита впевненість може
стати нижчою за conf, із яким запущено кожного учасника. Фільтруйте за злитою
оцінкою, не припускаючи, що поріг учасника все ще діє.
Учасники з різними списками класів
Учасники не зобов'язані мати спільний список класів. Їхні простори міток
об'єднуються за назвою, а кожен учасник отримує таблицю відповідності для
перетворення власних ідентифікаторів класів на ідентифікатори об'єднання.
ensemble.names є цим об'єднанням, яке також містить повернений Results.
Рамки зливаються лише в межах однакової назви класу. Клас, відомий тільки одному учаснику, проходить без злиття й без штрафу: для масштабування оцінки використовується знаменник окремо для класу, тому клас, відомий одному учаснику, зберігає свою оцінку.
За часткового перетину в журнал записується попередження з назвами класів, які
не є спільними для всіх учасників. Це попередження потрібно читати уважно,
оскільки контрольна точка з назвами-заповнювачами на кшталт class_0 створює
об'єднання, що не перетинається з жодним іншим учасником, і злиття між
учасниками взагалі не відбувається.
Якщо учасник повертає ідентифікатор класу поза власним names, виникає RuntimeError.
Зовнішні детектори
from libreyolo import ExternalDetector, LibreEnsemble, SAMPLE_IMAGE def my_detector(pil_image): # Повернути (boxes, scores, labels): xyxy у пікселях початкового зображення. return ([[100.0, 100.0, 200.0, 300.0]], [0.9], [0]) external = ExternalDetector(my_detector, names={0: "person"}) ensemble = LibreEnsemble(["LibreYOLO9s.pt", external])result = ensemble(SAMPLE_IMAGE)print(len(result.boxes))ExternalDetector(fn, names) обгортає будь-який викликаний об'єкт, що приймає
зображення PIL і повертає (boxes, scores, labels), де рамки задано як xyxy у
пікселях початкового зображення. Він перевіряє кількість аргументів, форму рамок,
узгодженість довжин і наявність кожного ідентифікатора класу в names, а також
самостійно застосовує поріг conf.
Так у злитті може брати участь детектор, не завантажений LibreYOLO.
Виклик
from libreyolo import LibreEnsemble ensemble = LibreEnsemble(["LibreYOLO9s.pt", "LibreRFDETRs.pt"]) # Замініть clip.mp4 на відеофайл із диска.for result in ensemble("clip.mp4", stream=True, vid_stride=2): print(result.frame_idx, len(result.boxes))Сигнатура виклику відповідає окремій моделі й приймає ті самі джерела:
зображення, папки, списки, відео, запис екрана, вебкамери та мережеві потоки.
Для джерел наживо потрібен stream=True з тієї самої причини, що й в інших місцях.
| Аргумент | Типове значення | Примітки |
|---|---|---|
conf | 0.25 | Для кожного учасника; скаляр транслюється на всіх або задається одне значення на учасника |
iou | 0.45 | Власний поріг NMS кожного учасника, не поріг злиття |
imgsz | None | list зчитується для кожного учасника; int або кортеж транслюється на всіх |
device | None | Скаляр або одне значення на учасника, тому учасники можуть працювати на різних пристроях |
classes | None | Фільтрує злитий результат за ідентифікаторами класів об'єднання |
max_det | 300 | Застосовується до злитого результату |
Оскільки list для imgsz означає значення на учасника, imgsz=[480, 640]
задає 480 для першого учасника й 640 для другого, тоді як imgsz=(480, 640)
задає один прямокутний розмір для всіх. Цю відмінність легко пропустити.
Учасники викликаються з max_det щонайменше 300 незалежно від запитаного
значення, тому кожен працює з достатнім запасом, а ансамбль обрізає результат
один раз наприкінці.
Зображення декодується один раз, і той самий об'єкт передається кожному учаснику.
batch приймається для узгодженості й ігнорується; зображення обробляються послідовно.
Повернений результат
Повертається звичайний Results, той самий тип, що повертає окрема модель, з
names, установленим у простір об'єднаних класів. Усе з розділу
Робота з результатами застосовується без змін.
Єдина відмінність полягає в тому, що ансамбль заповнює result.speed. Його ключі
мають вигляд member_0, member_1 тощо, а також fusion; значення наведено в
мілісекундах. Це єдине місце в бібліотеці, де заповнюється speed.
Рядки з нескінченними або невизначеними рамками чи оцінками вилучаються перед злиттям. Коли учасники працюють на різних пристроях, злиття виконується на пристрої першого учасника, який щось повернув.
Обмеження ансамблю
val() і export() спричиняють NotImplementedError та вказують на учасників:
валідуйте й експортуйте кожного окремо. Методу train взагалі немає, тому його
виклик спричиняє AttributeError.
Половинна точність не керується на рівні ансамблю. half=True потрапляє в той
самий попереджений шлях без дії, що й скрізь; налаштовуйте точність для кожного учасника.
Інтерфейсу командного рядка для ансамблювання немає. Доступний лише Python API.