Получить данные, обучить ViT, минимизировать проблему; и слишком избыточно

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

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

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

  1. Что это за «загадочный ящик», о котором вы говорите?
  2. Формальное описание проблемы
  3. Сбор необходимых данных
  4. Обработка качества данных (метка, поезд, вывод)
  5. Проанализируйте набор данных и найдите цель
  6. Идем на локацию

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

Весь код для этого проекта выложен в Notebooks и доступен в моей учетной записи Github. Если у вас есть какие-либо комментарии или вопросы, я хотел бы услышать их через LinkedIn.

Что это за «загадочный ящик», о котором вы говорите?

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

Загадочная коробка сразу выглядела очень интригующе (см. рисунок 1). Было ясно, что коробка была создана самим Сандером, поскольку на ней был очень отчетливый шаблон печати методом наплавки (FDM). На передней панели был обычный ЖК-экран и красная кнопка. С левой стороны был виден относительно большой висячий замок. Это один из кодовых замков, для открытия которых требовался четырехзначный код. Висячий замок предотвращает открытие сдвижной крышки в нижней части коробки. С правой стороны находится небольшой лишний отсек, в котором находится батарея на 9 вольт. Довольно разумно иметь батарею снаружи, чтобы ее можно было заменить, когда мощность становится низкой.

С точки зрения создателей, загадочная коробка имеет довольно интересный дизайн. Верхняя сторона (как видно на рисунке 2) использовалась в качестве основы во время печати, так как эта сторона намного грубее, чем другие. Это делает держатель избыточной батареи также пригодным для печати без поддержки. Тем не менее, я не совсем уверен, как создается отверстие для ЖК-экрана. Самый простой способ — напечатать легкие опоры, которые вырезаются. На выступах, которые используются для навесного замка, все еще видны остатки печатной опоры. В общем, хороший проект, чтобы ваш принтер работал не менее 8 часов.

Красная кнопка — это своего рода кнопка переключения. При нажатии слышен щелчок и включается ЖК-экран. Через несколько секунд коробка приветствует вас Привет!!! классическая шутка в Исследовательской группе по физике жидкостей. Следующие несколько сообщений, отображаемых на экране, на голландском языке. Обзор экранов см. на рис. 3. Вот несколько быстрых переводов:

  1. Привет!!! -› ‹низкий голос› Привет ‹/низкий голос›
  2. Ван Харт gefeliciteerd! -› Искренние поздравления!
  3. Vind de twee punten en ga -› найди две координаты и вперед
  4. наар хет мидден ван де пунтен… -› в центр координат…
  5. Zoekt gps… Идет момент… -› Поиск GPS… Один момент…
  6. Afstand(p1)-Afstand(p2)=1048м -> Расстояние(p1)-Расстояние(p2)=1048м

После двух экранов подсказок система занята поиском сигнала GPS. Этот процесс, так называемое «время до первого исправления» (TTFF), может быть длительным, поскольку устройству всегда приходится выполнять так называемую «холодную перезагрузку». По стандарту это может занять до пяти минут.

Наконец, после GPS-привязки коробка выходит на свой основной рабочий экран, который показывает нам свой единственный вывод: расстояние до точки 1 минус расстояние до точки 2, которое является целым числом в метрах.

Сандер также дал некоторые дополнительные разъяснения по устройству. Устройство выводит значение, представляющее собой разницу между двумя расстояниями: d1 и d2. Эти расстояния представляют собой расстояние между текущим местоположением ящика и двумя неизвестными координатами. Цель состоит в том, чтобы найти координату точно между этими двумя неизвестными координатами.

Все это звучит просто, но давайте взглянем на более формальное описание.

Формальное описание проблемы

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

Местоположение цели G находится точно в центре линии между двумя неизвестными нам координатами p1 и p2. Расстояния d1 и d2 — это расстояние между текущим местоположением блока B и координатами p1 и стр2. Блок выводит на экран разницу между d1 и d2, которую мы назовем A. На рис. 4 показана упрощенная схема задачи.

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

Существует точка, в которой разность A равна нулю. Это когда расстояние d1 равно d2. В двухмерном пространстве это приводит к линии, перпендикулярной линии между p1 и p2 (см. рис. 5). Итак, технически, если мы найдем два разных места, в которых A равно нулю, мы могли бы соединить эти точки и узнать, что цель находится на этой линии.

Чтобы решить уравнение, мы могли бы попытаться решить систему уравнений (четыре неизвестных требуют четырех уравнений), но наша задача нелинейна. Это делает некоторые неприятные уравнения при попытке изолировать члены в уравнении (уравнение 1). В уравнении x и y — это два измерения текущей координаты местоположения блока. Возможно, не самое элегантное, но уравнение можно решить численно, минимизировав функцию ошибок. Это именно то, что мы делаем в машинном обучении с градиентным спуском и более современным методом «вперед-вперед».

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

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

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

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

Но прежде чем мы сможем что-то решить, нам нужно получить данные. В следующем разделе я опишу сложную настройку.

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

Самая большая проблема при попытке подойти к физической загадке как к проблеме данных заключается в том, что данных, скорее всего, нет. Это означает, что нам нужно собирать собственные данные. Судя по моему опыту физика, сбор данных — непростая задача. Многое может (и, вероятно, так и будет) пойти не так. Честно говоря, для этого проекта я проехал на велосипеде более 80 км.

Многое может (и, вероятно, так и будет) пойти не так.

Есть несколько способов, которыми мы можем подойти к сбору данных. Во-первых, мы могли бы просто передвигаться на велосипеде 🚴 (мы в 🇳🇱, так что да, мы 🚴 😃), и остановиться, чтобы записать текущую широту и долготу, а также выход коробки A. Технически, нескольких очков было бы достаточно, но где же в этом веселье.

Вместо того, чтобы выполнять всю эту ручную работу, почему бы не сделать классическую чрезмерную инженерию и снять видео с выводом таинственного ящика и сопоставить его с данными GPS-логгера. Для этого нам нужно соорудить какую-нибудь штуковину, фиксирующую камеру перед ЖК-экраном коробки. Самый простой способ, который я мог придумать, — это привязать мой мобильный телефон к какому-то контейнеру, который имеет минимальное расстояние, необходимое для получения четкого изображения с камеры. У пластиковой коробки были нужные размеры, поэтому я мог вставить коробку в коробку и создать представление о коробке (мы должны копнуть глубже). Камера должна быть прочной, чтобы движение между камерой и ЖК-экраном было минимальным. Это требует серьезной клейкой ленты.

Теперь у нас есть решение для записи вывода коробки в виде фильма. Обычное приложение камеры моего телефона было не очень хорошим, поэтому я использовал приложение под названием HD Camera. Чтобы получить текущее местоположение, нам нужен регистратор GPS. Для этого я скачал GPS logger из Play Market. После установки этих двух приложений я готов собирать некоторые данные. Вся установка показана на рисунке 7.

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

  1. Во время моей первой поездки сдох аккумулятор. Когда вы все еще записываете, когда телефон умирает, вы теряете запись. Это заставило меня добавить в комплект внешний аккумулятор. ( 🚴 ~12 км).
  2. Во время следующей поездки мой запас был полон. Я не видел сообщения об ошибке до конца поездки. ( 🚴 ~10 км).
  3. Во время очередной поездки мне не понравилось освещение из-за солнца. ( 🚴 ~9 км).
  4. Теперь мне понравились кадры, но почему-то у меня не было данных GPS. ( 🚴 ~9 км).
  5. Опять же, нет данных GPS. но я подозреваю, что проблема с экономией заряда батареи. ( 🚴 ~9 км).
  6. Я сделал GPS-логгер передним планом и HD-камерой фоном. Теперь у меня были данные GPS, но почему-то приложение камеры остановилось. ( 🚴 ~9 км).
  7. После превращения приложения GPS-логгера в плавающее приложение (многозадачная магия) я получил и данные GPS, и данные изображения. ( 🚴 ~9 км).
  8. Я удалил свой репозиторий, включая все записи. ( 🚴 ~9 км).

В общей сложности я проехал почти 80 км, чтобы получить полезные кадры, сопровождаемые данными GPS. Далее мы исследуем эти два источника данных и объединим их таким образом, чтобы получить A = f(x,y).

Обработка качества данных

Теперь, когда у нас есть два источника данных, нам нужно преобразовать их во что-то полезное. Сначала мы преобразуем данные GPS в наш DataFrame по умолчанию. Далее мы преобразуем видео размером 4,7 ГБ также в таблицу значений A. Это будет приличное сокращение данных 😃.

Преобразование данных GPS-логгера

В то время как камера на телефоне записывала изменяющиеся значения на ЖК-экране, приложение GPS-регистратора записывало текущие значения широты и долготы. Они хранятся с частотой записи 1 Гц в так называемом файле GPX, для которого существует отличная библиотека в Python. Он удобно называется gpxpy , и после установки pip данные могут быть загружены в блаженстве:

Теперь у нас есть DataFrame с 2133 измерениями, включая широту, долготу, высоту и время. Чтобы проверить, имеют ли смысл эти координаты, мы можем просто создать параметрический график с помощью Matplotlib. Однако в Python есть еще одна замечательная библиотека специально для карт под названием folium. Давайте визуализируем то, что я проехал на велосипеде:

На прекрасной карте Folium, где мы используем плитки «таменская акварель», мы видим именно то кольцо, которое я проехал на велосипеде, чтобы собрать данные. Прежде чем мы сохраним DataFrame на диск, нам нужно исправить часовой пояс в столбце времени. В настоящее время часовой пояс установлен на «z», и я не уверен, что это такое. Чтобы устранить любые сложности, давайте локализуем время.

Данные GPS готовы к использованию. Далее нам нужно обработать видеоматериал во что-то полезное.

Создание обучающего набора для детектора изображений

У нас есть видео, которое показывает весь вывод загадочной коробки во время велопробега. Нам нужно извлечь значение A из каждого кадра и соответствующее ему время. Используя это время, мы можем связать значение A с его местоположением по GPS. Чтобы извлечь значение A, мы обучим модель на основе DONUT. Это сквозная модель, не требующая оптического распознавания символов (OCR). Для обучения модели нам нужен размеченный набор данных. Во-первых, давайте проверим видеоданные с помощью OpenCV.

Видео записывалось с частотой кадров 30 кадров в секунду. Продолжительность видео составляет около 35 минут, что означает более 63 тыс. кадров. Это многовато, особенно из-за того, что большинство датчиков GPS для Arduino имеют частоту обновления > 1 Гц. Поэтому давайте выбирать только один кадр каждую секунду и помещать их в обычный список.

Мы извлекли 2119 кадров, что уже значительно меньше исходных 63 тыс., с которых мы начали. Глядя на кадр в Output 3 (слева), мы видим, что многие пиксели в кадре нам не очень интересны. В нашей предварительной обработке мы обрежем изображение так, чтобы у нас осталось только значение A. Мы также уменьшим изображение до черно-белого, что позже может решить проблемы с контрастом.

Теперь нам нужно только сохранить список. Это относительно простая задача для распараллеливания, поскольку она не зависит от предыдущего ввода, поэтому мы используем joblib для этого шага.

Теперь у нас есть набор данных, но он еще не помечен. Для маркировки я создал инструмент для маркировки данных в Jupyter Lab (или Notebook), который называется Pigeon-XT. Это расширенная версия инструмента для маркировки Pigeon, созданного Анастасисом Германидисом. Мы разметим 250 примеров, на что у меня ушло около 15 минут.

Теперь у нас есть помеченный набор данных. Он небольшой, около 10% от полного набора данных, но мы должны быть в состоянии обучить модель, которую можно использовать для заполнения недостающей части набора. Чтобы облегчить себе жизнь, мы можем использовать библиотеку datasets от 🤗 Huggingface. Мы можем импортировать наши данные с небольшими изменениями в виде набора данных Imagefolder.

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

Теперь у нас есть все, чтобы обучить модель распознавать недостающие метки. Давайте потренируемся 🔥🔥🔥!

Обучение модели 🍩 DONUT

Наконец-то мы можем начать обучение нашей модели. Мы будем обучать модель на основе DONUT, созданную Naver AI Lab. DONUT — это настоящая сквозная модель. Вы вводите изображение и получаете объект JSON с парами ключ-значение. Самое удивительное, что это можно сделать без OCR. Предыдущие современные методы, такие как LayoutLM, используют двухэтапный подход: сначала используйте OCR для извлечения всего текста и его местоположения, а затем вводите изображение и текстовые данные в модель для извлечения информации.

Проблема с извлечением информации из документов заключается в том, что в макете закодировано много информации. Человеческий мозг понимает связь между данными, когда они представлены в виде заголовка, таблицы или подписи. При использовании методов OCR эта информация теряется. Такие методы, как LayoutLM, используют изображение для возврата информации о макете. DONUT может сделать это за один раз и имеет встроенное распознавание символов. Судя по тому, что я видел, он работает довольно хорошо, но очень чувствителен к изменениям в документах. Когда документ имеет незначительные изменения макета, но по-прежнему извлекает ту же информацию, DONUT довольно легко дает сбой.

Наши изображения на самом деле не являются документами, но они имеют очень четкий текст и с точки зрения макета очень постоянны. Таким образом, DONUT был бы отличным (и излишним) методом извлечения значения A. Я проделал ту же процедуру, что и команда CloveAI в своем репозитории. Филипп Шмид написал отличную статью о том, как тренировать собственную модель типа DONUT. Сначала нам нужно подготовить наш набор данных для конкретных входных данных DONUT модели.

Теперь, когда данные находятся в форме, ожидаемой DONUT, нам нужно токенизировать данные. Этот шаг является общим для языковых моделей преобразования, таких как GPT‹n› и BERT, но теперь также используется для изображений в Vision Transformers (ViT).

Теперь у нас все готово для настоящей тренировки 🔥. В настоящее время это невероятно просто, так как вам больше не нужно писать свой тренировочный цикл. Хотя поначалу мне было немного грустно, потому что было довольно круто писать цикл, это было довольно повторяющимся. Huggingface дает вам предмет тренера, и он просто идеально подходит для работы.

После оценки тестового набора данных мы получаем потерю оценки 0,007, что довольно неплохо. Из наших 38 тестовых примеров 37 были обнаружены правильно. В том, что неверно, отсутствовала одна цифра: 059 вместо 1059. 37/38 дает точность 97%, что, вероятно, достаточно для маркировки полного набора. Прежде чем мы начнем выводить недостающие значения, давайте сохраним модель, а также отправим ее в хаб.

Теперь давайте сделаем вывод об отсутствующих метках.

Используйте модель, чтобы вывести недостающие данные

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

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

Параллельные работы не тривиальны. Читайте мою статью🏃🏻💨!

Теперь, когда у нас есть значения A из коробки, мы можем создать наш окончательный набор данных и объединить его с данными GPS. Как всегда, прогноз не был идеальным, и мы должны сделать небольшой массаж. Как ученый данных вы на самом деле массажистка данных.

Почти готово, единственная проблема — неправильно обнаруженные значения A. Их можно отфильтровать с помощью вращающегося фильтра. Мы удалим значения, которые имеют большую разницу, чем 5 метров (произвольно небольшое число) из скользящего среднего, и интерполируем их с оставшимися значениями.

Эта фильтрация выглядит довольно аккуратно. Теперь мы готовы объединить этот набор данных с нашими ранее записанными данными GPS.

Объединение изображения и данных GPS

Объединение значения A с данными GPS не должно вызывать особых проблем. Для каждой строки в наборе данных GPS мы будем искать ближайшее совпадение по времени и использовать значение A этой строки.

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

мы использовали машинное обучение, и это единственное, что имеет значение!

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

Проанализируйте набор данных и найдите цель

Как обсуждалось ранее, мы попытаемся найти два неизвестных местоположения p1 и p2. Если мы знаем эти две точки, мы также будем знать расположение ворот, которое должно быть точно между этими точками. Для поиска локаций воспользуемся формой minimize Scipy. Хотя я мог бы написать свою собственную функцию Haversine, я просто буду использовать пакет haversine. Чтобы использовать minimize, нам нужно написать функцию ошибки. Эта функция рассчитает разницу между известным A и рассчитанным A для всех наших значений в наборе данных и суммирует абсолютные различия. Функция минимизации должна минимизировать эту ошибку.

Вот оно, я нашел место. Цель находится точно между точками p1 и p2. Эта точка также соединяется с перпендикулярной линией между моими ранее найденными нулевыми точками. Но самый большой намек на то, что целевая локация — это барбекю-ресторан. Сандер и я очень любим гамбургеры, и в этом месте их большой выбор.

Отклонение от фактической точки и той, которую я нашел, составляет 93 метра. У этого может быть много причин. Во-первых, извлеченное значение A имеет только круглые числа. Хотя это имеет эффект, я подозреваю, что это лишь незначительный эффект, которым можно пренебречь при усреднении по большому набору данных. Конечно, фильтрация (сглаживание) сказывается. Может быть, какой-то шум добавлен специально, не уверен, мог ли я это заметить. Во всяком случае, я очень доволен результатом.

Идем на локацию

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

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

Честно говоря, это было непростым испытанием. Но это было веселое путешествие, и в конце мы собираемся съесть несколько гамбургеров. Вряд ли станет лучше.

Весь код этого проекта находится на Github. Большинство небольших наборов данных включены в репозиторий Git, однако более крупные видео доступны через мой Dropbox. Набор данных, используемый для обучения ViT, процессор DONUT и модель DONUT, также размещены в 🤗 Huggingface hub. Если у вас есть какие-либо вопросы, не стесняйтесь обращаться ко мне в LinkedIn.

И последнее, но не менее важное: я хочу поблагодарить 🙏 Сандера за интересный опыт. Извините, что так долго, но я думаю, что я немного переусердствовал. Это точно было весело! Бургеры?