Решение ESRI Data Science Challenge 2019, занявшее 3-е место

Вступление

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

Чтобы решить эту проблему, мы попытаемся обнаружить автомобили и бассейны с помощью RGB-чипов размером 224x224 пикселей аэрофотоснимков. В наборе обучающих данных было 3748 изображений с аннотациями ограничивающих рамок и метками в формате PASCAL VOC.

Эта проблема вместе с набором данных была опубликована ESRI на HackerEarth как ESRI Data Science Challenge 2019. Я участвовал и занял 3-е место в общедоступной таблице лидеров с mAP (средняя средняя точность) 77,99 atIoU = 0.3 с использованием современной модели RetinaNet. В следующем посте я объясню, как я пытался решить эту проблему.

RetinaNet

RetinaNet был сформирован путем внесения двух улучшений по сравнению с существующими одноступенчатыми моделями обнаружения объектов (такими как YOLO и SSD):

  1. Сети функциональных пирамид для обнаружения объектов
  2. Потеря фокуса при обнаружении плотных объектов

Сеть пирамид функций

Сети пирамид традиционно использовались для идентификации объектов разного масштаба. Сеть пирамид признаков (FPN) использует присущую многомасштабной пирамидальной иерархии глубоких CNN для создания пирамид признаков.

Одноэтапная сетевая архитектура RetinaNet использует магистраль Feature Pyramid Network (FPN) поверх архитектуры ResNet с прямой связью (a) для создания богатой, многомасштабной сверточной пирамиды функций (b). К этой магистрали RetinaNet присоединяет две подсети: одну для классификации якорных ящиков (c) и одну для регрессии от якорных ящиков к ящикам наземных объектов (d). Конструкция сети намеренно проста, что позволяет сосредоточить внимание на новой функции фокальных потерь, которая устраняет разрыв в точности между нашим одноступенчатым детектором и современными двухступенчатыми детекторами, такими как Faster R-CNN с FPN, в то время как работает на более высоких скоростях.

Потеря фокуса

Focal Loss - это улучшение кросс-энтропийной потери, которое помогает уменьшить относительную потерю для хорошо классифицированных примеров и уделять больше внимания жестким, неверно классифицированным примерам.

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

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

Теперь давайте начнем с фактической реализации и приступим к написанию кода. Вот репозиторий Github, по которому вы можете следить:



Установка Retinanet

Мы воспользуемся замечательной Keras-реализацией RetinaNet от Fizyr. Я предполагаю, что у вас есть машина для глубокого обучения. Если нет, следуйте моему руководству здесь. Также я бы рекомендовал использовать виртуальную среду. Следующий сценарий установит RetinaNet и другие необходимые пакеты.

В качестве альтернативы вы можете использовать экземпляр графического процессора (p2.xlarge) на AWS с AMI «глубокое обучение для компьютерного зрения с помощью Python». Этот AMI поставляется с предустановленным keras-retinanet и другими необходимыми пакетами. Вы можете начать использовать модель после активации виртуальной среды RetinaNet командой workon retinanet.

Примечание. Retinanet требует больших вычислений. Для пакета из 4 изображений (224x224) потребуется не менее 7–8 ГБ памяти графического процессора.

После установки RetinaNet создайте следующую структуру каталогов для этого проекта.

Я подробно объясню каждый из них, но вот обзор:
build_dataset.py - сценарий Python для создания набора поездов / тестов
config/esri_retinanet_config.py - файл конфигурации, который будет использоваться сценарием сборки.
dataset/annotations - каталог для хранения всех аннотаций к изображениям
dataset/images - каталог для хранения всех изображений
dataset/submission_test_data_images - каталог для тестирования отправки для задания Esri Data Science. Вы можете проигнорировать это, если работаете над своим собственным набором данных и другим проектом.
snapshots - каталог, в котором все снимки тренировок будут сохраняться после каждой эпохи
models - каталог, в котором снимки, преобразованные для оценка и тестирование. будет сохранен
tensorboard - каталог, в котором будет сохранен журнал тренировок, который будет использоваться тензорной платой
predict.py - скрипт для создания прогнозов по файлам теста отправки

Создание набора данных

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

В этом конфигурационном файле TRAIN_TEST_SPLIT = 0.75. Стандартной практикой является разделение 75–25, 70–30 или в некоторых случаях даже 80–20 между набором данных для обучения и тестирования из исходного набора данных. Но для целей этого конкурса я не делал тестовый набор данных, а использовал полный набор данных для обучения. Это было сделано потому, что был предоставлен только небольшой набор данных из 3748 изображений. Кроме того, был предоставлен набор тестовых данных из 2703 изображений (без аннотаций), на котором можно было протестировать модель, отправив прогнозы в режиме онлайн.

Затем давайте напишем скрипт Python, который будет читать все пути к изображениям и аннотации и выводить три CSV, которые требуются во время обучения и оценки модели:

  1. train.csv - этот файл будет содержать все аннотации для обучения в следующем формате: <path/to/image>,<xmin>,<ymin>,<xmax>,<ymax>,<label>
    Каждая строка будет представлять одну ограничивающую рамку, поэтому одно изображение может присутствовать в нескольких строках в зависимости от того, сколько объектов было аннотировано в это изображение.
  2. test.csv - аналогично train.csv по формату, этот файл будет содержать все аннотации для тестирования модели.
  3. classes.csv - файл со всеми уникальными метками классов в наборе данных с присвоениями индексов (начиная с 0 и игнорируя фон)

Начнем с создания build_dataset.py файла и импорта необходимых пакетов. Обратите внимание, мы импортируем файл esri_retinanet_config.py, который мы создали ранее в каталог config, и даем ему псевдоним config.

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

В предыдущем коде мы считываем пути к изображениям в список, рандомизируем список, разбиваем его на обучающий и тестовый набор и сохраняем их в другом списке dataset в формате (<dataset_type>, <list_of_paths>, <outpuCSV>). Мы также инициализируем набор CLASS для хранения всех уникальных меток классов в наборе данных.

Затем мы перебираем каждый набор данных (обучающий и тестовый) и открываем выходной файл CSV для записи. Для каждого набора данных мы перебираем каждый путь к изображению. Для каждого изображения извлеките имя файла и создайте соответствующий путь к аннотации. Это работает, потому что обычно файлы изображений и аннотаций имеют одно и то же имя, но разные расширения. Например, dataset/images/0000001.jpg имеет аннотации в dataset/annotations/0000001.xml. Измените этот раздел, если ваш набор данных соответствует другому соглашению об именах. Используя BeautifulSoup, проанализируйте файл аннотаций (XML). Затем мы можем найти «ширину», «высоту» и «объект (ы)» из проанализированного XML.

Для каждого изображения найдите все объекты и переберите каждый из них. Затем найдите ограничивающую рамку (xmin, ymin, xmax, ymax) и метку (имя) класса для каждого объекта в аннотации. Выполните очистку, обрезав любую координату ограничивающего прямоугольника, выходящую за границы изображения. Также проверьте работоспособность, если по ошибке какое-либо минимальное значение больше максимального, и наоборот. Если мы найдем такие значения, мы проигнорируем эти объекты и перейдем к следующему.

Теперь, когда у нас есть вся информация, мы можем приступить к записи в выходной CSV, по одной строке за раз. Кроме того, продолжайте добавлять метки к CLASSES набору. В конечном итоге это приведет к появлению всех уникальных меток классов.

Последнее, что осталось создать для набора данных в требуемом формате, - это записать метки классов с соответствующими индексами в CSV. В наборе данных ESRI есть только два класса - автомобили (метка: «1», индекс: 1) и плавательный бассейн (метка: «2», индекс: 0). Вот как classes.csv ищет набор данных Esri.

2,0
1,1

Обучение и оценка модели

Теперь, когда набор данных готов и RetinaNet установлен, давайте приступим к обучению модели на наборе данных.

# For a list of all arguments
$ retinanet-train --help

Для обучения модели я использовал следующую команду:

$ retinanet-train --weights resnet50_coco_best_v2.1.0.h5 \
--batch-size 4 --steps 4001 --epochs 20 \
--snapshot-path snapshots --tensorboard-dir tensorboard \
csv dataset/train.csv dataset/classes.csv

Рекомендуется загружать предварительно обученную модель или файл весов вместо обучения с нуля, чтобы ускорить обучение (потери начнут сходиться раньше). Я использовал веса из предварительно обученной модели с магистралью ResNet50 в наборе данных COCO. Используйте следующую ссылку, чтобы загрузить файл.

https://github.com/fizyr/keras-retinanet/releases/download/0.5.0/resnet50_coco_best_v2.1.0.h5

batch-size и steps будут зависеть от конфигурации вашей системы (в первую очередь графического процессора) и набора данных. Обычно я начинаю с batch-size = 8, а затем увеличиваю или уменьшаю в 2 раза, в зависимости от того, успешно началось обучение модели или нет. Если обучение начинается успешно, я прекращаю обучение (CTRL + C) и начинаю с более высокого размера пакета, в противном случае - с меньшего.

После того, как вы определились с размером пакета, вам нужно будет рассчитать шаги, которые вам понадобятся, чтобы охватить общий набор данных в каждую эпоху. Следующая команда даст вам количество строк в train.csv, созданных ранее в каталоге dataset.

$ wc -l datatset/train.csv

Расчет размера шага прост: steps = count of rows in train.csv / batch-size. Затем установите количество epochs. По моему опыту, RetinaNet быстро сходится, поэтому меньшее количество эпох обычно работает. Если нет, то всегда можно взять курс обучения прошлой эпохи и тренировать свою модель дальше. Поэтому мы предоставим snapshot-path, где модель будет сохраняться после каждой эпохи.

Мы также предоставим tensorflow-dir, где будут сохранены все журналы, и можно будет запустить тензорную таблицу для визуализации процесса обучения. Чтобы запустить tenorboard, откройте новое окно терминала и выполните указанную ниже команду. Перед запуском убедитесь, что у вас установлен тензорборд.

# To launch tensorboard
$ tensorboard --logdir <path/to/logs/dir>

Наконец, предоставьте csv файлам набор обучающих данных и метки классов. И выполните тренировочную команду. А теперь иди, сделай Железного Человека или поспи, или еще что-нибудь, пока твоя модель тренируется. Каждая эпоха с 3748 (224x224) изображениями занимала чуть более 2 часов на графическом процессоре K80 Tesla на экземпляре AWS p2.xlarge.

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

# To convert the model
$ retinanet-convert-model <path/to/desired/snapshot.h5> <path/to/output/model.h5>
# To evaluate the model
$ retinanet-evaluate <path/to/output/model.h5> csv <path/to/train.csv> <path/to/classes.csv>
# Sample evaluation
95 instances of class 2 with average precision: 0.8874
494 instances of class 1 with average precision: 0.7200
mAP: 0.8037

В этой выборочной оценке 125 тестовых изображений модель смогла достичь 80,37% mAP (средняя средняя точность) при обучении 375 изображений за 18 эпох. Это хороший результат для такого небольшого набора данных.

Прогнозы

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

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

Создайте синтаксический анализатор аргументов для приема аргументов при выполнении сценария, а затем проанализируйте аргументы. Аргумент model примет путь к файлу обученной модели, который будет использоваться для прогнозирования. Для меток классов и выходного каталога прогнозов значения по умолчанию были взяты из файла конфигурации. Следовательно, это не обязательные аргументы. Аргумент input будет принимать путь к каталогу, содержащему изображения, для которых будут сделаны прогнозы. Также доступен аргумент confidence для фильтрации слабых прогнозов.

Затем загрузите отображение метки класса из CSV метки класса и превратите его в словарь. Загрузите модель, которая будет использоваться для прогнозирования. Используйте путь к каталогу, указанный в аргументе input, чтобы получить и составить список всех путей к изображениям.

Выполните итерации по каждому пути изображения, чтобы мы могли делать прогнозы для каждого изображения в предоставленном наборе данных. Строки 6–9 в приведенном выше коде извлекают имя файла изображения из пути к изображению, а затем создают и открывают путь к выходному текстовому файлу, в котором будут сохранены прогнозы для этого изображения. В строках 11–15 мы загружаем изображение, предварительно обрабатываем его, изменяем его размер и затем расширяем его размеры перед передачей в модель. В строке 18 мы передаем предварительно обработанное изображение в модель, и она возвращает предсказанные блоки (координаты ограничивающего прямоугольника), оценки вероятности для каждого поля и связанные метки. В последней строке в блоке выше измените масштаб координат ограничивающего прямоугольника в соответствии с размером исходного изображения.

Затем переберите каждое обнаружение, предсказанное моделью. Пропустите те, чей балл меньше предоставленного значения достоверности. Хотя, если вы хотите рассчитать MAP (средняя средняя точность), сохраните все прогнозы. Для этого передайте значение аргумента confidence как 0.0. Координаты ограничивающего прямоугольника будут иметь float значения, поэтому преобразуйте их в int. Постройте строку для каждого прогноза в требуемом формате: <classname> <confidence> <ymin> <xmin> <ymax> <xmax> и запишите ее в файл. Закройте файл, как только все обнаружения для этого изображения будут записаны в соответствующий файл.

$ python predict.py --model models/output.h5 --input dataset/submission_test_data_images --confidence 0.0

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

Эксперименты и результаты

Изначально я обучил модель, используя только 10% данных (375 изображений) за 18 эпох. Эта модель имела MAP 71 при значении достоверности 0,5 на тестовых изображениях. Я возобновил обучение модели на полном наборе данных из 3748 изображений для еще 10 эпох, чтобы получить увеличенное значение MAP до 74. Я решил немного спроектировать модель и внести изменения в якорные блоки. В наборе данных были только квадратные ограничивающие прямоугольники, и я изменил соотношение сторон прямоугольников с [0.5, 1, 2] на [1]. Это казалось хорошим экспериментом, но я понял, что это не так, поскольку соотношения якорных ящиков будут меняться по мере увеличения изображений. Это привело к тому, что обучение сети произошло намного быстрее, чем раньше, с полным набором данных, поскольку размер сети уменьшился. Точность прогнозов тоже немного повысилась, но потом стала падать. Я решил использовать результаты второй эпохи со значением достоверности 0,0, чтобы включить все прогнозы. В результате я получил карту 77.99, которая обеспечила мне 3-е место в испытании. Я также, безуспешно, попробовал несколько других экспериментов с масштабами изображений, которые будут использоваться для FPN и параметров увеличения данных, но остановился на более ранних результатах для окончательной отправки.

Резюме

В этом посте мы рассказали о современной модели RetinaNet и о том, как я использовал ее для Esri Data Science Challenge 2019 для обнаружения автомобилей и бассейнов на аэрофотоснимках 224x224. Мы начали со структурирования каталога проекта. Затем мы создали набор данных для поезда / теста, который будет использоваться моделью. Модель была обучена с соответствующими аргументами, а затем обученная модель была преобразована для оценки и прогнозирования. Мы создали еще один сценарий для обнаружения тестовых изображений при отправке и записи прогнозов на диск. В конце я кратко опишу эксперименты, которые я пробовал, и результаты, которых добился.

использованная литература







Спасибо, что прочитали этот пост. Надеюсь, это вам поможет. Не стесняйтесь оставлять сообщения с комментариями / предложениями. Вы также можете связаться со мной в LinkedIn. Вот репозиторий GitHub с кодом: