Русский
Русский
English
Статистика
Реклама

Cnn

Миллион домашних фотографий лица, лица, лица

21.01.2021 02:23:02 | Автор: admin

Итак, все фотографии разложены по папкам и находить фотографии Новых годов или дней рождения стало быстро и удобно. Фотографии из отпусков тоже можно найти относительно быстро, но хотелось большего. А именно, искать по людям и не просто по людям, а по набору людей, например, найти все совместные фотографии детей или фотографии с бабушкой и т.д.

Поэтому я решил немного углубиться в так называемый Face Recognition.

Все просто?

С первого взгляда, когда смотришь на тематические статьи, кажется, что со стороны пользователя все просто: взял по одной фотографии человека, сказал системе, что это, мол, Саша, Алиса, Тима; потом на вход дается набор фотографий и система однозначно (ну ок, не однозначно, а с некоторой большой вероятностью) устанавливает кто есть кто. Но на практике так работает, если надо отличить Байдена от Обамы на протокольных фотографиях из Белого дома. Не совсем так работает, когда нужно различать лица на домашних фотках (плохой ракурс, освещение, перекрытие лиц предметами и т.д.). Совсем не так работает, когда на фотографиях близкие родственники. И почти никак не работает если фотографии покрывают всю жизнь от младенчества до зрелости (я и сам часто не могу отличить фотографию одного своего ребенка от другого если нет контекста или хотя бы даты снимка).

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

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

Но об этом чуть позже.

Белогривые лошадки?

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

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

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

На случай, если кого интересуют облачные решения, оставлю пару ссылок:

https://azure.microsoft.com/en-us/services/cognitive-services/face/

https://cloud.google.com/vision/docs/face-tutorial

https://aws.amazon.com/rekognition/

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

CPU -> GPU

Сначала я, будучи наивным, запустил поиск лиц по алгоритму CNN (см. ниже) на CPU.

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

Но, как и многие, я знал, что для ускорения некоторых типов вычислений можно применять GPU. Так вот, Face Recognition как из тех типов вычислений. Поэтому, почитав отзывы и подобрав подходящий вариант, я помчался на местную онлайн барахолку и за довольно небольшую сумму наличности купил GeForce GTX 1050 Ti. И, даже на такой скромной карточке, процесс пошел куда шустрее меньше секунды на одну фотографию! Но, увы, это не происходит по щелчку пальцев. Вначале надо чтобы весь зоопарк библиотек смог с этой видеокарточкой заработать.

И тут начинается веселье: сперва надо поставить драйвера для видеокарточки и CUDO. Потом поставить библиотеки с поддержкой CUDO? Нет, потом надо ставить сборочное окружение, так как теперь все те библиотеки, что запросто встали из репозиториев без поддержки GPU ускорения придется собирать и ставить руками.

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

Итак, сервер настроен, библиотеки стоят, вентиляторы охлаждения CPU и GPU работают на малых оборотах в ожидании задач, можно и начать распознавать лица.

Этапы распознавания

Весь этап распознавания лиц на фотографии можно разбить на несколько этапов:

  1. Нахождение лиц на фотографии (face detection)

  2. Поиск элементов лица (landmarks detection)

  3. Кодировка лица (face encoding)

  4. Сравнение лица с шаблонами (face matching)

Нахождение лиц на фотографии можно делать разными способами, но самые популярные это:

  • Гистограмма направленных градиентов (HOG).

  • Алгоритм на базе сверточных нейронных сетей (CNN).

HOG работает быстро, вполне достаточно CPU, но распознает хуже и только фронтальные лица.

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

Я использовал реализации этих алгоритмов из библиотеки face_recognition (пришлось немного ее модифицировать, чтобы бы заставить работать с элементами лица полученными в других библиотеках, см. ниже). Эта библиотека является по сути удобной оберткой над dlib.

При первом же прогоне полноформатной фотографии на 8 мегапикселей с цифрового фотоаппарата, видеокарта сказала: Упс и выкинула исключение о том, что, мол, милый друг, моих 4 GB видеопамяти для этой задачи маловато. Поэтому волевым решением было решено урезать все входные фотографии до 1000 пикселей по длинной стороне (max_image_size в конфиге, если что), этого размера вполне достаточно, если вас не сильно интересуют лица всяких прохожих далеко на заднем плане (о да, иногда, в процессе отладки, там попадались довольно странные лица)

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

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

Я игрался с двумя алгоритмами: первый из упомянутой выше библиотеки face_recognition (она же dlib), второй из библиотеки face-alignment.

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

Это меня немало так опечалило, и я предпринял две попытки это как-то исправить.

Вначале, решил в лоб научить нейронную сеть отличать хороший результат от плохого, руками отобрал примерно 10000 фотографий с хорошим/плохим результатом настроил нейронку и получил точность около 80%, что было совсем недостаточно, так как до этого система примерно на таком же количестве искала точки корректно. Да, немного точность повысилась, но процент брака, а соответственно потенциальной ручной работы, был велик.

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

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

Для лучшего распознавания элементов лица некоторым алгоритмам (например из библиотеки deepface) желательно чтобы лицо было выровнено по линии глаз, но некоторые обходятся и без этого (из face_recogintion, dlib).

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

После кодировки лицо можно сравнить с кодировками шаблонов других лиц и по близости к ним судить о принадлежности одному или другому человеку.

Вот как раз с шаблонами и началась самая большая заморочка.

При малом количестве шаблонов (я в начале взял по одной фотографии лица каждого человека которого надо было распознавать, потом по 10) у меня так и не получилось подстроить пороги срабатывания так, чтобы близкие родственники однозначно распознавались, кто есть кто. Поставишь большой порог, система путает братьев/сестер, поставишь маленький, и для многих фотографий соответствия вообще не находится. В итоге, методом многократных итераций, подобрал максимальный порог при котором не было путаницы между отдельными людьми и смирился с тем что шаблонов придется делать много, очень много. Теперь если взять фотографии за тот год из которого нет ни одного шаблона, то лишь где-то две трети лиц будут однозначно распознаны, остальные либо попадут в категорию вроде бы похож (weak match) либо вообще не распознаны, такие приходиться посмотреть глазами, добавить в шаблоны и повторить итерацию сравнения.

Профильно-фронтальные проблемы

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

К сожалению, я не смог найти в открытом доступе готовых алгоритмов которые решают эту задачу и смирился с тем что шаблонов потребуется много. При этом я также понял, что в большинстве случаев для исходных задач (а ну-ка найди мне фотографии Алисы с бабушкой) чаще всего фотографии в профиль не так важны, поэтому добавил в систему возможность отсекать фотографии в профиль, тем самым снижая объем ручной работы. Так что, если кто-то захочет воспользоваться моей системой, но при этом не готов на подвиг в виде бессонных ночей рассматривания сотен и тысяч (пусть даже любимых) лиц, то можно просто использовать конфигурационный файл с именем frontal.cfg и снизить объем последующей ручной работы в несколько раз.

А что насчет видео?

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

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

Во-первых, распознавать только первые несколько минут видео (max_video_frames в конфиге) так как, чаще всего, все главные действующие лица уже появились на видео. Во-вторых, распознавать не каждый кадр, а с некоторым прореживанием (video_frames_step в конфиге) так как главные действующие лица не мелькают в кадре лишь на мгновение, а держатся долго. Ну и в довершение, лицо должно попасться не в одном кадре, а в нескольких (min_video_face_count в конфиге) по той же причине, если лицо только мигнуло, то для исходной задачи оно не так уж и важно.

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

Если кому-то захотелось воспользоваться моими наработками, все исходники и краткая инструкция тут.

Под катом я приведу

несколько скриншотов и сценариев использования системы

Представим, что вы из тех людей, что читают README файлы и систему уже поставили и даже запустили.

Перед вами главное окно. Запустим распознавание папки:

Recognition -> Add new files

(первый запуск будет довольно долгий, так как библиотеки подгружают необходимые модели из Интернета)

Получим кучу нераспознанных лиц:

Добавим (кликом на нужные или выделением с последующим кликом на одну из выделенных) их в шаблоны, введем новое имя для прекрасной незнакомки:

По мере добавления новых людей, список будет расширяться и вводить имена будет не нужно, достаточно выбрать из уже введённых ранее (заглавную фотографию человека можно заменить подложив в папку с шаблонами файл 0_face.jpg).

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

Если просочилась ошибка поиска элементов лица, до данную фотографию опасно добавлять в шаблоны, так как совершенно непредсказуемо, что именно потом с ним совпадет. Для этого есть галочка Bad encoding, текущая фотография будет отнесена к данному человеку, но в сравнениях с другими она участвовать не будет.

Повторим сравнение: Match -> Rematch folder.

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

Пока лица находятся в категории weak или unknown они не будут синхронизироваться и по ним не будет осуществляться поиск.

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

откроется оригинал фотографии.

А если хочется узнать с каким именно шаблоном было совпадение можно кликнуть на иконку

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

Ну распознали мы все, а что дальше?

Как дальше искать людей? Теперь в базе данных есть информация по каждой фотографии, кто на ней изображен. Отлично! Но как смотреть теперь? Поскольку у меня основной системой просмотра фотографий сейчас является Plex, то я не придумал ничего лучше, чем экспортировать данные о людях на фото в эту его базу в виде тегов. К сожалению, открытого API у них нет, но, к счастью, его база просто хранится в sqlite файле и имеет не очень сложный формат. Поэтому я просто пишу туда теги на прямую. (Не буду грузить статью деталями реализации базы данных Plex, но, если кому-то они интересны, могут посмотреть в исходниках в файле plexdb.py).

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

face-rec-plexsync -a set_tags

Немного подождать и вуаля! Теперь можно искать!

И все было замечательно до тех пор, пока некоторое время назад кто-то особо талантливый в команде разработки Plex не поломал расширенный поиск. Так что искать по отдельным лицам можно, а вот уже по сложным критериям (несколько лиц, лицо и год и т.д. теперь нельзя). Баг у них на форуме висит уже больше года, народ жалуется, но воз и ныне там. Поэтому я добавил возможность поиска в консоль и если консоль такой же ваш друг, как и мой, то можно обойтись и ей. (Up: в момент когда статья была уже на финальной стадии, баг все-таки починили и в следующем релизе все должно работать)

Например, с помощью вот такого запроса, можно найти все фотографии с Тимой и Алисой за 2020 год

face-rec-db -a find_files_by_names -f 2020 -n Тима,Алиса

Понятно, что смотреть фотографии в консоли не очень интересно, но если добавить после команды что-то вроде

| xargs -I{} ln -s {} /mnt/multimedia/query/

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

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

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

Заключение

Вот такой вот небольшой пример наведения порядка в домашних фотографиях.

Многое уже сделано, стало гораздо удобнее, но еще есть масса хотелок, надеюсь когда-нибудь и до них дойдут руки:

  • Распознать голос в видео и аудио записях. Есть десятки тысяч аудио заметок и искать по ним вообще невозможно, это даже хуже, чем видео.

  • Кроме лиц добавить распознание других объектов: животные, предметы.

  • Как фантазия на будущее, распознавать условия съемки и окружение: горы, море, в помещении и т.д.

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

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

Спасибо за внимание!

Подробнее..

Тихая революция и новый дикий запад в ComputerVision

21.04.2021 02:17:19 | Автор: admin

Казалось бы, революция с Computer Vision уже была. В 2012 году выстрелили алгоритмы основанные на сверточных нейронных сетях. Года с 2014 они дошли до продакшна, а года с 2016 заполонили все. Но, в конце 2020 года прошел новый виток. На этот раз не за 4 года, а за один. поговорим о Трансформерах в ComputerVision. В статье будет обзор новинок, которые появились в последний год. Если кому-то удобнее, то статья доступна в виде видео на youtube.

Трансформеры - это такой тип нейронных сетей, созданных в 2017 году. Изначально, они использовались для переводов:

Но, как оказалось, работали просто как универсальная модель языка. И пошло-поехало. Собственно, известная GPT-3 - порождение трансформеров.

А что с ComputerVision?
А вот тут все интересненько. Не сказать, что трансформеры хорошо подходят для таких задач. Все-таки временные ряды, да к тому же одномерные. Но уж больно хорошо работают в других задачах. В своем рассказе я пройдусь по наиболее ключевым работам, интересным местам их приложения. Постараюсь рассказать про разные варианты как трансформеры смогли запихать в CV.

DETR

На дворе 2020. Поперло. С чего? Тут сложно сказать. Но мне кажется, надо начать с DETR (End-to-End Object Detection with Transformers), который вышел в мае 2020 года. Тут Трансформеры применяются не к изображению, а к фичам выделенным сверточной сетью:

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

Конечно, есть пара забавных штук, которых до DETR никто не делал (например как реализуется позиционное кодирование, которое необходимо для трансформера). Я описал свои впечатления тут.
Могу лишь добавить, что DETR открыл путь к возможности использования трансформеров для ComputerVision.Использовали ли его на практике? Работает ли он сейчас? Не думаю:

  1. Основная его проблема - сложность обучение, большое время обучения. Частично эту проблему решил Deformable DETR.

  2. DETR не универсальный. Есть задачи где работают лучше другие подходы. Например тот же iterdet. Но в каких-то задачах лидерство держит до сих пор (или его производные - https://paperswithcode.com/sota/panoptic-segmentation-on-coco-panoptic ).

Сразу после DETR вышел Visual Transformer (статья + неплохой обзор) для классификации. Тут трансформеры тоже берут выходной Feature map с стандартного backbone:

Я бы не назвал Visual Transformer большим шагом, но это характерная для тех времен мысль. Попробовать применить трансформер к тем или иным выделенным через backbone фичам.

VIT

Поехали дальше. Следующий большой шаг это ViT:

Он был опубликован в начале декабря 2020 года (реализация). И тут все уже по-взрослому. Трансформер как он есть. Картинка разбивается на мини-участки 16*16. Каждый участок подается в трансформер как слово, дополняясь позиционным энкодером.

И, внезапно, это все заработало. Не считая того что училось все долго (и точность не state-of-art). И на базах меньше 14 миллионов изображений работало как-то не топово.
Но все эти проблемы решил аналог. На этот раз от FaceBook - Deit. Который сильно упрощал обучение и инференс.

На больших датасетах этот подход до сих пор держит первые места почти на всех классификациях - https://paperswithcode.com/paper/going-deeper-with-image-transformers

На практике мы как-то попробовали использовать в одной задаче. Но, с датасетом в ~2-3 тысячи картинок, все это не очень заработало. И классические ResNet были куда стабильнее и лучше.

CLIP

Пойдем дальше. CLIP. Это очень интересное применение трансформеров совсем с другой стороны. В CLIP задача переформулирована. Задача не в том чтобы распознать изображение, а найти максимально близкое текстовое описание для изображения. Здесь трансформер учит лингвистическую часть эмбединга, а сверточная сеть - визуальные эмбединги:

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

Иногда это работает даже слишком круто:

Но, не смотря на то что это хорошо работает на некоторых датасетах - это не универсальный подход:

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

Мы пробовали из интереса протестировать на нескольких задачах, например распознавание действий/одежды. И везде CLIP работает очень плохо. Вообще про CLIP можно рассказывать очень долго. На Хабре есть хорошая статья. А я делал видео, где говорил про него:

Vision Transformers for Dense Prediction

Следующая сетка, которая, на мой взгляд показательна - Vision Transformers for Dense Prediction, которая вышла месяц назад. В ней можно переключаться между подходами Vit/Detr. Можно для первого уровня использовать свертки, а можно трансформеры.

При этом сетка используется не для детекции/классификации, а для сегментации/оценки глубины. Что дает State-of-art результат сразу по нескольким категориям, при этом в RealTime. Вообще очень печально что @AlexeyAB (автор Yolov4 и один из авторов статьи), не бахнул сюда публикацию отдельную про него. В целом сетка приятная, запускается из коробки, но пока нигде не пробовал. Если кому-то интересно, я делал более подробный обзор тут:

---------------------------------------

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

  • Трансформеры используются для обработки выхода сверточной сети

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

  • Трансформеры используются напрямую применяясь к изображению

  • Гибрид подходов 1-2

Все что будет ниже - примеры того как те же самые трансформеры/подходы используются для других задач. Поехали.

PoseFormer

Pose3D. Трансформер можно применить и к явным фичам, выделенным уже готовой сетью, например к скелетам:

В этой работе Трансформер используется для восстановления 3д модели человека по серии кадров. В CherryLabs мы делали такое (и более сложные реконструкции) ещё года 3 назад, только без трансформеров, с эмбедингами. Но, конечно, Трансформеры позволяют сделать это быстрее и стабильнее. Результат - вполне неплохое и стабильное 3D, без переобучения:

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

Если что, идея пришла одновременно много кому. Вот ещё один подход/реализация той же идеи.

TransPose

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

Сравните с классическими подходами в распознавании позы (достаточно старая версия OpenPose)

И таких стейджей было в разных работах до десятка. Сейчас они заменены одним трансформером. Получается, конечно, сильно лучше чем у современных сетей:

SWIN

Выше мы уже упоминали одну сетку по сегментации на базе Трансформеров от Intel. SWIN от Microsoft показывает результаты лучше, но уже не в RealTime.По сути это улучшенный и расширенный VIT/Deit, переработанный под сегментацию:

Это сказывается на скорости, зато внушительное качество, лидерство в множестве категорий - https://paperswithcode.com/paper/swin-transformer-hierarchical-vision

LOFTR

Есть задачи в которых сверточные сети вообще не очень работают. Например задача сопоставления двух изображений. Года полтора назад для такого зачастую использовали классический пайплайн через SIFT/SURF+RANSAK ( хороший гайд на эту тему + видео которое я записывал год назад ). Год назад появился SuperGlue- единственное крутое применение Graph Neural Network которые я видел в ComputerVision. При этом SuperGlue решал только задачу сопоставления. А теперь есть решение на трансформерах, LOFTR практически End-To-End:

Сам я попользоваться не успел, но выглядит круто:

Распознавание действий

В целом, конечно, трансформеры хороши всюду где есть последовательности, сложная логическая структура или требуется их анализ. Уже есть несколько сетей где действия анализируются трансформерами: (Video Transformer Network, ActionBert). Обещают в ближайшее время добавить в MMAction.

Трекинг

Я уже писал год назад огромную статью на Хабре что работает в трекинге и как трекать объекты. Множество подходов, сложная логика. Прошел всего год, и по многим бенчмаркам есть безусловный лидер - STARK:

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

TransTrackTransTrackTransTTransT

И все имеют неплохие скоры.

ReID

Реиндентификацию можно вынести из трекинга, как вы помните. 20 дней назад вышел трансформер с распознаванием ReID- весьма неплохо может бустануть трекинг.

Распознавания лиц через трансформеры недельной давности похоже тоже подошло:

Медицина

Если смотреть более конкретные применения тут тоже много интересного. VIT уже вовсю запихивают для анализа КТ и МРТ (1,2):

И для сегментации (1,2):

Удивительное

Что меня удивляет - я не вижу хорошей реализации OCR на трансформерах. Есть несколько примеров, но по бенчмаркам они как-то на дне:

Все state-of-art пока на классических подходах. Но люди пробуют. Даже сам что-то года 2 назад пробовал прикрутить. Но что-то результата это не дает.

Ещё из интересного

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

Что дальше

Мне кажется что трансформеры должны выйти в топ почти по всем категориям для ComputerVision. И, безусловно, для любой аналитики видео.

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

Ну а пока, смотрим на то как изменяется мир. Буквально каждый день. Когда материала накапливается достаточно - я обычно выкладываю большую статью на хабр. А про отдельные статьи/идеи обычно рассказываю у себя в канале - https://t.me/CVML_team (дублирую сюда https://vk.com/cvml_team ).

А текущая статья, если кому удобнее, доступна на youtube:

Подробнее..

Распознаем номера автомобилей. Разработка multihead-модели в Catalyst

11.06.2021 08:06:47 | Автор: admin

Фиксация различных нарушений, контроль доступа, розыск и отслеживание автомобилей лишь часть задач, для которых требуется по фотографии определить номер автомобиля (государственный регистрационный знак или ГРЗ).

В этой статье мы рассмотрим создание модели для распознавания с помощью Catalyst одного из самых популярных высокоуровневых фреймворков для Pytorch. Он позволяет избавиться от большого количества повторяющегося из проекта в проект кода цикла обучения, расчёта метрик, создания чек-поинтов моделей и другого и сосредоточиться непосредственно на эксперименте.

Сделать модель для распознавания можно с помощью разных подходов, например, путем поиска и определения отдельных символов, или в виде задачи image-to-text. Мы рассмотрим модель с несколькими выходами (multihead-модель). В качестве датасета возьмём датасет с российскими номерами от проекта Nomeroff Net. Примеры изображений из датасета представлены на рис. 1.

Рис. 1. Примеры изображений из датасета

Общий подход к решению задачи

Необходимо разработать модель, которая на входе будет принимать изображение ГРЗ, а на выходе отдавать строку распознанных символов. Модель будет состоять из экстрактора фичей и нескольких классификационных голов. В датасете представлены ГРЗ из 8 и 9 символов, поэтому голов будет девять. Каждая голова будет предсказывать один символ из алфавита 1234567890ABEKMHOPCTYX, плюс специальный символ - (дефис) для обозначения отсутствия девятого символа в восьмизначных ГРЗ. Архитектура схематично представлена на рис. 2.

Рис. 2. Архитектура модели

В качестве loss-функции возьмём стандартную кросс-энтропию. Будем применять её к каждой голове в отдельности, а затем просуммируем полученные значения для получения общего лосса модели. Оптимизатор Adam. Используем также OneCycleLRWithWarmup как планировщик leraning rate. Размер батча 128. Длительность обучения установим в 10 эпох.

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

Кодирование

Далее рассмотрим основные моменты кода. Класс датасета (листинг 1) в общем обычный для CV-задач на Pytorch. Обратить внимание стоит лишь на то, как мы возвращаем список кодов символов в качестве таргета. В параметре label_encoder передаётся служебный класс, который умеет преобразовывать символы алфавита в их коды и обратно.

class NpOcrDataset(Dataset):   def __init__(self, data_path, transform, label_encoder):       super().__init__()       self.data_path = data_path       self.image_fnames = glob.glob(os.path.join(data_path, "img", "*.png"))       self.transform = transform       self.label_encoder = label_encoder    def __len__(self):       return len(self.image_fnames)    def __getitem__(self, idx):       img_fname = self.image_fnames[idx]       img = cv2.imread(img_fname)       if self.transform:           transformed = self.transform(image=img)           img = transformed["image"]       img = img.transpose(2, 0, 1)             label_fname = os.path.join(self.data_path, "ann",                                  os.path.basename(img_fname).replace(".png", ".json"))       with open(label_fname, "rt") as label_file:           label_struct = json.load(label_file)           label = label_struct["description"]       label = self.label_encoder.encode(label)        return img, [c for c in label]

Листинг 1. Класс датасета

В классе модели (листинг 2) мы используем библиотеку PyTorch Image Models для создания экстрактора фичей. Каждую из классификационных голов модели мы добавляем в ModuleList, чтобы их параметры были доступны оптимизатору. Логиты с выхода каждой из голов возвращаются списком.

class MultiheadClassifier(nn.Module):   def __init__(self, backbone_name, backbone_pretrained, input_size, num_heads, num_classes):       super().__init__()        self.backbone = timm.create_model(backbone_name, backbone_pretrained, num_classes=0)       backbone_out_features_num = self.backbone(torch.randn(1, 3, input_size[1], input_size[0])).size(1)        self.heads = nn.ModuleList([           nn.Linear(backbone_out_features_num, num_classes) for _ in range(num_heads)       ])     def forward(self, x):       features = self.backbone(x)       logits = [head(features) for head in self.heads]       return logits

Листинг 2. Класс модели

Центральным звеном, связывающим все компоненты и обеспечивающим обучение модели, является Runner. Он представляет абстракцию над циклом обучения-валидации модели и отдельными его компонентами. В случае обучения multihead-модели нас будет интересовать реализация метода handle_batch и набор колбэков.

Метод handle_batch, как следует из названия, отвечает за обработку батча данных. Мы в нём будем только вызывать модель с данными батча, а обработку полученных результатов расчёт лосса, метрик и т.д. мы реализуем с помощью колбэков. Код метода представлен в листинге 3.

class MultiheadClassificationRunner(dl.Runner):   def __init__(self, num_heads, *args, **kwargs):       super().__init__(*args, **kwargs)       self.num_heads = num_heads    def handle_batch(self, batch):       x, targets = batch       logits = self.model(x)             batch_dict = { "features": x }       for i in range(self.num_heads):           batch_dict[f"targets{i}"] = targets[i]       for i in range(self.num_heads):           batch_dict[f"logits{i}"] = logits[i]             self.batch = batch_dict

Листинг 3. Реализация runnerа

Колбэки мы будем использовать следующие:

  • CriterionCallback для расчёта лосса. Нам потребуется по отдельному экземпляру для каждой из голов модели.

  • MetricAggregationCallback для агрегации лоссов отдельных голов в единый лосс модели.

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

  • SchedulerCallback для запуска LR Schedulerа.

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

  • CheckpointCallback чтобы сохранять лучшие веса модели.

Код, формирующий список колбэков, представлен в листинге 4.

def get_runner_callbacks(num_heads, num_classes_per_head, class_names, logdir):   cbs = [       *[           dl.CriterionCallback(               metric_key=f"loss{i}",               input_key=f"logits{i}",               target_key=f"targets{i}"           )           for i in range(num_heads)       ],       dl.MetricAggregationCallback(           metric_key="loss",           metrics=[f"loss{i}" for i in range(num_heads)],           mode="mean"       ),       dl.OptimizerCallback(metric_key="loss"),       dl.SchedulerCallback(),       *[           dl.AccuracyCallback(               input_key=f"logits{i}",               target_key=f"targets{i}",               num_classes=num_classes_per_head,               suffix=f"{i}"           )           for i in range(num_heads)       ],       dl.CheckpointCallback(           logdir=os.path.join(logdir, "checkpoints"),           loader_key="valid",           metric_key="loss",           minimize=True,           save_n_best=1       )   ]     return cbs

Листинг 4. Код получения колбэков

Остальные части кода являются тривиальными для Pytorch и Catalyst, поэтому мы не станем приводить их здесь. Полный код к статье доступен на GitHub.

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

Рис. 3. График лосс-функции модели в процессе обучения. Оранжевая линия train loss, синяя valid loss

В списке ниже перечислены некоторые ошибки, которые модель допустила на тест-сете:

  • Incorrect prediction: T970XT23- instead of T970XO123

  • Incorrect prediction: X399KT161 instead of X359KT163

  • Incorrect prediction: E166EP133 instead of E166EP123

  • Incorrect prediction: X225YY96- instead of X222BY96-

  • Incorrect prediction: X125KX11- instead of X125KX14-

  • Incorrect prediction: X365PC17- instead of X365PC178

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

Заключение

В статье мы рассмотрели способ реализации multihead-модели для распознавания ГРЗ автомобилей с помощью фреймворка Catalyst. Основными компонентами явились собственно модель, а также раннер и набор колбэков для него. Модель успешно обучилась и показала высокую точность на тестовой выборке.

Спасибо за внимание! Надеемся, что наш опыт был вам полезен.

Больше наших статей по машинному обучению и обработке изображений:

Подробнее..

Применение предобученной модели VGG16 для рекомендаций на основе изображений товаров

04.03.2021 02:16:58 | Автор: admin

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


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

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

Надо сказать, что в данном проекте у меня не было возможности делать интеграцию с бэкэндом интернет-магазина - классическая история для малых и средних интернет-магазинов. Необходимо было рассчитывать только на систему, которую я сделаю в стороне от сайта. Поэтому в качестве визуального решения на самом сайте я решил сделать всплывающий js-виджет. Одной строчкой в html-код добавляется js, понимает заголовок страницы, на который пришел пользователь, и передает его в бэкэнд сервиса. Если бэкэнд нашел в своей базе заранее загруженных товаров товар, то он ищет опять же в заранее подготовленной базе товаров, рекомендации и возвращает их в js, а js их потом отображает в виджете. Также, для снижения влияния на скорость загрузки, js создает iframe, в котором производит все работы с отображением виджета. Помимо прочего, это еще и позволяет убрать проблему с пересечением css-классов виджета и сайта.

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

А теперь перейдем непосредственно к поиску похожих товаров.

Для данного магазина я сделал два варианта поиска схожих товаров (опять же, классика A/B-тестирования) - рекомендации просто по схожим характеристикам; рекомендации, которые включали в себя первым слоем схожесть изображения, а вторым - схожесть характеристик.

С поиском схожих просто по характеристикам дело ясное. Перейдем к поиску схожих по изображениям.

Для начала подгружаем необходимые библиотеки:

!pip install theano%matplotlib inlinefrom keras.models import Sequentialfrom keras.layers.core import Flatten, Dense, Dropoutfrom keras.layers.convolutional import Convolution2D, MaxPooling2D, ZeroPadding2Dfrom keras.optimizers import SGDimport cv2, numpy as npimport osimport h5pyfrom matplotlib import pyplot as pltfrom keras.applications import vgg16from keras.applications import Xceptionfrom keras.preprocessing.image import load_img,img_to_arrayfrom keras.models import Modelfrom keras.applications.imagenet_utils import preprocess_inputfrom PIL import Imageimport osimport matplotlib.pyplot as pltimport numpy as npfrom sklearn.metrics.pairwise import cosine_similarityimport pandas as pdimport theanotheano.config.openmp = True

Дальше мне необходимо отсортировать изображения (так как они у меня названы исходя из индексов товаров в датафрейме, то я их отсортирую по алфавиту):

import redef sorted_alphanumeric(data):    convert = lambda text: int(text) if text.isdigit() else text.lower()    alphanum_key = lambda key: [ convert(c) for c in re.split('([0-9]+)', key) ]     return sorted(data, key=alphanum_key)dirlist = sorted_alphanumeric(os.listdir('images'))r1 = []r2 = []for i,x in enumerate(dirlist):    if x.endswith(".jpg"):        r1.append((int(x[:-4]),i))        r2.append((i,int(x[:-4])))extid_to_intid_dict = dict(r1)intid_to_extid_dict = dict(r2)

Задаем некоторые параметры:

imgs_path = "images/"imgs_model_width, imgs_model_height = 224, 224nb_closest_images = 3 # количество ближайших изображений (для тестирования вывода)

Загружаем саму модель (уже предобученную):

vgg_model = vgg16.VGG16(weights='imagenet')

Удаляем последний слой (следний слой даюет на выходе вероятности каждого из 1000 классов ImageNet - нам это не нужно в данном случае. Нам нужен просто 4096-мерный вектор для каждого из изображений, по которым мы потом будем искать с помощью простой косинусной метрикой).

Как найти имя слоя это отдельный анекдот, поэтому я смотрел в исходники модели.

Итак:

feat_extractor = Model(inputs=vgg_model.input, outputs=vgg_model.get_layer("fc2").output)

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

feat_extractor.summary()

Далее я иду в каталог заранее подготовленных изображений товаров (то есть, я беру xml каталога интернет-магазина, прохожусь по урлам, которые нашел для товаров, и скачиваю их в папку; ниже покажу код, который делает эту работу):

files = [imgs_path + x for x in os.listdir(imgs_path) if "jpg" in x]print("number of images:",len(files))

Дальше мне необходимо отсортировать изображения по названиям для более аккуратной работы с ними в последующем:

import redef atof(text):    try:        retval = float(text)    except ValueError:        retval = text    return retvaldef natural_keys(text):    '''    alist.sort(key=natural_keys) sorts in human order    http://nedbatchelder.com/blog/200712/human_sorting.html    (See Toothy's implementation in the comments)    float regex comes from https://stackoverflow.com/a/12643073/190597    '''    return [ atof(c) for c in re.split(r'[+-]?([0-9]+(?:[.][0-9]*)?|[.][0-9]+)', text) ]files.sort(key=natural_keys)

Далее загружаю изображения в специальный PIL формат:

original = load_img(files[1], target_size=(imgs_model_width, imgs_model_height))plt.imshow(original)plt.show()print("image loaded successfully!")

Конвертирую PIL изображения в numpy array:
в PIL формат данных - width, height, channel
в Numpy - height, width, channel

numpy_image = img_to_array(original) # сырое изображения в вектор

Конвертирую изображения в batch format.
expand_dims добавит дополнительное измерение к данным на определенной оси

Мы хотим, чтобы входная матрица в сеть имела следующий формат - batchsize, height, width, channels. Таким образом, мы добавляем дополнительное измерение к оси 0.

image_batch = np.expand_dims(numpy_image, axis=0) # превращаем в вектор-строку (2-dims)print('image batch size', image_batch.shape)

Подготавливаем изображение для VGG:

processed_image = preprocess_input(image_batch.copy()) #  библиотечная подготовка изображения

Теперь нам необходимо получить как бы особенности данного вектора (вытащить признаки):

img_features = feat_extractor.predict(processed_image)

То есть это будет векторочек признаков для данного изображения:

print("features successfully extracted!")print("number of image features:",img_features.size)img_features

Теперь всё тайное стало явью, и осталось дело техники итерироваться по всем изображениям и для каждого из него произвести векторизацию.

importedImages = []for f in files:    filename = f    original = load_img(filename, target_size=(224, 224))    numpy_image = img_to_array(original)    image_batch = np.expand_dims(numpy_image, axis=0)        importedImages.append(image_batch)    images = np.vstack(importedImages)processed_imgs = preprocess_input(images.copy())

Вытащим все особенности изображения:

imgs_features = feat_extractor.predict(processed_imgs)print("features successfully extracted!")imgs_features.shape

Ну и дальше посчитаем уже косинусную схожесть между изображениями:

cosSimilarities = cosine_similarity(imgs_features)

Сохраним результат в pandas dataframe:

columns_name = re.findall(r'[0-9]+', str(files))cos_similarities_df = pd.DataFrame(cosSimilarities, columns=files, index=files)cos_similarities_df.head()

Дальше получилась такая ситуация. В каталоге товаров данного магазина около 6000 SKU. После всех манипуляций выше получилась матрица размером 6000 * 6000. В каждом пересечении строк и столбцов находилось число формата float от 0 до 1 с 8 знаками после нуля, которое показывало схожесть. Когда я пробовал сохранить эту матрицу как есть в файл для дальнейшего применения в сервисе рекомендаций, то у меня получался файл весом около 430 мегабайт (хотя в оперативной памяти такая матрица занимала около 130 мегайбайт). Для меня это было не приемлемо. Хотя бы по той простой причине, что мне нужно было как-то выкладывать этот файл в GitHub, а дальше автоматома деплоить на сервер. GitHub не позволяет грузить файлы больше 100 мегайбат (или во всяком случае я не знаю как это делать). Да и в целом мне казалось, что это какой-то неприлично большой файл. Поэтому я начал думать :) И придумал вот что - мне по-большому счету не важно сколько знаков после запятой будет здесь - мне главное просто сравнивать цифры между собой. Поэтому я сделал следующее:

cos_similarities_df_2.round(2) # cos_similarities_df_2 - название датафрейма с косинусными метриками, которые сохранил выше

То есть, для начала я просто взял и отсек лишние цифры. Но формат колонки все равно оставался float. А в pandas float может быть минимально float16 - много.

Тогда я решил перевести эти значения в int:

cos_similarities_df_2.apply(lambda x: x * 100)cos_similarities_df_2.apply(lambda x: x.astype(np.uint8))

После этих манипуляций размер матрицы в оперативке уменьшился до 31 мегабайта. Это уже радовало.

Ну и дальше я сохранил этот файл в h5:

cos_similarities_df_2.to_hdf('storage/cos_similarities.h5', 'data')

В итоге получился файл весом 40 мегбайт. Это уже во-первых, удовлетворяло моим требованиям для хранения в GitHub, а во-вторых, в целом уже не так пугало своим размером :)

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

import re# function to retrieve the most similar products for a given onedef retrieve_most_similar_products(given_img):    print("-----------------------------------------------------------------------")    print("original product:")    original = load_img(given_img, target_size=(imgs_model_width, imgs_model_height))    original_img = int(re.findall(r'[0-9]+', given_img)[0])    print((df_items_2.iloc[[original_img]]['name'].iat[0], df_items_2.iloc[[original_img]]['pricer_uah'].iat[0], df_items_2.iloc[[original_img]]['url'].iat[0]))       plt.imshow(original)    plt.show()    print("-----------------------------------------------------------------------")    print("most similar products:")    closest_imgs = cos_similarities_df[given_img].sort_values(ascending=False)[1:nb_closest_images+1].index    closest_imgs_scores = cos_similarities_df[given_img].sort_values(ascending=False)[1:nb_closest_images+1]    for i in range(0,len(closest_imgs)):        original = load_img(closest_imgs[i], target_size=(imgs_model_width, imgs_model_height))        item = int(re.findall(r'[0-9]+', closest_imgs[i])[0])        print(item)        print((df_items_2.iloc[[item]]['name'].iat[0], df_items_2.iloc[[item]]['pricer_uah'].iat[0], df_items_2.iloc[[item]]['url'].iat[0]))        plt.imshow(original)        plt.show()        print("similarity score : ",closest_imgs_scores[i])kbr = '' # напишите сюда название товараfind_rec = int(df_items_2.index[df_items_2['name'] == kbr].tolist()[0]) # df_items_2 название моего базового датафрейма, куда я скачал каталог товаровprint(find_rec)retrieve_most_similar_products(files[find_rec])

Вот и все :)

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

А теперь еще покажу как я спарсил изображения, если это кому-то будет полезно:

Для начала создам необходимые мне директории, куда все буду складывать:

import osif not os.path.exists('storage'):    os.makedirs('storage')if not os.path.exists('images'):    os.makedirs('images')

Не буду показывать код, который делает подготовительну работу по созданию данных из xml магазина - это просто и не интересно.

А код, который забирает изображения, вот:

# importing required modulesimport urllib.requestimage_counter = 0error_list = []# Функция для загрузки изображения в нужную мне директориюdef image_from_df(row):    global image_counter        item_id = image_counter        filename = f'images/{item_id}.jpg'    image_url = f'{row.image}'    try:      conn = urllib.request.urlopen(image_url)           except urllib.error.HTTPError as e:      # Return code error (e.g. 404, 501, ...)      error_list.append(item_id)    except urllib.error.URLError as e:      # Not an HTTP-specific error (e.g. connection refused)            print('URLError: {}'.format(e.reason))    else:      # 200      urllib.request.urlretrieve(image_url, filename)      image_counter += 1

Теперь я беру мой сформированный датасет товаров из xml, и начинаю идти по каждой строчке и забирать изображения:

df_items_2.apply(lambda row: image_from_df(row), axis=1)

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

for i in error_list:  df_items_2.drop(df_items_2.index[i], inplace = True)  df_items_2.reset_index(drop=True, inplace = True) print(f'Удалил строки без изображений: {error_list}')print(len(error_list))

Собственно говоря, вот и все. Надеюсь, это кому-то будет полезно! )

А если нет, то не судите строго - хотел как лучше )

P.S. Кстати, недавно открыл для себя следующую вариацию VGG - VGG19. Судя по тестам, эта версия дает еще более лучшие предсказания.

P.S.S Отдельно хочу выразить большую благодарсть людям, без которого я бы не смог все это реализовать: это мой брат, Senior JavaScript Developer (помогал мне написать js для сайта и обходить CORS-политики); это Костя Дедищев, Senior Python Developer и Senior Engineer (помогал мне заворачивать все в Docker и настраивать CI/CD pipeline); это Екатерина Артюгина из SkillFactory, которая три месяца возилась со мной и с другими ребятами с рамках SkillFactory Accelerator (это курс я взял специально для того, чтобы создать свой первый реальный Data Science проект под присмотром более опытных ребят); это и Валентина Бабушкина (ментор, который помогал понять суть A/B-тестов и включить их в проект); это Валентин Малых (еще один ментор, который помогал с разумением NLP проблем и в частности работы опечаточников при создании чат-ботов (еще второй проект, над которым я работал в рамках акселлератора и о котором я может быть расскажу чуть позже); это Эмиль Маггерамов (ментор, который в целом курировал мое продвижение в акселлераторе по созданию данного проекта); это одногруппники Valery Kuryshev и Георгий Брегман (регулярно раз в неделю созванивались и делились полученным за неделю опытом).

Подробнее..

Анализ колоса пшеницы методами компьютерного зрения. Определение плоидности

19.08.2020 12:13:25 | Автор: admin
14-ого августа завершился первый воркшоп Математического центра в Академгородке. Я выступал в роли куратора проекта по анализу колоса пшеницы методами компьютерного зрения. В этой заметке хочу рассказать, что из этого вышло.

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

image

Описание данных


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

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

image

Всего 3603 фото с 644 уникальными посевными номерами. В наборе данных представлено 20 видов пшениц: 10 гексаплоидных, 10 тетраплоидных; 496 уникальных генотипов; 10 уникальных вегетаций. Растения были выращены в период с 2015 по 2018 годы в теплицах ИЦиГ СО РАН. Биологический материал предоставлен академиком Николаем Петровичем Гончаровым.

Валидация


Одному растению в нашем наборе данных может соответствовать до 5 фотографий, снятых по разным протоколам и в разных проекциях. Мы разделили данные на 3 стратифицированные подборки: train (обучающая выборка), valid (валидационная выборка) и hold out (отложенная выборка), в соотношениях 60%, 20% и 20% соответственно. При разбиение мы учитывали, что бы все фотографии определенного генотипа всегда оказывались в одной подвыборке. Данная схема валидации использовалась для всех обучаемых моделей.

Пробуем классический CV и ML методы


Первый подход, который мы использовали для решения поставленной задачи основан на существующем алгоритме разработанным нами ранее. Алгоритм из каждого изображения позволяет извлечь фиксированный набор различных количественных признаков. Например, длина колоса, площадь остей и т.д. Подробное описание алгоритма есть в статье Genaev et al., Morphometry of the Wheat Spike by Analyzing 2D Images, 2019. Используя данный алгоритм и методы машинного обучения, нами были обучены несколько моделей для предсказания типов плоидности.

image

Мы использовали методы логистической регрессии, случайный лес и градиентный бустинг. Данные были предварительно нормированы. В качестве меры точности мы выбрали AUC.

Метод Train Valid Holdout
Logistic Regression 0.77 0.70 0.72
Random Forest 1.00 0.83 0.82
Boosting 0.99 0.83 0.85


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

Интерпретируем результаты


Для каждой модели мы получали оценку важности каждого признака. В результате получили список всех наши признаков, ранжированный по значимости и отобрали топ 10 признаков: Awns area, Circularity index, Roundness, Perimeter, Stem length, xu2, L, xb2, yu2, ybm. (описание каждого признака можно посмотреть тут).
Примером важных признаков являются длина колоса и его периметр. Их визуализация показана на гистограммах. Видно, что гексаплоидные растения по характеристикам размера больше,
чем тетраплоидные.
image

Мы выполнили кластеризацию топ 10 признаков методом t-SNE

image

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

Для подтверждения нашей гипотезы о большей фенотипической изменчивости у гексаплоидов мы применили F статистку. F статистика дает значимость различий дисперсий двух распределений. Опровержением нулевой гипотезы о том, что не существует различий между двумя распределениями мы считали случаи, когда значение p-value меньше 0.05. Мы провели этот тест независимо для каждого признака. Условия проверки: должна быть выборка независимых наблюдений (в случае нескольких изображений это не так) и нормальность распределений. Для выполнения этих условий мы проводили тест по одному изображению каждого колоса. Брали, фотографии только в одной проекции по протоколу table. Результаты показаны в таблице. Видно, что дисперсия для гексоплоидов и тетраплоидов имеет значимые различия для 7 признаков. Причем во всех случаях значение дисперсии выше у гексоплоидов. БОльшую фенотипическую вариабельность у гексаплоидов можно объяснить большим числом копий одного гена.

Name F-statistic p-value Disp Hexaploid Disp Tetraploid
Awns area 0.376 1.000 1.415 3.763
Circularity index 1.188 0.065 0.959 0.807
Roundness 1.828 0.000 1.312 0.718
Perimeter 1.570 0.000 1.080 0.688
Stem length 3.500 0.000 1.320 0.377
xu2 3.928 0.000 1.336 0.340
L 3.500 0.000 1.320 0.377
xb2 4.437 0.000 1.331 0.300
yu2 4.275 0.000 2.491 0.583
ybm 1.081 0.248 0.695 0.643


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

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

Мы усредняли значения для каждого вида по 10 признакам, получив таблицу 20 на 10. Где каждому из 20 видов соответствует вектор из 10-ти признаков. Для этих данных построили матрицу корреляции и провели иерархический кластерный анализ. Синие квадраты на графике соответствуют тетраплоидам.
image

На построенном дереве, в общем, виды пшениц разделись на тетраплоидные и гексаплоидные. Гексаплоидные виды четко разделисись на два кластера среднеколосые T. macha, T.aestivum, T. yunnanense и длинноколосые T. vavilovii, T. petropavlovskyi, T. spelta. Исключение к гексаплоидным попал единственный дикий полиплоидный (тетраплоидный) вид T. dicoccoides.
В то время как в тетраплоидные виды попали гексаплоидные пшеницы с компактным типом колоса T. compactum, T. antiquorum и T. sphaerococcum и рукотворная изогенная линия АНК-23 мягкой пшеницы.

Пробуем CNN


image
Для решения задачи определения плоидности пшеницы по изображению колоса мы обучили сверточную нейронную сеть архитектуры EfficientNet B0 с предобученными на ImageNet весами. В качестве loss функции использовали CrossEntropyLoss; оптимизатор Adam; размер одного батча 16; изображения ресайзили до разрешения 224x224; скорость обучения меняли, согласно стратегии fit_one_cycle с начальным lr=1e-4. Обучали сеть в течении 10 эпох, накладывая случайным образом следующие аугментации: повороты на -20 +20 градусов, изменение яркости, контрастности, насыщенности, зеркальное отображение. Лучшею модель выбирали по метрике AUC, значение которой считалось в конце каждой эпохи.

В результате точность на отложенной выборке AUC=0.995, что соответствует accuracy_score=0.987 и ошибки равной 1.3%. Что является очень не плохим результатом.

Заключение


Эта работа является удачным примером того, как силами коллектива из 5 студентов и 2-ух кураторов в течении нескольких недель можно решить актуальную биологическую задачу и получить новые научные результаты.
Я хотел бы выразить благодарность всем участникам нашего проекта: Никита Прохошин, Алексей Приходько, Андреевич Евгений, Артем Пронозин, Анна Паулиш, Евгений Комышев, Михаил Генаев.
Николаю Петровичу Гончарову и Афонникову Дмитрию Аркадьевичу за предоставленный биологический материал и помощь в интерпретации результатов.
Математическому центру Новосибирского Государственного Университета и Институту Цитологии и Генетики СО РАН за организацию мероприятия и вычислительные мощности.
image

З Мы планируем подготовить вторую часть статьи, где расскажем про сегментацию колоса и выделении отдельных колосков.
Подробнее..

Из песочницы Оптимизация моделей в Tensorflow 1.x

12.11.2020 20:10:42 | Автор: admin

Tensorflow, хотя и сдаёт свои позиции в исследовательской среде, всё ещё остаётся популярным в практической разработке. Одна из сильнейших сторон TF, из-за которой он держится на плаву возможность оптимизации моделей для развертывания в условиях ограниченных ресурсов. Для этого существуют специальные фреймворки: Tensorflow Lite для мобильных устройств и Tensorflow Serving для промышленной эксплуатации. В Сети (и даже на Хабре) достаточно туториалов по их использованию. В этой статье мы собрали наш опыт оптимизации моделей без использования этих фреймворков. Мы рассмотрим некоторые методы и библиотеки, выполняющие поставленную задачу, опишем, как можно сэкономить пространство на диске и RAM, сильные и слабые стороны каждого подхода, а также некоторые неожиданные эффекты, с которыми мы столкнулись.


В каких условиях мы работаем


Одна из классических задач NLP тематическая классификация коротких текстов. Классификаторы представлены множеством разнообразных архитектур, начиная от классических методов типа SVC и заканчивая transformer-архитектурами типа BERT и его производных. Мы будем рассматривать CNN свёрточные модели.


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


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


Мы столкнулись с необходимостью выполнить оптимизацию модели на TF версии 1.x, сейчас официально считающейся устаревшей. Для TF 2.x многие из рассматриваемых техник либо неактуальны, либо интегрированы в стандартный API, и поэтому процесс оптимизации достаточно прост.


Давайте в первую очередь рассмотрим структуру нашей модели.


Как устроена TF-модель


Рассмотрим так называемую Shallow CNN сеть с одним сверточным слоем и несколькими фильтрами. Эта модель достаточно хорошо себя зарекомендовала для текстовой классификации поверх векторных представлений слов.



Для простоты будем использовать фиксированный предобученный набор векторных представлений размерностью v x k, где v размер словаря, k размерность эмбеддингов.


Наша модель содержит:


  • Embedding-слой, отображающий слова предложения в их векторные представления.
  • Несколько параллельных слоёв двумерных свёрток w x k. Например, набор окон (1, 1, 2, 3) будет соответствовать 4 параллельным сверткам, две из которых обрабатывают 1 слово, а другие по 2 и по 3 слова, как показано на рисунке.
  • Max-pooling по каждой свертке.
  • Объединяющий результаты пулинга единый длинный вектор, к которому добавлена dropout-регуляризация и softmax-слой для осуществления классификации.

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


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


Например, свёртка, обрабатывающая входную последовательность и продуцирующая 128 фильтров c w = 2 и k = 300 при нотации тензора ядра (фильтра) [filter_height, filter_width, in_channels, output_channels] это тензор, содержащий 2*300*1*128 = 76800 чисел формата типа float32, и соответственно, обладающий размером 76800*(32/8) = 307200 байт.


Для чего мы проводим эти подсчеты? При достаточно большом словаре (в нашем случае около 220 тыс. слов) размер слоя эмбеддингов размерностью 300 достигает примерно 265 МБ. Это неотъемлемая часть модели, и при использовании комплекта моделей потребление ресурсов может значительно вырасти.


В TF архитектура сети представляется в виде графа вычислений. Мы не будем подробно останавливаться на его устройстве (есть масса материалов на этот счет), однако отметим, что граф как правило компилируется отдельно от весов модели, то есть в базовом варианте у нас есть две сущности граф и веса (параметры модели), которые хранятся отдельно. Граф состоит из узлов (нод). В нашем случае граф вычислений имеет следующее строение:


Вычислительный граф


Как модель хранится на диске


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


Checkpoint


Чтобы сохранить чекпоинт, нужно обратиться к Saver API:


saver = tf.train.Saver(save_relative_paths=True)ckpt_filepath = saver.save(sess, "cnn.ckpt"), global_step=0)

Здесь параметр global_step указывает шаг, на котором было сохранено состояние модели, и дописывает этот шаг в название файла получаем файлы cnn-ckpt-0.


Тогда содержимое папки <model_path>/cnn_ckpt выглядит следующим образом:



Файл checkpoint текстовый и содержит путь к последнему чекпоинту модели. Состояние модели может записываться несколько раз в процессе обучения, и TF при считывании этого файла сразу найдёт путь к нужному чекпоинту. В нашем случае чекпоинт только один, поэтому проблемы накопления чекпоинтов у нас не возникло.


Файл .data представляет собой непосредственно значения весов модели, сохранённые на указанном шаге обучения. Заметим, что его размер около 800 МБ. Выше мы рассчитали, что большую часть весов модели занимает слой эмбеддингов (265 МБ). Остальной объём занимают параметры оптимизатора (значения первого и второго момента градиентов). Они обновляются каждый раз, когда градиенты пересчитываются.


Файл .index содержит является вспомогательным и содержит имена нод и их индексы.


Файл .meta является так называемым метаграфом это структура, содержащая граф вычислений (операции, переменные, их размерности и связи между ними), заданный в виде GraphDef, дополнительную информацию для загрузки модели и метаданные. Структуру графа можно описать в текстовом виде, и это вряд ли бы заняло больше пары килобайт. Но возникает вопрос почему тогда файл .meta занимает практически столько же места, сколько слой эмбеддингов? После профилирования мы обнаружили, что TF зачем-то сохраняет начальное состояние embedding-слоя. Возможно, это было бы полезно, но нам эта информация не нужна, поэтому, вообще говоря, эту ноду можно удалить из графа. Вы также можете проинспектировать ноды графа, например, так:


with tf.Session() as sess:   saver = tf.train.import_meta_graph('models/ckpt_model/cnn_ckpt/cnn.ckpt-0.meta')  # load meta   for n in tf.get_default_graph().as_graph_def().node:       print(n.name, n['attr'].shape)

С актуальной документацией по чекпоинтам можно ознакомиться здесь.


SavedModel


Формат, удобный для разворачивания и сервинга моделей. Является расширением концепции чекпоинта. Реализуется через API tf.saved_model. Взаимодействовать с сохранённой таким образом моделью можно как через tf.saved_model, так и через различные сервисы TF-экосистемы (TFLite, TensorFlow.js, TensorFlow Serving, TensorFlow Hub).


Сохранённая модель на диске выглядит следующим образом:



Файл saved_model.pb, по сути, содержит тот же метаграф, но отличается от .meta тем, что поддерживает хранение нескольких метаграфов (например, если нужно сохранить версию для последующего обучения и для инференса), а также предоставляет универсальный API, позволяющий взаимодействовать с моделью в различных фреймворках (и даже через CLI, то есть проверить работу вашей модели можно без дополнительного кода).


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


Основные методы оптимизации модели


Итак, мы имеем CNN-классификатор, написанный на TF версии 1.x, и необходимость при обучении и инфере работать с комплектом из нескольких моделей. Поэтому перед нами встал вопрос уменьшения объёма модели.


Мы заметили, что параметры нашей относительно небольшой свёрточной модели занимают около 1 ГБ, и рассмотрели следующие способы оптимизации:


  1. Заморозка графа
    Веса и структура модели экспортируются в один файл. Метод позволяет избавиться от элементов графа, не использующихся в инференсе, а также оптимизировать скорость обработки данных ( tools.optimize_for_inference ).
  2. Сохранение лёгкого чекпоинта
    Выборочное сохранение переменных графа. Фактически, мы удаляем данные, требующиеся только на стадии обучения узлы графа, связанные с оптимизацией и не входящие в tf.trainable_variables().
  3. Прунинг
    Обнуление весов, слабо влияющих на конечное предсказание. Очень эффективный метод, если наша модель содержит избыточное число параметров (напр. BERT).
  4. Квантизация
    Переход к вычислениям с более низкой точностью. Может применяться как во время, так и после обучения. Часто используется в сочетании с прунингом.
  5. Использование типов данных меньшей точности

Заморозка графа


Это способ сериализации, позволяющий сохранить в один файл сам граф, переменные и их веса в виде констант. Заморозка подразумевает удаление всех нод графа, которые не нужны для forward pass, то есть для непосредственного инференса модели. Следовательно, замороженную модель нельзя дообучить классическими способами. В нашем случае мы сократили размер модели с 1 ГБ до 265 МБ.


В TF 1.x эту операцию можно проделать различными способами, все они сводятся к следующим шагам.


Сначала нужно получить доступ к графу вычислений (загрузка с диска или из текущей сессии) и представление его в формате GraphDef:


graph = tf.get_default_graph()input_graph_def = graph.as_graph_def()

Затем непосредственная заморозка. Мы попробовали два метода: tf.python.tools.freeze_graph и tf.graph_util.convert_variables_to_constants. Оба метода принимают в том или ином виде граф (первый метод может загрузить уже сохранённый чекпоинт с диска) и список выходных нод (например, ['output/predictions']), на основе которого происходит вычисление, какие ноды нужны для инференса, а какие нужны только для обучения. При подаче одного и того же графа и списка выходных нод результат работы методов идентичен.


output_graph_def = graph_util.convert_variables_to_constants(self.sess, input_graph_def, output_node_names)

Наконец, граф нужно сериализовать.
freeze_graph() сериализует граф во время выполнения (имя файла, который будет записан, подаётся как аргумент). Результат graph_util.convert_variables_to_constants() необходимо дополнительно сериализовать:


with tf.io.gfile.GFile('graph.pb', 'wb') as f:    f.write(output_graph_def.SerializeToString())

Полученный файл весит всего 266 МБ, при этом эту модель можно загрузить на инференс следующим образом:


Развернуть код
# считываем GraphDef из файлаwith tf.io.gfile.GFile(graph_filepath, 'rb') as f:    graph_def = tf.GraphDef()    graph_def.ParseFromString(f.read())with tf.Graph().as_default() as graph:    # задаём входные плейсхолдеры    self.input_x = tf.placeholder(tf.int32, [None, self.properties.max_len], name="input_x")    self.dropout_keep_prob = tf.placeholder(tf.float32, name="dropout_keep_prob")    # заполняем текущий вычислительный граф значениями из считанного graph_def    input_map = {'input_x': self.input_x, 'dropout_keep_prob': self.dropout_keep_prob}    tf.import_graph_def(graph_def, input_map)

К любой ноде можно обращаться прямо в текущем графе, по умолчанию к названию нод добавляется префикс import:


predictions = graph.get_tensor_by_name('import/output/predictions:0')

И мы можем сделать предсказание:


feed_dict = {self.input_x: encode_sentence(sentence), self.dropout_keep_prob: 1.}sess.run(self.predictions, feed_dict)

При работе с замороженными графами мы столкнулись с некоторыми неожиданными эффектами, а именно:


  1. Задержка при первом предсказании. После того как граф поднимается в память и инициализируется в сессии, для получения предсказания достаточно вызывать sess.run(...). При том, что одно предсказание на CPU выполнялось примерно за 20 ms, для первого предсказания время выполнения доходило до ~2700 ms. Вероятно, это связано с кэшированием графа на устройстве. При загрузке из чекпоинта или из SavedModel такого эффекта не наблюдалось.
  2. Накопление занимаемой во время загрузки RAM. При загрузке модели на инференс из замороженного графа потребляется больше RAM, чем при загрузке из чекпоинта. При этом разница составляет те самые ~265 МБ, то есть память дублируется. Выглядит так, как будто TF не утилизирует память при загрузке из GraphDef оптимальным образом.
  3. Ещё одно наблюдение потребление RAM пустой сессией в разных версиях TF разное. В 1.15, последней версии TF 1.x, пустая сессия потребляет 118 MiB, тогда как в 1.14 всего 3 MiB.

Лёгкий чекпоинт


Основным преимуществом замороженного графа над чекпоинтом для нас была возможность удаления узлов графа, не используемых в инференсе. Но что если сохранением чекпоинта можно также гибко управлять и сохранять только необходимую информацию? На странице документации представлено описание доступного функционала модуля сохранения/восстановления TF-модели tf.train.Saver. Он обеспечивает возможность сделать две оптимизации, которые, подобно заморозке графа, позволяют убрать из чекпоинта всё лишнее:


  • Выборочное сохранение переменных графа
  • Выключение сохранения MetaGraph

Модуль tf.train.Saver позволяет при инициализации указать переменные которые мы хотим сохранить. Для инференса нам нужны только веса модели, таким образом данный функционал можно описать всего одной строкой кода:


saver = tf.train.Saver(var_list=tf.trainable_variables())

MetaGraph содержит информацию о структуре графа и позволяет автоматически строить граф при загрузке модели из чекпоинта. Однако мы можем продублировать код построения графа из обучения в инференсе, и тогда необходимость в сохранении meta данных пропадает. Отключить сохранение данных MetaGraph можно передав соответствующий флаг в метод save:


ckpt_filepath = saver.save(self.sess, filepath, write_meta_graph=False)

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


Прунинг


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


Существует несколько возможных вариантов реализации прунинга, которые работают в TF 1.x:


  • Grappler: cистема оптимизации графов tensorflow по умолчанию
  • Pruning API: реализация google-research
  • Graph Transform Tool: утилита оптимизации замороженного графа

Первое, что мы попробовали стандартные средства оптимизации графа tensorflow, реализуемые системой Grappler. Описание возможностей Grappler представлено на странице документации. Он работает из коробки и не требует никаких действий для активации, однако для настройки применяемых оптимизаций необходимо использовать метод set_experimental_options. Мы провели эксперименты с включенным и выключенным прунингом, оценивая в ходе эксперимента размер сжатого с помощью zip чекпоинта. Ожидалось, что размер zip архива в случае применения прунинга будет меньше за счёт большего количества нулевых значений в исходном файле, однако на деле результат оказался идентичным для обоих экспериментов. На данном этапе было решено отказаться от Grappler и попробовать другие реализации прунинга.


Реализация прунинга от google-research заключается в добавлении в вычислительный граф дополнительных переменных mask и threshold, соответствующих свёрточным слоям нейронной сети. В процессе обучения в этих переменных накапливается информация об использовании соответствующих весов модели для верного предсказания. После обучения модели производится прунинг, который использует информацию, накопленную в процессе обучения в переменных mask и threshold, для зануления весов, дающих наименьший вклад в верное предсказание модели, при этом уровень разреженности указывается в качестве гиперпараметра. Дополнительные переменные после прунинга удаляются и результат сохраняется в виде разреженного замороженного графа.


Были проведены эксперименты аналогичные экспериментам с Grappler, результат также оказался аналогичным. Возник вопрос: как проверить работает прунинг или нет? Если работает, почему размер не изменяется? Для ответа на этот вопрос мы сохранили два замороженных графа с прунингом и без, при этом коэффициент разреженности установили 0.99 для наглядности. Далее, используя утилиту mc, посмотрели содержимое файлов замороженного графа в hex формате:


Скриншоты hex-редактора


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


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


Квантизация


Метод, который помогает сильно сократить количество памяти на диске и в памяти. В нашем случае для квантизации использовался Graph transform tool.


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


Более сложная оптимизация quantize_nodes фактически преобразует все выполняемые с весами вычисления в 8-битные с преобразованиями с плавающей запятой до и после каждого вычисления. Это может помочь значительно ускорить вывод.


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


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


Использование типов данных меньшей точности


Использование типов данных меньшей точности это один из самых очевидных, но достаточно действенных подходов для уменьшения объема модели в оперативной памяти. При инициализации массивов по умолчанию используются 64 (32) разрядные числа в зависимости от операционной системы, но при векторизации данных такая точность может быть избыточной.


После перехода к новым типам данных мы получили заметные на больших датасетах сокращения в потреблении RAM на Ubuntu (где numpy по дефолту использует int64) за счет изменения типа данных на этапе индексации слов и меток лейблов. Поскольку размер словаря составлял 220 тысяч слов, то для индексации использовался int32, а для массива лейблов int16. В таблице можно увидеть результаты этой оптимизации в зависимости от количества классов и примеров на класс при обучении модели.



Как дополнительный эксперимент над типами данных мы пробовали менять точность внутри tf-графа. Точность каждого узла в данном эксперименте понижалась до float16. При этом, с исходными параметрами обучения, мы получили совсем незначительные изменения в потреблении памяти (меньше 10%), но значительно увеличилось время обучения (до 10 раз). На первый взгляд кажется, что этот эффект связан с неправильными настройками оптимизатора, хотя просто изменения epsilon и learning_rate не помогли. Поэтому вопрос, с чем именно связано увеличение времени обучения, остается открытым.


Влияние оптимизации на RAM


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



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


QA-секция


Q: У вашей модели тяжеленный эмбеддинг-слой, вы пробовали с ним что-нибудь сделать?


A: Основной вес нашей модели занимает слой эмбеддингов, поэтому было логично также обратить внимание на уменьшение размерности этого слоя. Для обучения эмбеддингов мы используем word2vec. После проведения серии экспериментов с различными значениями параметров (размер векторов, количество эпох, min count, learning rate), мы пришли к размеру словаря в 220 тысяч слов (размер слоя эмбедддингов 265 MB) без ухудшения качества работы нашей CNN, тогда как в более ранней реализации размер словаря составлял 439 тысяч слов (510 MB).


Уменьшение размера эмбеддинг-слоя в два раза, конечно, не может не радовать, и всё же он по-прежнему является очень тяжелым. Поэтому мы обратили своё внимание на эмбеддинги, использующие сабворды (или кусочки слов). В таких эмбеддингах словарь состоит не из слов, а из их частей. Мы использовали токенайзер YouTokenToMe, который на основе входного текста сначала добавляет в словарь только отдельные символы, потом пары символов, т.е. биграммы, затем триграммы и т.д., пока в конце концов не начинает добавлять целые слова. Такие кусочки слов выбираются на основе анализа частотности появления во входном тексте определенных наборов символов. При этомы мы сами можем ограничить размер словаря, потому что все целые слова, которые не попали в словарь, могут быть составлены из имеющихся кусочков слов. Экспериментально мы пришли к размеру словаря в 30 тысяч сабвордов (37 MB) без существенных потерь качества, а время обучения уменьшилось в 3.7 раза на CPU и в 2.6 раза на GPU. Использование такого подхода позволило не только значительно сократить размер словаря (что привело к уменьшению занимаемой памяти и ускорению времени работы), но и решить проблему OOV-слов.


Q: Окей, модели сжали, но они теперь годятся только для инференса?


A: Нет, мы умеем дообучать замороженный граф и легкий чекпоинт.


Для дообучения замороженного графа:


Шаг 1. Восстанавливаем граф в памяти:


with tf.gfile.GFile(path_to_pb, 'rb') as f:    graph_def = tf.GraphDef()    graph_def.ParseFromString(f.read())with tf.Graph().as_default() as graph:    tf.import_graph_def(graph_def, name='')    return graph

Шаг 2. "Вытаскиваем" веса из графа:


sess.run(restored_variable_names)

Шаг 3. Удаляем всё, кроме вытащенных из памяти весов.
Шаг 4. Перестраиваем граф, передавая веса в соответствующие переменные, например:


tf.Variable(tensors_to_restore["output/W:0"], name="W")

Для дообучения легкого чекпоинта потребуется восстановить граф, используя код его построения из обучения.


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


Сжатые остальными описанными способами модели мы дообучать не пробовали, но теоретически с этим не должно возникнуть каких-либо проблем.


Q: Есть ли какие-то еще способы уменьшения оптимизации, которые вы не рассмотрели?


A: У нас есть несколько идей, до реализации которых мы так и не дошли. Во-первых, constant folding это свёртывание подмножества узлов графа, предвычисление значения частей графа, слабо зависящих от входных данных. Во-вторых, в нашей модели хорошим решением кажется применить прунинг эмбеддингов.

Подробнее..

Перевод Временные сверточные сети революция в мире временных рядов

19.09.2020 18:15:27 | Автор: admin
Перевод статьи подготовлен в преддверии старта курса Deep Learning. Basic.



В этой статье мы поговорим о последних инновационных решениях на основе TCN. Для начала на примере детектора движения рассмотрим архитектуру временных сверточных сетей (Temporal Convolutional Network) и их преимущества перед традиционными подходами, такими как сверточные нейронные сети (CNN) и рекуррентные нейронные сети (RNN). Затем поговорим о последних примерах применения TCN, включая улучшение прогнозирования трафика, локализатор и детектор звука и вероятностное прогнозирование.

Краткий обзор TCN


В фундаментальной работе Леа и др. (2016) было впервые предложено использовать временные сверточные сети для сегментации действий на основе видео. Обычно процесс разбивается на два этапа: во-первых, вычисление низкоуровневых признаков с использованием (чаще всего) CNN, которая кодирует пространственно-временную информацию, и во-вторых, ввод низкоуровневых признаков в классификатор, который получает высокоуровневую временную информацию с помощью (чаще всего) RNN. Главным недостатком такого подхода является потребность в двух отдельных моделях. TCN предлагает унифицированный подход, чтобы покрыть оба уровня информации по принципу иерархии.

На рисунке ниже представлена структура энкодера-декодера, информацию по архитектуре можно найти в первых двух ссылках в конце статьи. Наиболее критические вопросы решаются следующим образом: TCN может взять ряд любой длины и на выходе получить ту же самую длину. Казуальная (casual) свертка используется там, где присутствует полностью сверточная одномерная архитектура сети. Ключевой характеристикой является то, что выходное значение в момент времени t свертывается только с теми элементами, которые произошли по времени до него.



Шумиха вокруг TCN дошла даже до журнала Nature, где недавно появилась публикация работы Яна и др. (2020) об использовании TCN в задачах прогнозирования погоды. В своей работе авторы провели эксперимент по сравнению TCN и LSTM. Одним из результатов стал вывод о том, что TCN хорошо справляется с задачами прогнозирования временных рядов.



В следующих разделах представлены реализация и расширение классической TCN.

Улучшение прогнозирования трафика


Сервисы райдшеринга и онлайн-навигации могут улучшить прогнозирование трафика и изменить пребывание на дорогах к лучшему. Уменьшение количества пробок, уменьшение загрязнения окружающей среды, безопасное и быстрое вождение вот всего несколько целей, которых можно достичь за счет улучшения прогнозирования дорожного движения. Поскольку эта проблема основывается на данных в реальном времени, необходимо использовать накопленные данные о трафике. По этой причине Дай и др. (2020) недавно представили гибридную пространственно-временную графовую сверточную сеть (Hybrid Spatio-Temporal Graph Convolutional Network, H-STGCN). Основная идея заключается в том, чтобы использовать преимущества отношения кусочно-линейной скользящей плотности потока и преобразовывать предстоящий объем трафика в его эквивалент времени движения. Одним из наиболее интересных подходов, которые они использовали в своей работе, является свертка графа для получения временной зависимости. Составная матрица смежности отражает врожденные характеристики аппроксимации трафика (чтобы узнать больше, читайте статью Ли 2017 года). В следующей архитектуре представлены четыре модуля для описания всего процесса прогнозирования.



Локализация и обнаружение звуковых событий


Область локализации и обнаружения звуковых событий (SELF) продолжает расти. В автономной навигации понимание среды играет большую роль. Гирджис и др. (2020) недавно предложили новую архитектуру звуковых событий SELF-TCN. Группа исследователей утверждает, что их фреймворк превосходит современные решения в этой области, сокращая время обучения. В их SELDnet (архитектура представлена ниже) многоканальная аудиозапись, дискретизированная на частоте 44,1 кГц, извлекает, применяя кратковременное преобразование Фурье, фазу и величину спектра и выделяет их в виде отдельных входных признаков. Затем соединяются сверточные блоки и рекуррентные блоки (двунаправленные GRU), а затем идет полностью соединенный блок. На выходе из SELDnet можно получить обнаружение звуковых событий и направление, откуда пришел звук.



И для того, чтобы превзойти существующее решение, авторы представили SELD-TCN:



Поскольку расширенные свертки позволяют сети обрабатывать различные входные данные, может потребоваться более глубокая сеть (на которую будут влиять нестабильные градиенты во время обратного распространения ошибки). Авторы исследования эту проблему смогли решить, адаптировав архитектуру WaveNet (Дарио и др., 2017). Они показали, что рекуррентные слои не требуются для задач SELD, и при этом смогли определить время начала и окончания активных звуковых событий.

Вероятностное прогнозирование


Новый фреймворк, разработанный Ченом и др. (2020) может быть применен для оценки плотности вероятности. Прогнозирование временных рядов улучшает многие сценарии принятия бизнес-решений (например, управление ресурсами). Вероятностное прогнозирование позволяет извлекать информацию из исторических данных и минимизировать неопределенность будущих событий. Когда задача прогнозирования состоит в том, чтобы предсказать миллионы связанных временных рядов (как в розничном бизнесе), требуются непомерно большие трудовые и вычислительные ресурсы для оценки параметров. Чтобы разрешить эти трудности, авторы предложили систему оценки плотности и прогнозирования на основе CNN. Их структура может выучить скрытую корреляцию между рядами. Научная новизна в их работе заключается в предложенной ими глубокой TCN, представленной в их архитектуре:



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

Заключение


В этой статье мы рассмотрели последние работы, связанные с временными сверточными сетями, которые превосходят по тем или иным характеристикам классические подходы CNN и RNN в решении задач временных рядов.

Источники


  • Lea, Colin, et al. Temporal convolutional networks: A unified approach to action segmentation. European Conference on Computer Vision. Springer, Cham, 2016.
  • Lea, Colin, et al. Temporal convolutional networks for action segmentation and detection. proceedings of the IEEE Conference on Computer Vision and Pattern Recognition. 2017.
  • Yan, Jining, et al. temporal convolutional networks for the Advance prediction of enSo. Scientific Reports 10.1 (2020): 115.
  • Li, Yaguang, et al. Diffusion convolutional recurrent neural network: Data-driven traffic forecasting. arXiv preprint arXiv:1707.01926 (2017).
  • Rethage, Dario, Jordi Pons, and Xavier Serra. A wavenet for speech denoising. 2018 IEEE International Conference on Acoustics, Speech and Signal Processing (ICASSP). IEEE, 2018.
  • Chen, Yitian, et al. Probabilistic forecasting with temporal convolutional neural network. Neurocomputing (2020).
  • Guirguis, Karim, et al. SELD-TCN: Sound Event Localization & Detection via Temporal Convolutional Networks. arXiv preprint arXiv:2003.01609 (2020).




Читать ещё:


Подробнее..

Искусственный интеллект в сети ЦОД опыт Huawei

05.12.2020 18:16:42 | Автор: admin
По следам своего доклада на конференции AI Journey, прошедшей 4 декабря, хочу рассказать вам, как правильное применение ИИ-систем в управлении сетью позволяет строить на базе решений Huawei современные центры обработки данных без узких мест и без потери пакетов. Выгоды от таких решений особенно наглядны, когда в ЦОДе эксплуатируются хранилища All-Flash, проводится обучение нейросетей или выполняются высокопроизводительные вычисления на GPU.





Трансформация ЦОД


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

Это первый шаг, за которым следует дальнейшее повышение эффективности работы дата-центра путём автоматизации, оркестрации и перехода в режим гибридного облака. И самый дальний предел трансформации из досягаемых на сегодняшний день внедрение систем искусственного интеллекта.



Решения Huawei для каждого этапа трансформации


На каждой стадии в зависимости от IT-зрелости заказчика компания Huawei предлагает собственные решения, призванные обеспечить наилучший результат модернизации без лишних трат. Сегодня хотелось бы подробнее поговорить как раз о вишенке на торте системах ИИ в современных ЦОДах.



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

Буквально несколько лет назад технологии хранения данных на твердотельных дисках стали широко доступны, а производительность центральных процессоров продолжает расти. Благодаря этому хранилища и вычислительные узлы перестали быть главными причинам задержек. А вот сеть ЦОД долгое время оставалась в структуре дата-центров своего рода младшим братом.

Производители пытались решить проблему по-разному. Кто-то выбирал для построения сети лицензированные технологии InfiniBand (IB). Сеть получалась специализированной и способной решать только узкопрофильные задачи. Кто-то предпочитал строить сетевые фабрики на протоколах Fibre Channel (FC). Оба подхода имели свои ограничения: либо пропускная способность сети оказывалась относительно скромной, либо общая цена решения кусалась, что вдобавок усугублялось зависимостью от одного вендора.

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



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

В последнее время FC шагнул вперёд к автономным сетям хранения данных, но продолжает нести в себе ограничения производительности. Сейчас мейнстрим шестое поколение технологии, позволяющее добиться пропускной способности 32 Гбит/с, начинают внедряться и решения 64 Гбит/с. При этом с помощью Ethernet мы уже сегодня, используя таблицы приоритета, можем получить 100, 200 и даже 400 Гбит/с до сервера.



Дополнительные возможности сети ЦОД приобретают особое значение в мире, где твердотельные накопители со скоростными интерфейсами занимают всё большую долю рынка, вытесняя классические шпиндельные. Huawei стремится к тому, чтобы дать СХД на основе SSD полностью раскрыть свой потенциал.



Сеть ЦОД следующего поколения


Небольшой пример того, как мы это делаем. На схеме изображена одна из наших систем хранения данных, признанных самыми быстрыми в мире. Здесь же показаны наши серверы, построенные на архитектуре x86 или ARM и демонстрирующие производительность на уровне ожиданий крайне требовательных клиентов. В ЦОДах на основе этих решений нам удаётся добиться сквозной задержки не более 0,1 мс. Получить такой результат нам помогает использование новых application-технологий.

Классические технологии, применяемые в СХД, были ограничены, в частности, достаточно высокими задержками, которые обуславливались шиной SAS. Переход на новые протоколы, такие как NVMe, позволил значительно улучшить этот параметр, и вместе с тем ограничивающим производительность фактором становилась сама сеть.



Рассмотрим в рамках этого же примера использование сетей с дополнительными лицензируемыми алгоритмами. Они позволяют оптимизировать сквозную задержку, существенно повысить пропускную способность сети и увеличить количество операций ввода-вывода на единицу времени. Такой подход помогает избежать двойной закупки, подчас необходимой для достижения необходимых параметров производительности, а совокупная экономия (в измерении TCO) при внедрении новой сети достигает 1840% в зависимости от моделей применяемого оборудования.



Что же это за вау-алгоритмы?


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

Используя в коммутаторах дополнительные чипсеты машинного обучения, мы научили сеть работать в режиме, позволяющем строить интеллектуальные сети ЦОД без потерь пакетов (мы назвали его iDCN).



Как достигается интеллектуальная оптимизация? Те, кто занимается нейросетями, легко найдут на схеме знакомые элементы и механизмы training / inference. Наличие в наших решениях встроенных моделей сочетается со способностью обучаться на конкретной сети.



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



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



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



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



От теории к практике


Применяя такие алгоритмы и работая на уровне всей сети, а не отдельных её срезов, мы решаем все основные проблемы производительности. Уже есть интересные кейсы внедрения и использования подобных технологий в банковской сфере. Востребованы эти механизмы и в других отраслях, например среди операторов связи.



Обратимся к результатам открытых тестов. Независимая лаборатория The Tolly Group протестировала наше решение и сравнила его с решениями Ethernet и IB других производителей. Как показали испытания, производительность продукта Huawei эквивалентна возможностям IB и на 27% превосходит Ethernet-продукты других крупных производителей.



Максимальную эффективность сеть ЦОД без потерь демонстрирует в нескольких сценариях, как то:

  • обучение ИИ;
  • централизованное хранение;
  • распределённое хранение;
  • высокопроизводительные вычисления на GPU.




В заключение рассмотрим один из сценариев применения интеллектуальной сети ЦОД. Многие заказчики используют распределённые системы хранения (SDS). Интегрируя между собой программные СХД разных производителей с помощью нашего решения, можно добиться на 40% более высокой производительности, чем без него. А значит, когда известен требуемый уровень производительности вашей SDS, его можно добиться, используя на 40% меньше серверов.

***


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

Машинное обучение. Нейронные сети (часть 3) Convolutional Network под микроскопом. Изучение АПИ Tensorflow.js

18.09.2020 12:15:01 | Автор: admin

Смотрите также:

  1. Машинное обучение. Нейронные сети (часть 1): Процесс обучения персептрона

  2. Машинное обучение. Нейронные сети (часть 2): Моделирование OR, XOR с помощью TensorFlow.js

В предыдущих статьях, использовался только один из видов слоев нейронной сети полносвязанные (dense, fully-connected), когда каждый нейрон исходного слоя имеет связь со всеми нейронами из предыдущих слоев.

Чтобы обработать, например, черно-белое изображение размером 24x24, мы должны были бы превратить матричное представление изображения в вектор, который содержит 24x24 =576 элементов. Как можно вдуматься, с таким преобразованием мы теряем важный атрибут взаимное расположение пикселей в вертикальном и горизонтальном направлении осей, а также, наверное, в большинстве случаев пиксел, находящийся в верхнем левом углу изображения вряд ли имеет какое-то логически объяснимое влияние на пиксел в нижнем правом углу.

Для исключения этих недостатков для обработки изображений используют сверточные слои (convolutional layer, CNN).

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

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

Рисунок 1 Принцип работы последовательно подключенных сверточных слоев, с выделением характерных признаков на каждом из уровней. Каждый следующий из набора последовательно подключенных CNN слоев извлекает более сложные паттерны, базируясь на ранее выделанных.Рисунок 1 Принцип работы последовательно подключенных сверточных слоев, с выделением характерных признаков на каждом из уровней. Каждый следующий из набора последовательно подключенных CNN слоев извлекает более сложные паттерны, базируясь на ранее выделанных.

1. Сверточные слои нейронной сети (convolutional layer)

Основным элементом CNN является фильтр (матричной формы), который продвигается вдоль изображения c шагом в один пиксел (ячейку) вдоль горизонтальной и вертикальной осях, начиная от левого верхнего угла и заканчивая нижним правым. На каждом шаге CNN выполняет вычисление с целью оценки на сколько похожа часть изображения попавшиеся в окно фильтра с паттерном самого фильтра.

Предположим, что мы имеем фильтр размерностью 2x2 (матрица K) и он делает проекцию на исходное изображение, которая обязательно тоже имеет размерность 2x2 (матрица N), тогда значение выходного слоя вычисляется следующим образом:

\left[\begin{matrix}n_{11}&n_{12}\\n_{21}&n_{22}\\\end{matrix}\right]\ast\left[\begin{matrix}k_{11}&k_{12}\\k_{21}&k_{22}\\\end{matrix}\right]=n_{11}k_{11}+n_{12}k_{12}+n_{21}k_{21}+n_{22}k_{22}

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

Обратите внимание, что данное выражение имеет похожую форму записи операции суммирования в полносвязанные слоях (fully-connected, dense layers):

{sum=\ \vec{X}}^T\vec{W}=\sum_{i=1}^{n=4}{x_iw_i}=x_1w_1+x_2w_2+x_3w_3+x_4w_4

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

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

Рисунок 2 Вычисление внутри сверточных слоевРисунок 2 Вычисление внутри сверточных слоев

Размерность ядра фильтра (kernel size) обычно выбирают квадратной формы и с нечетным количеством элементов вдоль осей матрицы 3, 5, 7.

Если мы имеем форму ядра фильтра (kernel) [kh, kw], а входное изображение размерностью [nh, nw], то размерность выходного сверточного слоя будет (рисунок 3):

c_w=n_w-k_w+1; c_h=n_h-k_h+1Рисунок 3 Принцип формирования сверточного выходного слоя с размерностью ядра фильтра [3,3]Рисунок 3 Принцип формирования сверточного выходного слоя с размерностью ядра фильтра [3,3]

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

Чтобы избежать потери разрешений выходных изображений, в настройках сверточных слоев используют дополнительный параметр отступ (padding). Это расширяет исходное изображения по краям, заполняя эти ячейки нулевыми значениями. Предположим, что мы добавляем ph и pw ячеек к исходному изображению, тогда размер выходного сверточного слоя будет:

c_w=n_w+p_w-k_w+1; c_h=n_h+p_h-k_h+1

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

p_w=k_w-1; p_h=k_h-1Рисунок 4 Включение отступов в исходное изображения для сохранения той же размерности для выходного сверточного слояРисунок 4 Включение отступов в исходное изображения для сохранения той же размерности для выходного сверточного слоя

Как вы уже может обратили внимание - сверочный фильтр начинает свое движение с верхнего левого угла изображения и продвигается вдоль горизонтальной и вертикальной осей на одну ячейку вдоль направления движения. Однако иногда для снижения времени вычисления или же для уменьшения размерности выходного слоя, перемещение сверточного слоя может происходить вдоль направления движения больше чем на одну ячейку (stride). Это еще один из параметров сверточного слоя шаг перемещения (stride).

Рисунок 5 Передвижение сверточного слоя с шагом (stride) больше единицыРисунок 5 Передвижение сверточного слоя с шагом (stride) больше единицы

Предполагая, что размер шага вдоль горизонтальной и вертикальной осей равны соответственно sw, sh, тогда размер выходного сверточного слоя составит:

c_w=\left \lfloor (n_w+p_w-k_w+s_w)/s_w \right \rfloor; c_h=\left \lfloor (n_h+p_h-k_h+s_h)/s_h \right \rfloor

Так же стоит отметить, что сверточный слой может содержать один и более фильтров (каждый фильтр это аналог скрытого слоя с нейронами в полносвязанном слое нейронной сети). Каждый фильтр будет ответственен за извлечение из изображения своих специфичных паттернов (признаков). Представим, что на вход первого сверточного слоя (CONV1) было подано изображение размерностью 9x9x1 (изображение с одним цветовым каналом черно-белое изображение), а сверточный слой имеет 2 фильтра с шагом перемещения ядра 1x1 (stride) и отступ (padding) подобран таким образом, чтобы выходной слой сохранял туже размерность, что и входной. Тогда размерность выходного слоя будет 9x9x2 где 2 это количество фильтров (см. рисунок 6). На следующем шаге в CONV2 сверточном слое, обратите внимание, что при задании размерности фильтра 2x2, его глубина определяется глубиной входного слоя, которой равен 2, тогда ядро будет размерностью 2x2x2. Выходной слой после второго сверточного слоя (CONV2) тогда будет 9x9x4, где 4 количество фильтров в сверточном слое.

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

Обратите тут внимание, что во всех фреймворках мы будем задавать kw и kh для фильтра, однако если при этом этот на сверточный слой было подано изображение размерностью nw x nh x nd, где nd - количество цветовых каналов изображения или же количество характеристик, извлеченных на предыдущем сверточном слое, то размерность фильтра будет kw x kh x nd (смотри рисунок 6, CONV2).

На рисунке 7 изображен подход при вычислениях, если на сверточный слой подано цветное изображение с тремя каналами RGB, а сверточный слой задан матрицей 3x3. Как было указано выше, так как глубина входного изображения равна трем (3 канала), то фильтр будет иметь размерность 3x3x3.

Риунок 7 - Вычисления в сверточном слое, если входное изображение имеет три канала RGBРиунок 7 - Вычисления в сверточном слое, если входное изображение имеет три канала RGB

АПИ TensorFlow.js для сверточного слоя

Для создания сверточного слоя, необходимо воспользоваться следующим методом: tf.layers.conv2d, который принимает один аргумент это объект параметров, среди которых важны к рассмотрению следующие:
- filter number число фильтров в слое

- kernelSize number | number[] размерность фильтра, если задана number, то размерность фильтра принимает квадратную форму, если задана массивом то высота и ширина могут отличаться

- strides number | number[] - шаг продвижения, не обязательный параметр и по умолчанию задан как [1,1], в горизонтальном и вертикальном направлениях окно фильтра будет продвигаться на одну ячейку.

- padding same, valid настройка нулевого отступа, по умолчанию valid

Рассмотрим режимы задания нулевого отступа.

Режим 'same'

Сверточный слой будет использовать нулевые отступы при необходимости, при котором выходной слой будет всегда иметь размерность, которая вычисляется делением входной ширины (длины) на шаг передвижения фильтра (stride) с округлением в большую сторону. Например, входная ширина слоя вдоль одной из осей - 11 ячеек, шаг перемещения окна фильтра 5, тогда выходной слой будет иметь размерность 13/5=2.6, с учетом округления в большое сторону это будет 3 (рисунок 8).

Рисунок 8 Режим работы valid и same для отступов в фреймворках при kernelSize=6 и strides=5.Рисунок 8 Режим работы valid и same для отступов в фреймворках при kernelSize=6 и strides=5.

В случае если stride=1, то размерность выходного слоя будет равна размерности входного слоя (рисунок 9), фреймворк добавит столько нулевых отступов, сколько необходимо (рисунок 8).

Рисунок 9 Режим работы valid и same для отступов в фреймворке при kernelSize=3 и strides=1Рисунок 9 Режим работы valid и same для отступов в фреймворке при kernelSize=3 и strides=1

Режим 'valid'

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

Практическое использование сверточного слоя в TensorFlow.js

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

- для извлечения вертикальных линий:

\left[\begin{matrix}1&0&-1\\1&0&-1\\1&0&-1\\\end{matrix}\right]

- для извлечения горизонтальных линий:

\left[\begin{matrix}1&1&1\\0&0&0\\-1&-1&-1\\\end{matrix}\right]

В первую очередь понадобится АПИ, который преобразует загруженное изображение в тензор, для этого будет использован tf.browser.fromPixels. Первым аргументом является источник изображения, это может быть указатель на img или canvas элемент в разметке.

<img src="./sources/itechart.png" alt="Init image" id="target-image"/><canvas id="output-image-01"></canvas><script>   const imgSource = document.getElementById('target-image');   const image = tf.browser.fromPixels(imgSource, 1);</script>

Далее, соберем модель, которая состоит из одного сверточного слоя, содержащей один фильтр размерностью 3x3, с режимом отступа same и активационной функцией relu:

const model = tf.sequential({    layers: [        tf.layers.conv2d({            inputShape: image.shape,            filters: 1,            kernelSize: 3,            padding: 'same',            activation: 'relu'        })    ]});

Данная модель в качестве входных параметров принимает тензор формой [NUM_SAMPLES, WIDTH, HEIGHT,CHANNEL], однако tf.browser.fromPixel возвращает тензор размерностью [WIDTH, HEIGHT, CHANNEL], то поэтому нам надо модифицировать форму тензора и добавить еще одну ось там где располагаются образцы изображений (в нашем случае будет только один, так как одно изображение):

const input = image.reshape([1].concat(image.shape));

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

model.getLayer(null, 0).setWeights([    tf.tensor([         1,  1,  1,         0,  0,  0,        -1, -1, -1    ], [3, 3, 1, 1]),    tf.tensor([0])]);

Далее пропустим исходное изображение через модель, а также нормализуем выходной тензор таким образом, чтобы все значения были в промежутке 0-255, а также уберем ось NUM_SAMPLES:

const output = model.predict(input);const max = output.max().arraySync();const min = output.min().arraySync();const outputImage = output.reshape(image.shape)    .sub(min)    .div(max - min)    .mul(255)    .cast('int32');

Чтобы отобразить тензор на плоскости canvas, воспользуемся функцией tf.browser.toPixels:

tf.browser.toPixels(outputImage, document.getElementById('output-image-01'));

Тут приведен результат работы с применением двух разных фильтров:


2. Подвыборочный слой (pooling layer)

Обычно для обработки изображения, последовательно подключается несколько сверточных слоев (обычно это десять или даже сотня слоев), при этом каждый сверточный слой увеличивает размерность выходного изображения, что в свою очередь приводит к увеличению времени обучения такой сети. Для того, чтобы его снизить, после сверточных слоев применяют подвыборочный слой (pooling layer, subsample layer), которые уменьшают размерность выходного изображения. Обычно применяется MaxPooling операция.

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

В целом преобразования в этом слое похожи на преобразования в сверточном слое. Этот слой также имеет ядро (kernel) определенного размера, который скользит вдоль изображения с задаваемым шагом (stride) и в отличии от сверточного слоя в фреймворках по умолчанию он равен не 1x1, а равен размеру задаваемого ядра. В выходном изображении, каждая ячейка будет содержать максимальное значение пикселов в проекции ядра на определённом шаге (см. рисунок 10).

Рисунок 10 Преобразование в подвыброчном слоеРисунок 10 Преобразование в подвыброчном слое

Как видим, при исходном изображении размерностью 4x4, подвыборочный слой с размерностью фильтра 2x2 и размерностью шага (stride) по умолчанию, равному размерностью фильтра 2x2, выходное изображение имеет размерность в два раза меньше входного.

Также покажем наглядно, что этот слой сглаживает пространственные смещения (рисунок 11) во входном слое. Обратите внимание, что на втором рисунке изображение смещено на один пиксел влево относительно первого изображения, тем не менее выходные изображения для обоих случаев после MaxPooling слоев идентичные. Это еще называют пространственной инверсией (translation invariance). На третьем рисунке, выходной слой уже не идентичен первым двум, но тем не менее пространственная инверсия составляет 50%. Тут в примере рассматривались смещения вдоль вертикальной, горизонтальных осях, но MaxPooling также толерантен к небольшим вращением изображениям также.

Рисунок 11 Сглаживание пространственных смещений после MaxPooling слояРисунок 11 Сглаживание пространственных смещений после MaxPooling слоя

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

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

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

АПИ TensorFlow.js для подвыборочного слоя (pooling layer)

В зависимости от слоя вы можете выбрать или tf.layers.maxPooling2d или tf.layers.averagePooling2d. Оба метода имеют одинаковую сигнатуру и принимают один аргумент объект параметров, среди которых важны к рассмотрению следующие:

- poolSize number | number[] размерность фильтра, если задана number, то размерность фильтра принимает квадратную форму, если задана массивом то высота и ширина могут отличаться

- strides number | number[] - шаг продвижения, не обязательный параметр и по умолчанию имеет туже размерность, что и заданный poolSize.

- padding same, valid настройка нулевого отступа, по умолчанию valid

Подробнее..

Эксперимент в распознавании рукописных текстов на кириллице

16.12.2020 14:07:22 | Автор: admin

Введение

Распознавание рукописного текста (англ. Handwritten Text Recognition, HTR) - это автоматический способ расшифровки записей с помощью компьютера. Оцифрованный текст рукописных записей позволило бы автоматизировать бизнес процессы множества компаний, упростив работу человека. В данной работе рассматривается модель распознавания рукописного текста на кириллице на основе искусственной нейронной сети. В исследовании использовалась система SimpleHTR разработана Гаральдом, а также LineHTR, расширенной версией системыSimple HTR. Подробнее о SimpleHTR можно почитать здесь.

Датасет

В этом разделе опишу два типа наборов данных: Первый набор данных содержит рукописные цитаты на кириллице. Он содержит 21 000 изображений из различных образцов почерка (названия стран и городов). Мы увеличили этот набор данных для обучения, собрав 207 438 изображений из доступных форм или образцов.

Второй HKR для рукописной казахско-русской базы данных состоял из отдельных слов (или коротких фраз), написанных на русском и казахском языках (около 95% русского и 5% казахского слова/предложения, соответственно). Обратите внимание, что оба языка являются кириллическими написаны и разделяют одни и те же 33 символа. Кроме этих персонажей, в казахском алфавите есть еще 9 специфических символов. Некоторые примеры набора данных HKR показаны ниже:

Некоторые образцы набора данныхНекоторые образцы набора данных

Этот окончательный набор данных был затем разделен на обучающие (70%), валидация (15%) и тестовые (15%) наборы данных. Сам тестовый набор данных был разделен на два субданных (по 7,5% каждый): первый набор данных был назван TEST1 и состоял из слов, которые не были включены в обучающий и проверочный наборы данных; другой субдатасет был назван TEST2 и состоял из слов, которые были включены в обучение набор данных, но полностью различные стили почерка. Основная цель разбиения тестового набора данных на наборы данных TEST1 и TEST2 нужно было проверить разница в точности между распознаванием невидимых слов и слов, видимых на стадии обучения, но с невидимыми стилями почерка.

SimpleHTR модель

Предлагаемая система использует ANN, при этом для извлечения объектов используются многочисленные слои CNN с входной фотографии. Затем выход этих слоев подается в RNN. RNN распространяет информацию через последовательность. Вывод RNN содержит вероятности для каждого символа в последовательности. Для прогнозирования конечного текста реализуются алгоритмы декодирования в выход RNN. Функции CTC отвечают за декодирование вероятностей в окончательный текст. Для повышения точности распознавания декодирование может также использовать языковую модель. CTC используется для получения знаний; выход RNN представляет собой матрицу, содержащую вероятности символов для каждого временного шага. Алгоритм декодирования CTC преобразует эти символические вероятности в окончательный текст. Затем, чтобы повысить точность, используется алгоритм, который продолжает поиск слов в словаре. Однако время, необходимое для поиска фраз, зависит от размеров словаря, и он не может декодировать произвольные символьные строки, включая числа.

Операции: CNN: входные изображения подаются на слои CNN. Эти слои отвечают за извлечение объектов. Есть 5х5 фильтры в первом и втором слоях и фильтры 3х3 в последних трех слоях. Они также содержат нелинейную функцию RELU и максимальный объединяющий слой, который суммирует изображения и делает их меньше, чем входные данные. Хотя высота изображения уменьшается в 2 раза в каждом слое, карты объектов (каналы) добавляются таким образом, чтобы получить выходную карту объектов (или последовательность) размером от 32 до 256. RNN: последовательность признаков содержит 256 признаков или симптомов на каждом временном шаге. Соответствующая информация распространяется РНН через эти серии. LSTM-это один из известных алгоритмов RNN, который переносит информацию на большие расстояния и более эффективное обучение, чем типичные РНН. Выходная последовательность RNN сопоставляется с матрицей 32х80.

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

Модель SimpleHTR, где зеленые значки - это операции, а розовые- потоки данныхМодель SimpleHTR, где зеленые значки - это операции, а розовые- потоки данных

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

LineHTR модель

Модель LineHTR - это просто расширение предыдущей модели SimpleHTR, которая была разработана для того, чтобы позволить модели обрабатывать изображения с полной текстовой строкой (а не только одним словом), таким образом, чтобы еще больше повысить точность модели. Архитектура модели LineHTR очень похожа на модель SimpleHTR, с некоторыми различиями в количестве слоев CNN и RNN и размере входных данных этих слоев: она имеет 7 слоев CNN и 2 слоя Bidirectinal LSTM (BLSTM) RNN.

Ниже кратко представлен конвейер алгоритма LineHTR:

  • На входе изображение в градациях серого фиксированного размера 800 x 64 (Ш x В).

  • Слои CNN сопоставляют это изображение в градациях серого с последовательностью элементов размером 100 x 512.

  • Слои BLSTM с 512 единицами отображают эту последовательность признаков в матрицу размером 100 x 205: здесь 100 представляет количество временных шагов (горизонтальных позиций) в изображении с текстовой строкой; 205 представляет вероятности различных символов на определенном временном шаге на этом изображении)

  • Слой CTC может работать в 2 режимах: режим LOSS - чтобы научиться предсказывать правильного персонажа на временном шаге при обучении; Режим ДЕКОДЕР - для получения последней распознанной текстовой строки при тестировании

  • размер партии равен 50

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

Все модели были реализованы с использованием Python и deep learning библиотеки Tensorflow. Tensorflow позволяет прозрачно использование высоко оптимизированных математических операций на графических процессорах с помощью Python. Вычислительный граф определяется в скрипте Python для определения всех операций, необходимых для конкретных вычислений. Графики для отчета были сгенерированы с помощью библиотеки matplotlib для Python, а иллюстрации созданы с помощью Inkscape-программы векторной графики, аналогичной Adobe Photoshop. Эксперименты проводились на машине с 2-кратным " Intel Процессоры Xeon(R) E-5-2680, 4x " NVIDIA Tesla k20x и 100 ГБ памяти RAM. Использование графического процессора сократило время обучения моделей примерно в 3 раза, однако это ускорение не было тщательно отслежено на протяжении всего проекта,поэтому оно могло варьироваться.

SimpleHTR эксперименты

SimpleHTR модель-это обучение, валидация и тестирование на двух различных датасетах. Для того чтобы запустить процесс обучения модели на наших собственных данных, были предприняты следующие шаги:

Создан словарь слов файлов аннотаций

Файл DataLoader для чтения и предварительного владения набором данных изображений и чтения файла аннотаций принадлежит изображениям

Набор данных был разделен на два подмножества: 90% для обучения и 10% для проверки обученной модели. Для повышения точности и снижения частоты ошибок мы предлагаем следующие шаги: во-первых, увеличить набор данных, используя данные увеличение; во-вторых, добавьте больше информации CNN слоев и увеличение ввода размера; в-третьих, удалить шум на изображении и в скорописи стиле; В-четвертых, заменить ЛСТМ двусторонними ГРУ и, наконец, использование декодера передача маркера или слово поиска луча декодирование, чтобы ограничить выход в словарь слова.

Первый Набор Данных: Для обучения на собранных данных была обработана модель SimpleHTR, в которой есть 42 названия стран и городов с различными узорами почерка. Такие данные были увеличены в 10 раз. Были проведены два теста: с выравниванием курсивных слов и без выравнивания. После изучения были получены значения по валидации данных, представленных в Таблице ниже.

Алгоритм

выравнивание скорописи

нет выравнивания

CER

WAR

CER

WAR

bestpath

19.13

52.55

17.97

57.11

beamsearch

18.99

53.33

17.73

58.33

wordbeamsearch

16.38

73.55

15.78

75.11

Эта таблица показывает точность распознавания SimpleHTR для раличных методов декодирования (bestpath, beamsearch, wordbeamsearch). Декодирование наилучшего пути использует только выход NN и вычисляет оценку, принимая наиболее вероятный символ в каждой позиции. Поиск луча также использует только выход NN, но он использует больше данных из него и, следовательно, обеспечивает более детальный результат. Поиск луча с character-LM также забивает символьные последовательности, которые еще больше повышают исход.

Результаты обучения можно посмотреть на рисунке ниже:

Результаты эксперимента с использованием SimpleHTR (lr=0,01): точность модели.Результаты эксперимента с использованием SimpleHTR (lr=0,01): точность модели.Результаты эксперимента с использованием SimpleHTR (lr=0,01): погрешность модели.Результаты эксперимента с использованием SimpleHTR (lr=0,01): погрешность модели.

На рисунке ниже показано изображение с названием региона, которое было представлено на вход, а на другом рисунке мы видим узнаваемое слово " Южно Казахстанская с вероятностью 86 процентов.

Пример изображения с фразой " Южно-Казахстанская на русском языкеПример изображения с фразой " Южно-Казахстанская на русском языкеРезультат распознаванияРезультат распознавания

Второй набор данных (HKR Dataset): Модель SimpleHTR показала в первом тесте набора данных 20,13% символьной ошибки (CER) и второго набора данных 1,55% CER. Мы также оценили модель SimpleHTR по каждому показателю точности символов(рисунок ниже). Частота ошибок в словах (WER) составил 58,97% для теста 1 и 11,09% для теста 2. Результат например TEST2 показывает что модель может распознавать слова которые существуют в обучающем наборе данных но имеют полностью различные стили почерка. Набор данных TEST1 показывает, что результат не является хорошим, когда модель распознает слова, которые не существуют в обучении и наборы данных проверки.

Следующий эксперимент проводился с моделью LineHTR, обученной на данных за 100 эпох. Эта модель продемонстрировала производительность со средним CAR 29,86% и 86,71% для наборов данных TEST1 и TEST2 соответственно (рисунок ниже). Здесь также наблюдается аналогичная тенденция переобучения обучающих данных.

Заключение

Эксперименты по классификации рукописных названий городов проводились с использованием SimpleHTR и LineHTR на тестовых данных были получены следующие результаты по точности распознавания: 57,1% для SimpleHTR рекуррентного CNN с использованием алгоритмов декодирования с наилучшим путем, 58,3% для Beamsearch и 75,1% wordbeamsearch. Лучший результат был показан для Wordbeamsearch, который использует словарь для окончательной коррекции текст при распознавании.

Подробнее..

Загадочные субтитры на CNN

01.12.2020 02:09:30 | Автор: admin
Зрители CNN обратили внимание, что в выпуске новостей 12/11/2020 на их официальном YouTube-канале вместо субтитров какая-то каша из обрывков английских слов, сплошным капсом:



Как такое могло получиться? (По состоянию на 1/12/2020, субтитры на YouTube так и не исправлены.)

Stenotype


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



Сто лет назад стенотайп был вариантом печатной машинки, и каждая клавиша оставляла оттиск на бумаге. Каретки не было: после каждого аккорда из одной или нескольких одновременно нажатых клавиш, бумага проматывалась на одну строчку вниз. Оттиск каждой литеры приходился всегда на одно и то же место в строке. Клавиши P, R, S, T присутствуют в двух экземплярах каждая под левой и под правой рукой.



Каждый аккорд, и каждая строчка в распечатке, соответствует одному слогу. Промежутков между словами нет, а сами слова передаются фонетически: на показанной выше распечатке застенографирована фраза You should be able to read these short words. Поскольку клавиш меньше, чем букв в английском алфавите, и тем более чем звуков в английской речи, то используется хитроумная система кодирования, например [n] записывается как PB, [l] как HR, дифтонг [e] как AEU, и т.п. При таком кодировании, например, слово gleam записывается как TKPWHRAOEPL аккорд из одиннадцати одновременно нажатых клавиш!
Пример отрывка стенограммы судебного заседания

После заседания стенографист должен был сидеть и перепечатывать свою стенограмму на обычной печатной машинке, потому что прочесть её неподготовленному человеку решительно невозможно. В приведённом примере фраза absolutely one hundred percent записана как SHRAOUT HRAOE WOPB HUPBD PERS, что означает [sljutli wn hnd prs] пропущенные слоги должны восстанавливаться по контексту, а гласные не вполне соответствуют словарной транскрипции. Существуют разные системы обозначений и сокращений, так что даже самим стенографистам сложно читать записи один другого. Вот короткий отрывок из учебника стенографии, исправленного владельцем под другую систему:



Считается, что средний темп английской речи 130 слов в минуту, а стенографист со стенотайпом может печатать до 300 слов в минуту, что позволяет записывать разговор даже тогда, когда собеседники перебивают друг друга. Есть аналогичная система Velotype, ориентированная на побуквенную запись вместо фонетической; она позволяет печатать до 200 слов в минуту. Разница вызвана тем, что в английских словах почти всегда букв больше, чем звуков иногда вдвое, как в словах choose [tuz] или earth []. Велотайп был создан в 1939, когда расцвели табуляторы и автоматическая обработка данных; мотивацией для побуквенной записи была более простая расшифровка стенограмм, которую можно было бы поручить даже электромеханической машине. Вместе с прочей оргтехникой электрифицировались и сами стенографические машины: вместо механически соединённых клавиш и литер, печатающих на бумаге, с конца прошлого века используется электроника, удобные дисплеи, цифровое хранение и обработка стенограмм.



В начале этого века возможности компьютерной обработки естественных языков достигли того уровня, чтобы автоматически расшифровывать стенотайповые фонетические стенограммы и превращать SHRAOUT HRAOE в absolutely. Затем поддержка лиц с ограниченными возможностями достигла того уровня, чтобы телеканалы сопровождали прямой эфир субтитрами в реальном времени для тех, кому читать проще, чем слушать. Это значит, что за эфиром следит стенографист со стенотайпом, он записывает речь диктора и других участников передачи, его компьютер расшифровывает стенограмму, при необходимости автоматически переводит её на язык целевой аудитории, и передаёт получившийся текст в эфир и на YouTube.

Судя по всему, 12/11/2020 у CNN в этой сложной системе что-то засбоило, и вывод расшифровщика перемешался с кусками нерасшифрованной стенограммы. Даже если они во время передачи и обнаружили сбой, то решили ничего не трогать, чтобы не отломалось что-нибудь более важное. Случай на Fox News в 2013 наделал гораздо больше шума: в тот раз расшифровщик не справился с именем Джохара Царнаева, устроившего взрыв на Бостонском марафоне, и подставил вместо него в субтитры имя актрисы Зоуи Дешанель.
Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru