Farm Vision — коли валідація бреше
Історія про те, як модель із блискучою валідацією провалилась на реальних фото, і чому єдиним чесним суддею став golden set, зібраний руками.
Farm Vision — мобільний детектор людей і тварин, що працює повністю на iPhone, без хмари та інтернету. Дев’ять класів у реальному часі: людина, кіт, собака, кінь, корова, вівця, коза, свиня, олень. Прикладна мета проста — знати, хто ходить уночі по двору на фермі.
Але справжня історія проєкту не про застосунок. Вона про один момент, коли модель показала чудові метрики й водночас не працювала — і про те, як я це з’ясував.
Демо, яке зібралось швидко
Технічний старт був легким. Flutter, офіційний плагін ultralytics_yolo,
модель yolo26n — найменша з сімейства. Плагін бере на себе камеру, інференс
і малювання рамок, тому найризикованіший етап — ручне декодування вихідних
тензорів YOLO — відпав сам собою. За кілька днів був робочий застосунок:
навів камеру, з’явились рамки з підписами. Локально, офлайн, реальний час.
Готова модель знає 80 класів COCO, але серед них немає кози. Щоб відрізняти козу від собаки — а це і була початкова мета, «щоб не було 300 собак на полі», — модель треба було дотренувати на власних дев’яти класах. Тут і почалась справжня робота.
Модель, яка склала іспит по власному конспекту
Перша версія навчилась у Google Colab і показала блискучу валідацію:
mAP для кози 0.872, для корови 0.982. За метриками — готовий продукт.
Я не повірив цифрам і зібрав окремий тест: golden set — 120 фотографій, по 15 на клас, вручну знайдених у Google, різного стилю: пасовища, крупні плани, різні фони, чорно-білі. Головна умова — ці фото ніколи не потрапляли в тренування.
Результат: 45 зі 120. Коза, корова й вівця — 0 із 15. Модель, що на валідації «знала» корову на 98%, на реальних фото не впізнавала її жодного разу.
Причина називається domain shift. Валідаційна вибірка береться з тих самих датасетів, що й тренування. Якщо всі кози в датасеті — фермерські кадри одного стилю, модель блищить на таких самих фермерських valid-фото і провалюється на студійних чи «інтернетних». Це іспит, складений за власним конспектом.
Правило, яке я забрав звідси назавжди: валідаційний mAP — це орієнтир напрямку, а не оцінка якості. Рішення ухвалюються лише за golden set.
Модель вчила стиль фото, а не тварину
Копаючи глибше, я помітив майже ідеальну закономірність: успіх класу на golden set збігався з тим, чи має він різностильне джерело даних. Класи з окремих «інтернетних» датасетів (кінь, олень, кіт) — 15 із 15. Класи лише з одного фермерського датасета (коза, корова) — 0 із 15.
Це shortcut learning. Замість справжньої ознаки — «як виглядає коза» — модель знайшла легшу шпаргалку: «фото в стилі ферми → напевно коза; фото в стилі Google → напевно кінь». Коли я наводив камеру на екран, стиль ставав «інтернетним», і будь-яка тварина ставала конем.
Лікування — три удари по шпаргалці:
- Мультидоменність. Кожен клас має мати дані щонайменше з 2–3 різних джерел і стилів. Кіт вилікувався (0.67 → 15/15) простим додаванням OIDv4-датасету. Козу й корову врятували чотири різні джерела кожній.
- Аугментація при тренуванні (
mosaic,mixup, зсуви кольору, повороти): кожне фото щоепохи випадково перекручується, і модель не може запам’ятати стиль конкретного датасета. - Новий пункт чек-листа даних: різноманітність стилів усередині класу, а не 3000 однакових кадрів з одного двору.
Три ітерації й гойдалка між класами
Прогрес по golden set був монотонним:
| Версія | Результат | Що зробили |
|---|---|---|
| v1 | 45/120 | базовий датасет — виявлено domain shift |
| v2 | 74/120 | додано «інтернетні» джерела; кіт 3→15 |
| v3 | 90/120 | різностильні джерела для корови й кози; Open Images через FiftyOne |
Головне досягнення: коза 0 → 14/15, корова 0 → 15/15. Початкова мета проєкту досягнута — Farm Vision відрізняє козу від собаки.
Але посилення одних класів має ціну. У v3 з’явилась гойдалка між схожими класами: я влив чотири джерела кіз і сім тисяч фото корів — і коза з коровою піднялись, але вівця просіла (10 → 3, плутає з козою) і свиня теж (7 → 5, плутає з коровою). Посилення класу тисне на схожого сусіда: коза↔вівця, корова↔свиня. Ідеального балансу не буває; кожна ітерація зміщує рівновагу.
Я свідомо зупинився на v3 і задокументував межу, а не заховав її. План виправлення (підняти вівцю й свиню мультидоменними даними, не ріжучи агресивно козу й корову) записаний — але робити його зараз означало б ще один цикл Colab-мук заради шліфування вже досягнутої мети.
Пастка, у яку легко впасти під час тесту
Один окремий урок вартий згадки. Коли v3 «погано» впізнавала тварин на екрані ноутбука — це був не провал моделі, а невалідний тест. Перезйомка екрана додає муар, відблиски й змінює стиль зображення на «пере-цифровий». Модель училась на живих фото, не на фотографіях фотографій.
Чесний тест — це жива тварина або рівна роздруківка, а не камера, наведена на монітор. Симптом виникає на шляху вимірювання, а не в об’єкті вимірювання.
Що з цього забрати
Технічно я пройшов повний ML-цикл, який у справжніх командах займає місяці: збір даних → domain shift → діагностика shortcut learning → чесне тестування → Open Images через FiftyOne → три ітерації балансування.
Але головний висновок не технічний. Найважча частина машинного навчання — не тренування моделі, а знання, чи можна вірити її метрикам. Валідація бреше, коли дані тренування й дані застосування — різного стилю. Єдиний захист від цієї брехні — набір, зібраний руками саме для того, щоб їй не піддатись.