Kaggle аналіз місцевості Амазонки за супутниковими знімками

Нещодавно на kaggle.com проходило змагання Planet understanding the amazon from space До цього розпізнаванням зображень не займався, тому подумав, що це чудовий шанс навчитися працювати з картинками. Тим паче, що, як запевняють люди в чатику, поріг входження був дуже низький, хтось навіть назвав датасет «MNIST на стероїдах».
Власне, з чого починається кожне змагання — із постановки проблеми та метрики якості. Завдання полягало в наступному: були дані знімки з супутників території Амазонки, і для кожного знімка необхідно було проставити лейбли: дорога, річка, поле, ліс, хмари, ясне небо і так далі (всього 17 штук). Причому однією картинці могли одночасно перебувати кілька класів. Як видно, крім типу місцевості, були ще й класи, що стосуються погодних умов, і, що здається логічним, погода на знімку може бути тільки одна. Ну, не може бути одночасно і ясно, і хмарно. При вирішенні змагання я не дивився очима на дані, сподіваючись, що машина сама розбереться хто чий брат, тому довелося покопатися, щоб навести приклади зображень:

Як таке завдання вирішувати? Взяти якусь згорткову нейронну мережу, передбачену на великому датасеті, і донавчати ваги на своєму наборі картинок. Теоретично я, звичайно, це чув, і все тут начебто зрозуміло, але взяти і реалізувати це поки що руки не доходили. Ну що ж, насамперед потрібно вибрати фреймворк для роботи. Я пішов простим шляхом і використовував keras, тому що у нього дуже хороша документація та зрозумілий людський код.
Тепер трохи докладніше про процедуру донавчання ваг (в галузі це називається Fine-tune). Береться згорткова нейронна мережа,наприклад VGG16, навчена на датасеті ImageNet, що складається з декількох мільйонів картинок, передбачатиме один з 1000 класів. Ну добре, передбачає вона кішку, собаку, машину, ну і що? У нас класи зовсім інші… А сенс у тому, що нижні шари нейромережі вже вміють розпізнавати такі базові компоненти картинки, як лінії та градієнти. Нам залишається лише видалити верхній шар із 1000 нейронів і поставити замість нього свій із 17 нейронів (саме стільки класів може бути на супутникових знімках).
Таким чином, нейромережа передбачатиме ймовірність кожного з 17 класів. Як можна сказати, чи є конкретний клас на картинці? Найпростіша ідея - відсікати по єдиному порогу: якщо ймовірність більша, ніж, наприклад, 0.2, то клас включаємо, якщо менше - то не включаємо. Можна цей поріг підібрати для кожного класу окремо, і я не вигадав нічого розумнішого, ніж незалежно (що звичайно ж неправда, поліпшення одного порога може впливати на підбір іншого) перебирати, як їх називали в чаті, thresholds.
Сказано зроблено. Результат - топ 90% лідерборда. Так, погано, але ж треба з чогось починати. І тут хочеться сказати про величезну перевагу змагань — форум, на якому купа людей, професіоналів і не дуже працюють над однією проблемою, і навіть публікуються готові baseline'и. З обговорень я одразу зрозумів, що неправильно файнтюнів. Справа в тому, що потрібно донавчати ваги у два етапи:
- «Заморозити» ваги всіх шарів, крім останнього (того, що з 17 нейронів) і навчати лише його
- Після того, як лосс впаде до деякого значення і далі вагатиметься біля нього, «розморозити» всі ваги і навчати сітку цілком
Аугментації
Я чув, що для збагачення датасету можна робити аугментацію — деякі трансформації зображення, що подається на вхід нейромережі. Ну справді, якщо ми повернемо картинку, то нікуди річка чи дорога з неї не подінуться, але тепер для навчання у нас буде не одна картинка, а цілих дві. Я вирішив особливо не мудрувати і повертав картинки тільки на кути, кратні 90 градусам, а також віддзеркалював їх. Таким чином, розмір тренувального датасета збільшився у 8 разів, що відразу на краще позначилося на якості.

Ансамбль 1
Останнім часом існує думка, що для перемоги у змаганні потрібно просто «скочити xgboost'и». Так, без побудови ансамблів навряд чи вдасться вийти в топ, але без гарного фічінженерінгу, без грамотної валідації, це просто не дасть жодного результату. Тому перед тим, як розпочати ансамблювання, потрібно зробити чималий обсяг роботи. Які моделі можна комбінувати в задачі розпізнавання зображень? Очевидна ідея - різні архітектури згорткових нейромереж. Про їхню топологію можна почитати в цій статті.
Я використав такі:
- VGG16
- VGG19
- Resnet101
- Resnet152
- Inception_v3
Худо-бідно, я дістався кордону бронзової медальки. А тим часом до кінця змагання залишався тиждень. Саме в цей час настає так званий merge deadline момент, після якого забороняється об'єднання в команди. І ось, 20 хвилин до deadline'a, я думаю, а чому б мені, власне, не об'єднатися з кимось у команду? Дивлюся на лідерборд, сподіваючись знайти когось із чату біля себе. Але немає нікого online. Тільки asanakoy, який на той момент був на цілих 40 рядків вище за мене. А що, а раптом? Ну, я й написав. Залишається 2 хвилини до deadline'a - отримую відповідь, що asanakoy не проти об'єднатися, якщо я готовий і далі щось робити. Артем Санакоєв виявився PhD student in Computer Vision із вже наявними перемогами у змаганнях, що, звичайно, не могло мене не тішити. Об'єднання у команди —величезний плюсучасті у змаганнях на kaggle, тому що це дозволяє новачкам на кшталт мене дізнаватися щось нове від своїх досвідченіших колег безпосередньо під час спільного вирішення завдання. Пробуйте брати участь, і якщо у вас будуть хоч невеликі успіхи та велике бажання щось робити, то вас обов'язково візьмуть у команду, де навчать і все покажуть. Ну що ж, перше, що хочеться після об'єднання, — одержати миттєвий буст, об'єднавши свої рішення. Що ми і зробили, піднявшись при цьому на 30 рядків, що було цілком непогано, враховуючи, що для цього було докладено мінімум зусиль. Завдяки Артему я зрозумів, що сильно недонавчав свої мережі, і одна його модель у мотлох приділяла за якістю всі мої разом узяті. Але був ще час, щоб за допомогоюпорад свого співкомандника все виправити.
Ансамбль 2
І ось залишалося кілька днів до кінця змагання, Артем поїхав на конференцію CVPR, а я залишився сидіти з пачкою пророкувань різних сіток. Усереднення це звичайно добре, але існують більш просунуті техніки, такі як стекінг. Ідея наступна - розбити тренувальний сет, наприклад, на 5 частин, навчати модель на 4 з них, передбачати на 5-й, і зробити так для всіх 5-ти фолдів. Пояснювальна картинка:

Докладніше про стекінг, знову ж таки, на прикладі змагання, можна почитати тут.
Як це застосувати до нашого завдання: є у нас передбачення можливостей кожної мережі на кожен з 17 класів, разом 6 мереж * 17 класів = 102 признка. Це і буде нашою новою тренувальною вибіркою. На отриманій таблиці я навчав бінарний класифікатор для кожного з класів (всього 17 класифікаторів). В результаті на виході були ймовірності лейблів для кожного знімка. Далі до них можна застосувати жадібний алгоритм, що вже використовувався раніше. Але, на мій подив, стекінг давав результати гірше, ніж просте усереднення. Для себе я це пояснив тим, що у нас були різні розбиття тренувального сета на фолди — Артем використав 5 фолдів, а я тільки 4 (чим більше фолдів, тим краще, але потрібно витрачати більше часу на навчання моделей). Тоді було вирішено робити стекінг тільки на прогнозах своїх нейромереж, а потім узяти виважену суму результату з прогнозами Артема. Як моделі другого рівня використовувалися: lightgbm, xgboost і тришаровий персептрон, після чого їх виходи усереднювалися.

І ось тут стекінг реально заробив, на лідерборді ми піднялися до впевнених срібних медаль. Ідей і часу майже не залишалося, і я вирішив додати до стекінгу ще однунейромережа з останнім шаром з чотирьох нейронів і softmax активацією, яка передбачала виключно погодні умови. Якщо відоме вужче завдання, то чому б це не використовувати? Це дало покращення, але не сказати, що дуже сильне.
Тим не менш, в результаті ми опинилися на 17 місці з майже тисячі, що для першого змагання з deep learning здається дуже непогано. Золоті медальки починалися з 11 місця, і я зрозумів, що ми були реально близькі, а рішення відрізняється від топового, можливо лише деталями реалізації.