Думаю, осталось не так много людей, которые не слышали о хакатонах и соревнованиях по Data Science. О них полгода назад услышал и я. Участвуя во всём, что видел (и что-то даже выигрывая), я не смог пройти мимо AgroCode 2020, организованного Россельхозбанком. Мне удалось попасть в топ лучших участников по нескольким направлением, а в одном - и вовсе взять призовое место. Благодаря этим достижениям я стал Data Science специалистом в Центре Развития Финансовых Технологий Россельхозбанка. А как у меня это вышло – читайте ниже.

Главный агрокодинг страны

Для начала, скажу пару слов о самом мероприятии. AgroCode 2020 объединил много неравнодушных к новым технологиям в сельском хозяйстве людей. Он состоял из нескольких активностей:

  1. Соревнование по анализу данных Agro Data Science Cup с 2 задачами:

    • определение сельскохозяйственной культуры по ежедневным значениям нормализованного индекса вегетации

    • определение недостатков плодов лимона по фотографии.

  2. Хакатон Agro Hack с 6 кейсами из агроиндустрии:

    • создание рекомендательного сервиса по размещению культур на сельскохозяйственных полях

    • выбор оптимального поставщика удобрений

    • определение заболоченных и переувлажненных участков сельскохозяйственных угодий

    • создание нейросети для обнаружения заболеваний листьев яблони по фотографии

    • разработка полетного задания для группировки сельскохозяйственных дронов

    • разработка приложения для повышения урожайности космической клубники.

      Для участия в хакатоне командам, подавшим заявку, нужно было пройти отбор. В итоге за призовые места боролись по 10 команд для каждого кейса.

  3. Конкурс идей Agro Idea, которые должны помочь решить проблемы фермеров и агроиндустрии.

Выбирая из этих вариантов, я решил поучаствовать в соревновании по анализу данных и хакатоне, на котором моей команде удалось пройти отбор. Участие в хакатоне не принесло нам призов, хотя, как мне кажется, решение получилось интересным. О нем вкратце расскажу ближе к концу статьи. DS- соревнование прошло куда более результативно. В задаче с лимонами удалось попасть в топ-10, а в задаче с индексом вегетации - занять 2 место!

Задача про индекс вегетации

В данной задаче требовалось определить сельскохозяйственную культуру по ежедневным значениям нормализованного индекса вегетации. Данные для неё были собраны по 17 регионам РФ преимущественно Европейской части России.

Что за данные?

Данные выглядели крайне просто и понятно: год, ID поля, площадь поля, наименование культуры (закодированное в численное представление) и данные индекса вегетации за каждый из 365 дней.

Датасет для задачи про индекс вегетации
Датасет для задачи про индекс вегетации

Метрика

Для оценивания качества прогноза организаторами конкурса была выбрана взвешенная F1-мера из библиотеки sklearn (с параметром average="weighted").

Метрика важна для понимания того, как получить наилучший скор. Для этой задачи я сделал вывод: нужно как можно меньше допускать ошибки в наиболее распространенных классах, так как они сильно влияют на метрику. Это должно быть учтено на этапе создания модели.

Индекс вегетации? Это что?

Индекс вегетации или, как выражаются умные люди, NDVI — это довольно простое отношение

NDVI = \frac{NIR-RED}{NIR+RED}

Для его подсчета нам нужны спутниковые снимки, содержащие 4 канала: классические RGB и канал инфракрасного спектра. Соответственно, в формуле RED — это канал красного цвета, а NIR — инфракрасного спектра.

Изображение поля с подсчитанным ndvi
Изображение поля с подсчитанным ndvi

Какие особенности я увидел в этих данных?

  • Во-первых, наблюдается волатильность в начале года примерно до 45 дня и пропущенные значения индекса, начиная с 279 дня. Это объясняется довольно просто: в начале года большая часть полей покрыта снегом и из-за этого индекс принимает аномальные значения, в конце года (предположительно) данные собираются не так активно, так как период сбора урожая закончен.

  • Во-вторых, в течение всего года индекс может совершать неоправданные колебания, которые происходят из-за высокой облачности (хорошая идея для аналитиков в этой отрасли - учитывать облачность при подсчете индекса). С этой проблемой я боролся путем сглаживания временного ряда и эвристических констант, однако эти способы только ухудшали модель.

  • В-третьих, классы крайне несбалансированны. Очевидно, ведь пшеницы высаживается гораздо больше, чем овса. Эту проблему я решал уже на стадии создания и обучения модели.

Создание новых фичей

Вполне логичным решением для получения лучших значений метрики является создание фичей. Тут я вспомнил про одну из своих любимых, которая часто помогает в решении задач, связанных с физическими или химическими процессами. Это скорость - одна из важнейших характеристик процесса, которую легко получить, вычитая из значения текущего дня значение предыдущего. Для значений индекса по дням и скоростей изменения индекса я добавил среднее, сумму и значения по квантилям.

Но и это не всё… Так как мы говорим об индексе растительности, можно придумать также и фичи, непосредственно связанные с растениями. Так появляются количество дней и сумма значений индекса, в которые индекс превышал каждый из квантилей. Аналогично и со скоростями.

Также я пробовал убирать или оставлять ID, площадь поля и год. По итогу эффективным оказался только признак площади. И последнее - брал значения индекса и скоростей с определенным периодом. Параметры периода я тестировал через цикл и смотрел метрику на валидации. Лучшими оказались два варианта: брать ежедневные значения индекса и значения скоростей с периодом 2 и 4 дня.

А что с дисбалансом?

Распределение культур в тренировочных данных
Распределение культур в тренировочных данных

В данных есть классы, которые отличаются количеством сэмплов на порядки. Из-за этого обычная KFold или StratifiedKFold не подходит, так как некоторые образцы будут всё равно теряться. Таким образом я написал достаточно простую функцию сэмплирования. Случайным образом выдаётся набор данных для каждого таргета, и эти данные конкатенируются. В валидацию же уходят данные, которые не попали в тренировочный набор. Таким образом я обеспечил присутствие всех культур на каждой итерации кросс-валидации. А дальше дело остается за настройкой и обучением модели.

Модель

Как и подобает поступать на таких соревнованиях, я использовал градиентный бустинг, взяв классификатор из своей любимой библиотеки CatBoost. И, естественно, подобрал оптимальные параметры обычным перебором, остановившись на следующим варианте:

params = {
  'iterations': 2000, 
  'depth': 6, 
  'early_stopping_rounds': 500, 
  'l2_leaf_reg': 5, 
  'bagging_temperature': 1, 
  'random_seed': 17, 
  'class_names': classes, 
  'auto_class_weights': 'Balanced', 
  'eval_metric': 'TotalF1', 
  'loss_function': 'MultiClassOneVsAll', 
  'task_type': 'GPU', 
  'devices': '0:1', 
  'verbose': 2000 
}

Важными настройками для модели оказались веса для классов с параметром “Balanced” и функция потерь “MultiClassOneVsAll”. Данные настройки оказывают дополнительную поддержку в борьбе с дисбалансом данных. Остается обучить и получить предсказания. Сначала запускается цикл, в котором я меняю параметры датасета, потом цикл, в котором меняется random_seed для получения различных сэмплов датасета. Далее - обучение и получение предикта. Предсказания брались только от тех моделей, которые показывали высокий скор на валидации, затем они складывались и нормировались. Если посмотреть распределение культур для предсказаний, то можно увидеть, что в предсказаниях присутствуют практически все культуры.

Предсказанное распределение культур в тестовых данных
Предсказанное распределение культур в тестовых данных

Результат

Итогом работы алгоритма стало 18 место на публичном лидерборде. Было довольно грустно в течение нескольких дней наблюдать эту картину, зная, что на валидации получается адекватный скор. Но когда появились результаты тестирования на приватной части, я резко переместился с 18 на 2 место. Отрыв от первого места крайне незначительный, а вот от третьего - солидный. Именно поэтому валидация — это чуть ли не самое важное в таких соревнованиях.

Итоговый лидерборд первой задачи
Итоговый лидерборд первой задачи

Задача про лимоны

Вторая задача была куда более сложной: организаторы решили не отдавать призы просто так и сделали для задачи некоторые технические ограничения. К тому же мне немного не хватало опыта работы с изображениями. Однако, собрав все возможные статьи с Хабра, Кэггла и Медиума, я решил попробовать решить и эту задачу.

Главной тематикой были вот такие (как на картинке) лимоны. Целью же было выявить вероятности того, что тот или иной дефект присутствует на изображении.

А какой лимон выберешь ты?
А какой лимон выберешь ты?

Немного о данных

Данные представляют собой набор изображений и аннотаций к ним. Изображения — это цветные картинки лимонов размера 1056 х 1056 пикселов в формате .jpg. Лимоны на картинках изображены с разных ракурсов и в разных условиях освещения. Аннотации содержат наименования категорий, категории недостатков для каждого изображения, полигоны областей с недостатками и прочее. Аннотации есть только для тренировочной выборки. А вот ссылка на датасет: https://www.kaggle.com/maciejadamiak/lemons-quality-controldataset

Метрика

В этой задаче метрикой является стандартный ROC-AUC. Для подсчета используется следующий код:

def score(y_true, y_preds):
  table = y_true.merge(y_preds, left_on='image_id', right_on='image_id')
  m = keras.metrics.AUC(curve='ROC')
  m.update_state(table.iloc[:, 1:10], table.iloc[:, 10:])
  return m.result().numpy() 

Сложности

Организаторы довольно сильно усложнили задачу в два действия.

Это было не соревнование csv-файлов. Нужно было засабмитить только .py файл с кодом, который выполняется в докер-контейнере. При этом должно было выполниться не только предсказание ответов, но и обучение модели. Да ещё и интернета в контейнере нет, что порождает запрет на использование предобученных весов для моделей.

Но на этом организаторы не остановились и установили лимит на время исполнения отправленного кода всего в 20 минут. А теперь вспомните, что нужно обучать модель в контейнере. Стало страшно? Мне стало.

Исходя из ограничений стало понятно, что тяжелую модель, обученную в течение нескольких часов использовать не получится. К тому же, после длительного решения первой задачи соревнования времени на эту осталось крайне мало. Решив не изобретать велосипед, я воспользовался бейзлайном, который предоставили организаторы, переписал предобработку, модель и процесс обучения и предсказания.

Предобработка данных

Самым классическим решением для предобработки изображений является аугментация. Я использовал изменение яркости, различные повороты и сдвиги.

aug = ImageDataGenerator(
  rotation_range=40,
  width_shift_range=0.1,
  height_shift_range=0.1,
  brightness_range=[0.5, 1],
  shear_range=0.2,
  channel_shift_range=0.2,
  zoom_range=0.2,
  horizontal_flip=True,
  vertical_flip=True,
  fill_mode="nearest"
) 

Что за нейронка?

Я собрал довольно классическую модель, которая всегда дает неплохой результат. В качестве backbone взят VGG16, далее AveragePooling, полносвязный слой (Dense) и Dropout.

model = VGG16(weights=None, include_top=False, input_shape=[image_size, image_size, 3]) 
x = AveragePooling2D(pool_size=(2, 2))(model.output) 
x = Flatten()(x) 
x = Dense(256, activation='relu')(x) 
x = Dropout(0.5)(x) 
output = Dense(9, activation='sigmoid')(x)

Безусловно, модель можно было улучшать и улучшать, меняя и добавляя слои или пробуя другие бэкбоуны.

Для обучения я использовал KFold кросс-валидацию. Для меня это спорное решение, так как требуется больше времени на обучение модели, но проверить больше вариантов я просто не успел.

Результат

Несмотря на относительно простое решение и недостаток времени на его улучшение мне удалось завершить соревнование на 9 месте, что я считаю еще одним небольшим достижением.

Итоговый лидерборд для второй задачи
Итоговый лидерборд для второй задачи

А теперь про хакатон

Сразу после окончания соревнования по анализу данных начался хакатон. Наша команда называется “Московские Зайцы” и нас часто можно встретить на других подобных мероприятиях. Задачу выбирали исходя из того, что может получиться и для чего уже есть идеи. Так мы пришли к задаче оптимизации выращивания марсианской клубники.

Картинка из нашей презентации
Картинка из нашей презентации

О задаче

Для задачи организаторы поставили следующие цели:

  1. Нужно создать прототип приложения, которое помогло бы эффективнее выращивать марсианскую клубнику, используя собираемые данные.

  2. Приложение должно помогать реагировать на аномалии в данных – неблагоприятные условия роста и неисправности датчиков.

  3. На основании предоставленных данных необходимо найти оптимальные режимы выращивания марсианской клубники (режимы полива, температуры, влажности, освещения и т.п.).

Марсианский комплекс по выращиванию клубники состоит из 5 теплиц, в каждой из которых размещены датчики температуры, освещенности, влажности и кислотности почвы.

Нам были предоставлены данные за 3 года по каждой из теплиц. Для каждого года дана урожайность в условных единицах. Датасет с показаниями датчиков Датасет с показателями урожайности

Датасет с показаниями датчиков
Датасет с показаниями датчиков
Датасет со значениями урожайности
Датасет со значениями урожайности

Наше решение

Мы сразу разделили задачу на две части: анализ данных и создание веб-приложения.

Для решения первой задачи я проанализировал все показатели теплицы, чтобы выявить оптимальные, и сравнил их с тем, что советуют садоводы в интернете.

Если вы планируете выращивать клубнику, то можете принять к сведению мои выводы:

  • Наибольший урожай достигался, если температура в большинстве случаев находилась в пределах от 19 до 30 градусов, а средняя температура колебалась от 23 до 25 градусов. Эти данные соответствуют общепринятым температурам для выращивания клубники.

  • Наибольшая урожайность также достигалась при уменьшенной увлажненности почвы. Это опять же соответствует практикам выращивания клубники, когда для получения более качественного урожая при созревании уменьшается полив.

  • Клубника - светолюбивое растение. Неудивительно, что высокая урожайность достигалась вкупе с большим количество света.

  • Значение средней кислотности почвы во всех случаях составляло около 7.

Потом я сделал предиктивную модель, обученную на наиболее благоприятных условиях для выращивания клубники. Данная модель подгоняла текущий курс выращивания под оптимальный. О результатах работы алгоритма судить сложно, так как было предоставлено мало данных и нам не дали времени на реальный эксперимент. Данная модель выдавала вот такую картинку:

Температурный режим в теплице
Температурный режим в теплице

На графике: красный - режим одного случая из датасета, зеленый - предсказанный для этого случая уровень, желтый - рекомендованный уровень (сглаженный предсказанный уровень).

Что же мы придумали в качестве веб-приложения? Навык для голосового помощника. Логика заключалась в том, что на Марсе достаточно тяжелые условия, которые требуют специальной экипировки и постоянной занятости. Следовательно, нужно упростить интерфейс, а самый простой для взаимодействия интерфейс — это голосовой помощник. Можно посмотреть принцип его работы в этом видео:

Отчего же не победа?

Поверьте, я не раз задавался этим вопросом после объявления результатов, но предполагаю, что судьи выбирали наиболее применимый проект. Однако, нет повода отчаиваться, это явно не последний Agro Hack :) (по секрету скажу, что велик шанс ежегодного агрокодинга).

Стажировка

А теперь угадайте, что я сделал после оглашения результатов? Правильный ответ - успел написал организаторам с вопросом о стажировке быстрее, чем они написали об этом мне! Отбором мне послужили тестовое задание и собеседование, которые я успешно прошел.

Сейчас я уже познакомился с коллегами и исследую применимость моделей для маркетинговых задач и даже получил первую зарплату!

Так что, если вы думаете, куда податься во время или после учёбы или вовсе сменить работу, то знайте, что в Центре Развития Финансовых Технологий, который кстати курирует лично глава Россельхозбанка Борис Листов, вы найдете отличных коллег и займетесь развитием интересных и новых для России продуктов.