← Усі статті

Farm Vision — коли валідація бреше

Історія про те, як модель із блискучою валідацією провалилась на реальних фото, і чому єдиним чесним суддею став golden set, зібраний руками.

Опубліковано: 4 хв читання

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 → напевно кінь». Коли я наводив камеру на екран, стиль ставав «інтернетним», і будь-яка тварина ставала конем.

Лікування — три удари по шпаргалці:

  1. Мультидоменність. Кожен клас має мати дані щонайменше з 2–3 різних джерел і стилів. Кіт вилікувався (0.67 → 15/15) простим додаванням OIDv4-датасету. Козу й корову врятували чотири різні джерела кожній.
  2. Аугментація при тренуванні (mosaic, mixup, зсуви кольору, повороти): кожне фото щоепохи випадково перекручується, і модель не може запам’ятати стиль конкретного датасета.
  3. Новий пункт чек-листа даних: різноманітність стилів усередині класу, а не 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 → три ітерації балансування.

Але головний висновок не технічний. Найважча частина машинного навчання — не тренування моделі, а знання, чи можна вірити її метрикам. Валідація бреше, коли дані тренування й дані застосування — різного стилю. Єдиний захист від цієї брехні — набір, зібраний руками саме для того, щоб їй не піддатись.