С JSoup, Flutter и AutoML Vision Edge

Недавно я выпустил свое первое приложение для Android Идентификатор птицеедов, основанный на Flutter классификатор для небольшого набора видов птицеедов, и хотел рассказать, как я это сделал. В этом посте больше внимания будет уделено тому, как я получил данные для обучения, спроектировал и запустил приложение. Если вам нужна подробная информация о том, как я построил сам классификатор, ознакомьтесь с другой моей публикацией Как создать приложение для Android с настраиваемым классификатором с использованием Flutter и AutoML Vision Edge.

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

Мозговой штурм

Раньше я работал в основном разработчиком бэкэнда Java, поэтому мой опыт в других областях, таких как интерфейс и машинное обучение, был ограничен. Я хотел узнать что-то новое, желательно в этих областях. Создание классификатора казалось хорошим способом поразить двух мух одним ударом! Однако на первый взгляд часто используемые мобильные кроссплатформенные интерфейсы не казались интуитивно понятными, вероятно, из-за моего ограниченного опыта работы с JavaScript. Затем я наткнулся на Flutter, который вместе с Dart показал себя намного лучше.

Примерно в то же время Google выпустила бета-версию своего сервиса AutoML Vision Edge. С его помощью вы можете создавать собственные модели классификаторов, используя достаточно большой набор помеченных обучающих изображений, без необходимости писать какой-либо связанный с машинным обучением код. Хотя использование этого в некоторой степени означало бы поражение цели изучения машинного обучения, плюсы упрощенного и ускоренного развития перевешивали этот недостаток в моих глазах.

Совершенно не связанный с этим примечание, я начал держать птицеедов в качестве домашних животных около года назад. Одна вещь, которая мне нравится в них, - это сумасшедшие цветовые сочетания разных видов. Использование научных названий (таких как Brachypelma hamorii) всегда казалось мне более простым, но многие поставщики используют общие названия (например, мексиканский краснокрылый птицеед), которые неоднозначны и различаются от одного места / продавца к другому. Отсюда не потребовалось много времени, чтобы установить связь - классификатор птицеедов казался очевидным выбором.

Данные обучения

На момент написания статьи Google рекомендует загружать не менее 10 изображений на этикетку для функциональной модели и не менее 100 изображений на этикетку для хорошей модели. Таким образом, загрузка и маркировка изображений вручную была невозможна. Таким образом, я создал для этой цели веб-парсер на основе Java (вероятно, наиболее сложную часть, которой нет в приложении), используя JSoup. Высокоуровневый процесс выглядел примерно так:

  1. Используя Мировой каталог пауков, сопоставьте все научно признанные виды с их научными синонимами.
  2. Выполните поиск в галерее изображений популярного форума по содержанию птицеедов Arachnoboards по каждому виду.
  3. Отфильтруйте несоответствующие теги, например, вентральные снимки и линьки, чтобы гарантировать загрузку только высококачественных дорсальных изображений.
  4. Используя простую многопоточность для увеличения скорости, загрузите изображения в структуру папок, которая представляет метки ML в форме «род-вид».
  5. После завершения загрузки сделайте две копии структуры папок, по крайней мере, с 10 и 100 изображениями для каждого вида.
  6. Для набора, содержащего не менее 100 изображений на этикетку, быстро отсканируйте все изображения вручную и удалите плохие (фильтрация плохих тегов значительно повысила качество изображения, но не уловила небольшую часть изображений, которые я считал плохими, например изображения с наложенными текст, отредактированные изображения и т. д.) - в интересах экономии времени я не делал этого для двух других наборов

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

Чтобы загружать и маркировать изображения, скребок должен знать, какие метки (т. Е. Виды) нужно искать. Всемирный каталог пауков - это обновленная общедоступная база данных таксонов пауков. Каждый вид указан вместе с историей его научных описаний, включая синонимы и ранее принятые названия, так что это отличный источник этикеток. WSC на самом деле предлагает публичный API для своих данных, но я нашел это несколько сбивающим с толку, и поэтому решил использовать JSoup и здесь. Использование TreeMap позволяет скребку сопоставлять виды с их синонимами в алфавитном порядке.

Чтобы сделать алгоритм очистки более эффективным, виды могут быть дополнительно сопоставлены с их родом, а также с их URL-адресом (сборщик должен знать, куда идти дальше). Здесь JSoup используется впервые.

Теперь, когда парсер знает местонахождение всех родов, он может перемещаться по нему, а затем переходить к URL-адресу каждого вида в этом роде и отслеживать его синонимы. Обратите внимание, что не все записи в извлеченном списке на самом деле являются синонимами. В редких случаях этот список также содержит ссылки на родственные виды, например, в случае Brachypelma hamorii и smithi (которые выглядят одинаково и часто ошибочно принимаются друг за друга). Я не охватил все крайние случаи, но скребок учитывает многократное упоминание вида или синонима посредством его сопоставления (дубликаты не добавляются).

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

{
  genus-species1: [ synonym1, synonym2, ...],
  genus-species2: [ synonym1, synonym2, ...],
  ...  
}

Затем ему нужно куда-нибудь загрузить изображения. Поскольку имена папок - это то, как AutoML Vision Edges определяет метки, каталогам необходимо присвоить соответствующие имена.

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

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

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

Разобравшись с этим, давайте посмотрим на конкретную реализацию.

Рабочие классы должны реализовать метод Run(), который вызывается ScrapeMaster и будет очищать изображения для одного вида и его синонимов. Для этого ему необходимо знать HTML-структуру страниц, которые он сканирует. В частности, он должен знать точный URL-адрес для каждого вида и синонима. Кроме того, поскольку результаты выводятся на страницы, он должен знать, как перейти на следующую страницу или остановиться, если страниц больше не осталось.

Все, что осталось сделать, это сделать саму работу, но с одной оговоркой. Скребок не должен загружать все найденные изображения. Изображения отправляются пользователем и могут иметь много недостатков (помимо простой маркировки неправильным видом): изображение может быть линьки тарантула (т.е. экзувий), а не живого тарантула, или это может быть снимок с брюшной полости ( т.е. отображать паука снизу). Я счел эти типы изображений нежелательными, поэтому эти теги были исключены. Таким образом можно было бы сделать гораздо больше оптимизаций. Кроме того, могут быть не изображения для всех видов и / или синонимов, поэтому, если они не найдены, скребок может удалить неиспользуемый каталог.

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

Вот и все. Затем я просмотрел папки как минимум со 100 изображениями и вручную удалил плохие. В результате были получены следующие наборы обучающих данных:

  • Все виды: ~ 7000 изображений на нескольких сотнях этикеток
  • Виды с минимум 10 изображениями: ~ 4000 изображений на ~ 100 этикетках
  • Виды с минимум 100 изображений: ~ 2600 изображений на 21 этикетке

Изготовление моделей

Этот шаг довольно прост. Я загрузил 3 набора данных и сказал Google обучить модель для каждого в течение нескольких часов.

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

Модель второго набора была намного лучше, чем первая, со средней точностью 60%, но все же не очень удобна. Большинство классификаций были неправильными, и все правильные имели очень низкие значения достоверности, а следующие лучшие классификации совершенно неродственных видов отставали всего на 1-2%.

Говорят, в третий раз - прелесть. Последний набор, который я сейчас использую в приложении, превратился в модель со средней точностью 82%. На этот раз он смог правильно определить четкие снимки всех моих собственных и других птицеедов. Это меня устраивало.

Создание приложения

Документация Firebase по интеграции моделей AutoML Vision Edge великолепна! У меня были некоторые незначительные проблемы с правильной подготовкой изображения для прогона через модель, но их поддержка была очень полезной, и мне не потребовалось много времени, чтобы понять, что не так. На этом этапе стала очевидна мощь граничных вычислений, поскольку моя модель могла делать классификации за ~ 0,07 секунды. Реализованный мною простой счетчик загрузки большую часть времени даже не был виден. И для работы ему не требовалось подключение к Интернету. К тому же сама модель оказалась довольно легкой (размер приложения примерно 17 МБ).

Теперь я начал работать с Flutter, и мне понравилось, как быстро я смог его освоить! Как давний поклонник Jetbrains и тот, кто ценит удобство, я был очень рад обнаружить, что выбранная мной среда IDE уже предлагает полную поддержку Flutter. Создание виджетов объектно-ориентированным способом - это легкий ветерок, который позволил мне выполнять итерацию очень быстро. Фактически, Flutter позволил мне создать пользовательский интерфейс так быстро, что я отвлекся, пытаясь создать множество дополнительных функций, которые я удалил позже, когда переоценил, сколько времени я мог бы выделить для этого хобби-проекта. В конце концов, пользовательский интерфейс получился очень простым, но сделал все необходимое (как мне кажется) удобным для пользователя способом. Пользователь может взять изображение или выбрать его из своей галереи, и оно сразу классифицируется. Мне даже удавалось иногда показывать рекламу, не нарушающую работу сайта.

Запуск

Получить приложение в Play Store оказалось не так сложно, как я ожидал. После того, как я убедил свою жену сделать значок / баннер, который вы видите вверху, мне просто нужно было загрузить несколько снимков экрана, отметить некоторые поля, загрузить пакет приложений и вуаля - появился список приложений. Несколько моих друзей с радостью помогли протестировать его и сразу обнаружили первую ошибку! Оказывается, на некоторых устройствах возникали проблемы с выбором изображений из галереи. Поскольку это редкая проблема, я еще не нашел первопричину, но мои подозрения склоняются либо к плагину выбора изображений Flutter, который рассказывает истории о редкой проблеме с потерей данных изображения, либо о старых устройствах, просто не поддерживающих какую-то часть app (в этом случае повышение минимальной поддерживаемой версии Android легко решит проблему). Но в остальном все выглядело довольно хорошо. Через несколько дней я успешно запустил свое приложение.

Что не так хорошо

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

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

  • Было неясно, может ли моя модель обрабатывать половые диморфные виды (то есть те, у которых самцы и самки имеют разную окраску / рисунок).
  • Было неясно, сможет ли моя модель справиться с возрастными различиями (некоторые виды птицеедов сильно меняют цвет при созревании)
  • Некоторым показалось, что классификации полностью отключены.

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

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

Что прошло хорошо

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

  1. Перезапустить скребок
  2. Загрузите новый набор данных и обучите новую модель
  3. Обновите название используемой модели в приложении

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

Очистив World Spider Catalog, я смог убедиться, что мое приложение всегда использует самые актуальные научные названия, при этом все еще выполняя поиск изображений под предыдущим именем. Таким образом, мне не нужно обновлять ярлыки вручную, и я не потеряю ценные данные обучения.

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

Заключительные выводы

Что касается моего следующего побочного проекта (всякий раз, когда я нахожу время начать другой), я определенно хочу больше подумать о варианте использования. Создание приложения просто ради создания чего-либо - это хорошо и все такое, но с минимальной отдачей. Подход должен быть похож на «Решение проблемы», а не на «Создать что-нибудь для изучения конкретных технологий». Но я перейду этот мост, когда доберусь до него!

Ссылки