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

Нейронные сети

Its a match финские ученые создают идеально привлекательные лица, учитывая личные предпочтения мозга

11.03.2021 12:11:09 | Автор: admin

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

Итак, существует ли идеальная пара? На основе данных ЭЭГ ученые научили генеративно-состязательные нейронные сети (GAN) предсказывать и воссоздавать лица, которые потенциально будут казаться нам привлекательными. Только представьте, итоговая точность предсказаний составила >80%. Интересно, что будет, если сеть сможет в перспективе влиять на подборку пары в Tinder и подобных приложениях? Но разберемся во всем по порядку.

Предыстория



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

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

Как проходил эксперимент



В проведенном исследовании приняли участие 30 сотрудников и студентов из Хельсинского университета. Для обучения GAN они использовали 30 тыс. фотографий знаменитостей. Так сеть научили создавать синтетические портреты. Всего их смоделировали 240.

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

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

Что потом?


Благодаря интерфейсу мозг-компьютер данные передали GAN. И так удалось обучить сеть создавать привлекательные лица для конкретных добровольцев. Однако, до какой степени они будут привлекательными им предстояло еще проверить.

Спустя 2 месяца, ученые вновь собрали участников. Они поместили в подборки изображений как новые привлекательные, так и другие нейтральные и/или непривлекательные. Добровольцы получили матрицу из 24 картинок. Оценка привлекательности проводили по шкале от 1 до 5. Нажатием клавиш участники проставили оценки изображениям.

В итоге выяснилось, что 86,7% изображений, созданных GAN, участники эксперимента выделили как привлекательные. Что интересно еще 20% изображений, которые потенциально создавались как непривлекательные, оказались все же симпатичными для добровольцев. То есть результат работы сети был ложноотрицательный.

Идеальная пара



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

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

Shazam vs ЭЭГ?


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

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

Исследователи из Индии и Нидерландов смогли научиться воссоздавать из активности мозга, фиксируемой ЭЭГ, конкретные песни. Точность определения мелодии составила 85%.

Обучение сети проводили на 20 добровольцах, которые слушали 12 мелодий. Когда сеть работала на данных одного конкретного испытуемого, точность идентификации мелодии составила почти 85%. Когда процесс распознавания запустили без привязки к личности, то точность снизилась почти на 77%.

Подробнее..

Edge платы для домашнего Computer Vision

13.04.2021 06:09:41 | Автор: admin

Я люблю делать всякие странные штуки с Computer Vision. Из того, что я выкладывал на Хабре - умная кормушку для птиц и камера для слежения за ребенком. По работе примерно тем же занимаюсь. Так что слежу за актуальным рынком embedded устройств для ComputerVision. Прошлый обзор я делал полтора года назад. Для Embedded это долго. В этом я сосредоточусь на устройствах которые вышли недавно + некоторый анализ что из этих устройств можно использовать дома/для хобби.

Рассказ будет построен следующим образом:

  • Продуктовые железки которые стали классикой продакшна / железки которые почти доросли до таких.Их можно взять и запустить из коробки. Большие OpenSource комьюнити/персональные фреймворки. Время развертывания обученной сети на такой железке в 1-2 дня.

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

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

  • Железка есть информации почти нет/нельзя получить без запросов. На рынке нет истории использования/успеха.

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

  • Далеко не все из перечисленного я лично использовал/тестировал

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

  • Я фокусируюсь на устройствах где есть GPU/NPU или прочие ускорители инференса. Безусловно, есть всякие FPGA, и прочее, но я не считаю их применимыми для хоббийных проектов. (что такое NPU GPU TPU и другие аббревиатуры - можно прочитать в этой замечательной статье)

Часть 1. Ближе всего к продукту

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

  • Jetson

  • Intel

  • Coral

  • Android телефоны

  • Прочие Embedded, устройства с хорошим процессором, без NPU/GPU

И в этом списке за последние 2 года появился бы лишь Coral. Но, в реальности, все сильно интереснее. Появились не только и не столько новые архитектуры, как имплементации/доработки старых. О чем мы и поговорим.

В мире Jetsonов новинок нет. Сейчас в продаже:

  • jetson nano

  • jetson xavier nx

  • jetson agx

  • jetson tx2

12ого началась конференция GTC от NVIDIA, но ничего нового на ней не объявили, так что, скорее всего, на следующий год ничего нового не будет.

Встречаются имплементации Jetson'а, под другие экосистемы. Самое известное - AWS Panorama. Jetson находящийся внутри экосистемы Амазона.

PanoramaPanorama

Jetson, безусловно, одна из самых удобных плат для хобби. Есть разводка GPIO, много кода который работает из коробки. Нейронные сети можно никуда не конвертировать, используя в оригинальном фреймворке.
Cтоит понимать, что из всех четырех Jetson'ов для хобби лучше всего подходит Nano. Он стоит лишь 100$, что значительно меньше следующего в серии NX, стоящего 400$. В теории, TX2в середине, но его почти нет в продаже + менее удобная плата. Проектов на Jetson очень много. Например из того что было в медийном пространстве - 1, 2. Вот тут есть неплохая подборка.
Лично я участвовал где-то в 5-7 проектах где Jetson был основной платформой. 2-3 из них переросли в полноценные продукты. Но, вынужден сказать, что для хобби его не использовал ни разу. Почему? Какая-то совокупность факторов всегда. Nano у меня был первой серии, там были баги по питанию. Иногда была не нужна производительность дополнительная. Иногда хотелось опробовать чего-то нового.

В отличие от Jetson, на базе Movidius появляются интересные штуки. В первую очередь это M.2 и mPCIe карты. Какие-то даже уже были, когда я писал прошлый обзор: 1, 2, 3.
Сейчас их очень много от разных производителей.

Удобны ли ли они для каких-нибудь прототипов и хобийных проектов? Мне кажется, что ниша есть, но очень узкая:

  • Когда надо много производительности (есть сборки где есть несколько мовидиусов)

  • Когда USB соединение слишком нестабильно, но M.2/PCIe хватит (переносные устройства)

Во вторую очередь - это ряд устройств от luxonis. Они устраивали большую компанию на кикстартере про OAK и OAK-D. OAK это платы где movidius воткнут не на материнскую плату, а на плату с камерой. Кроме того, у них есть несколько устройств с movidius когда он стоит на плате 1, 2. Я не буду вдаваться подробнее тут, кому интересно - про них я делал более подробный обзор у себя в блоге/на Youtube:

Кому лень читать/смотреть - вот краткая выдержка:

  • + Минус одно USB соединение + к стабильности - для части проектов полезно

  • - От USB все равно не уйти - до прода скорее всего не дойдет

  • + Неплохой дизайн, неплохой корпус

  • - Заменили хороший OpenVino на какой-то мутный DepthAI

  • - Дорого. Дороже чем собрать такое с оригинальным Movidius'ом, дороже чем с Jetson Nano

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

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

Кроме luxonis до movidius'а в камере догадался FLIR, достаточно крупный производитель камер. Они выпустили FireFly DL, который явно на порядки более продуктовый чем OAK, а стоит только на 100$ дороже (+объектив).

Чтобы закончить с Movidius окончательно. По простоте использования/наличию документации/комментариям и поддержке - на мой взгляд Movidius - это один из самых разумных ускорителей для своих проектов на нейронных сетях. Его минус - сложный переход с продакшну, и необходимость конвертации сети. То что вышло за последние годы - расширяет возможности использования. Но все же мне не хватает плат сравнимых с RPi3 где мовидиус бы стоял напрямую на плате.

Google Coral. Вот тут много нового. В своем прошлом обзоре я был крайне недоволен им - очень ограниченные платы, очень бажные. Но прогресс. Баги пофикшены, выпущена новая линейка. Есть почти все то же самое что и в movidius, только напрямую от производителя - отдельные платы, стики, M.2, pci-e, чистые чипы, и.т.д..

В качестве основного фреймворка - специальный tflite, где уже сильно меньше потерь на конвертации. Но, как видно, слоев все равно сильно меньше чем в том же ONNX.
Из плюсов, про которые не сказал - на базе Coral уже сторонние производители создают свои решения. Например Asus. Из минусов - считанные разы слышал что его использовали в продакшене. И, обычно, для каких-то простых задач.
Для себя я понимаю почему я избегаю Coral - при прочих равных не хочу трогать TensorFlow. А по текущим характеристикам он нигде не превосходит вариантов выше.

Телефоны. Так же, стоит отметить, многие телефоны получили поддержку из плат сопроцессоров для нейронных сетей. Есть несколько фреймфорков для инференса моделей на них. Например тут и тут описывал. Зачастую телефон стал удобнее чем embedded для пилота и хобби. Основной минус - отсутствие периферии. Второй серьезный минус для меня - внутренняя инфраструктура приложений. Конечно, Unity и Flutter упрощают логику использования. Но все же, лично для меня, телефоны сильно сложнее чем Linux-системы.
С другой стороны, в телефоне уже есть и камера и акселерометр, и интернет.

Прочее. Под "прочим" я в первую очередь подразумеваю системы где заход в нейронные сети идет со стороны процессора. Например в процессорах Intel за счет OpenVino можно сильно оптимизировать сети. На некоторых процессорах, например на RaspberryPI есть оптимизация под инструкции Neon. RPi4 вполне может справляться с какими-то задачами детекции и трекинга в реальном времени. Так что если вам нужно с помощью машинного зрения раз в день проверять рассаду - думаю подойдет.

Часть 2. Работает, но мало информации

Есть такая забавная штука. В ML сейчас 90% знаний открыто. Есть статьи, большая часть публикаций с OpenSource. Подробнейшие фреймворки на Nvidia и Intel. Но рынок аппаратных платформ был исторически не такой. Хотите подключить камеру по csi-mpi к своей платформе? Будьте добры купите дорогущий мануал по протоколу. Хотите разрабатывать на нашей платформе? Для этого вам нужно специальное программное обеспечение которое просто так вы не скачаете. И много фирм по производству железа по-другому и не мыслят. Как результат мы имеем полтора гайда на платформу до её покупки. Невозможность протестировать до покупки. Отсутствие форумов по теме. И проблему с каждой функцией где что-то пошло не так.

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

  • RockChip

  • Gyrfalcon

Обе платформы я видел в проде. Даже немного экспериментировал. Но у обоих платформ крайне неудобный фреймворк переноса сетей/использования.

RochChip. Основная платформа на которой все делается - Rockchip 3399Pro. Самая популярная реализация, наверное - Firefly, RockPiN10 или Toybrick.

Что забавно, у того же ASUS есть версия не только на базе Google Coral, но и на базе RockChip.
Сейчас разрабатывается новая версия - 1, 2.
В целом, плюс RockChip'а - это плата которую любят все разработчики железа. Есть референсный дизайн, комплектующие, и.т.д. Собрать продукт проще и быстрее чем на Jetson.
Но перенос сети весьма непредсказуем. Документация куцая и полукитайская. Как поддерживается - не понятно. Я видел несколько проектов где сети все же перенесли. Но, гарантировать что это всегда можно, нельзя.

Gyrfalcon. Вторым примером закрытой архитектуры, но на базе которой я видел проекты - является Gyrfalcon

Забавно, что платы на его базе в продаже почти отсутствуют. Что-то из того что есть: 1, 2, 3 .

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

Делать ли свои проекты на базе этих платформ? Подходят ли они для хобби? В целом, такое мне кажется возможным. Главное чтобы были простые сетки. Классификация/базовая детекция, и.т.д.
По цене такие платформы достаточно дешевы и сравнимы с OpenVino|Jetson вариантами.
Но надо серьезно понимать зачем так делать. Это может быть:

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

  • желание распаять свою систему

  • нехватка в Jetson|RPi каких-то возможностей

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

Часть 3. Внешне все выглядит неплохо, есть какая-то документация, но примеров нет

Пожалуй, сюда я отнесу только одну плату Khadas VIM3 . Много кто о ней знает, но не видел ни одного человека который бы что-то на ней сделал. Есть в открытой продаже, просто купить. Заявлены неплохие параметры по скорости.

Судя по документации перенос моделей достаточно простой. Но, так как никто не пробовал/не тестировал, - не понятны ограничения. Сама плата собрана на базе процессора Amlogic A311D, который содержит NPU модуль. Amlogic многие позиционируют как конкурент RockChip, сравнивая их. Но сравнений именно NPU модулей - нет.

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

Часть 4. Железки есть, информации нет

Большая часть плат тут имеет очень слабую документацию. Часть плат - это отладочные платы для мобильных платформ. Другая часть - платы специально под ML, но по которым очень мало информации и данных в интернете.

BeagleV. Плата которая пока не вышла, но выглядит неплохо. Разработана на базе процессора U74 от SiFive. Используется RISC-V архитектура.

BeagleBoard - странная плата с комьюнити вокруг. Именно вокруг этого комьюнити частично построена плата BeagleV. Плата сделана на базе NPU модуля от Texas Instruments. Вроде даже какие-то репозитории есть:

  1. Фреймворк от TI для обучения нейронных сетей. Аж 55 звезд на гитхабе.

  2. Репозиторий платы. Аж 88 звезд.

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

Пример настоящего Edge (минимальное использование ЦПУ и энергоэффективности) это - Sipeed Maixduino и Grove AI Hat. Но, разработка на них, судя по отзывам, которые я слышал, ужасна. Сети плохо поддерживаются, мало производительности. Вот тут пример использования. Ну, по сути все проблемы обычного Arduino.
Я видел людей которые делали и адекватные проекты на их базе, и хоббийные. Но я не готов:)

Глобально, Qualcomm - это, конечно, производитель процессоров для мобильных. Но, на их же базе, есть какое-то количество именно embedded платформ. При этом, Qualcomm - имеет свой SDK, и свою платформу для исполнения нейронных сетей. Года 2.5 назад я сталкивался с ней. И тогда там была жесть. Простейший слой сложения не поддерживался. Что там говорить про сплиты или объединения. По сути работал лишь VGG на трехканальный вход.
Сейчас, судя по слухам все лучше. К тому же, должен нормально работать Tensorflow lite.
Но вот с платами под эмбеддед у Qualcomm плохо. Есть вот такое (но стоит почти 500 баксов). Или вот такое (мало информации, но выглядит прикольно, за 300 баксов камера + корпус + ускоритель = неплохо).

Примерно так же себя ведет Huawei. Есть фреймворк. Не пользовался, и не знаю. Есть какое-то количество плат. Должен работать Tensorflow lite.
Но, мне сложно придумать где такая плата будет иметь смысл на использование.

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

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

P.S.

Про девайсы которые попадают мне в руки/про которые я читаю - иногда пишу у себя в блоге (telegramm, vk). Наверное, через год-два проапдейчу тут что накопится.
Прошлый апдейт делал на ютубе.

Подробнее..

Дообучение нейросети для поиска лиц в медицинских масках

29.04.2021 14:14:31 | Автор: admin

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

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

Итак, разместим наш датасет из 1962 фотографий в двух каталогах в папке dataset в масках в WithMask и без маски в Withoutmask соответственно. В каждой по 981 фотографии. Ещё одно важное замечание, это то, что дообучаем мы именно на лицах, а не просто, что человек на изображении в маске или без, хотя можно было и так.

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

from tensorflow.keras.preprocessing.image import ImageDataGeneratorfrom tensorflow.keras.applications import MobileNetV2from tensorflow.keras.layers import AveragePooling2Dfrom tensorflow.keras.layers import Dropoutfrom tensorflow.keras.layers import Flattenfrom tensorflow.keras.layers import Densefrom tensorflow.keras.layers import Inputfrom tensorflow.keras.models import Modelfrom tensorflow.keras.optimizers import Adamfrom tensorflow.keras.applications.mobilenet_v2 import preprocess_inputfrom tensorflow.keras.preprocessing.image import img_to_arrayfrom tensorflow.keras.preprocessing.image import load_imgfrom tensorflow.keras.utils import to_categoricalfrom sklearn.preprocessing import LabelBinarizerfrom sklearn.model_selection import train_test_splitfrom sklearn.metrics import classification_reportfrom imutils import pathsimport matplotlib.pyplot as pltimport numpy as npimport argparseimport os

# Указываем начальные гиперпараметры

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

INIT_LR = 0,004

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

EPOCHS = 20

Третий это размер пакета или батча, означает количество данных в одной партии.

BS = 32

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

imagePaths = list(paths.list_images (r'C:\dataset'))  # В этой папке хранятся два каталога с масками и безdata , labels = [] , []for imagePath in imagePaths:# Извлечение класса из директории (с маской или без)label = imagePath.split(os.path.sep)[-2]# Загружам входное изображение 224х224 и обрабатываем егоimage = load_img(imagePath, target_size = (224, 224))image = img_to_array(image)image = preprocess_input(image)# Обновляем список файлов и классовdata.append(image)labels.append(label)# Переводим в NumPy массивdata = np.array(data, dtype="float32")labels = np.array(labels)# Переводим классы в бинарный вид, т.е. 0 без маски 1 с маскойlb = LabelBinarizer()labels = lb.fit_transform(labels)labels = to_categorical(labels)# Разобьём датасет  на тренировочный и тестовый 80% на 20%;(trainX, testX, trainY, testY) = train_test_split(data, labels,  test_size = 0.20, stratify = labels, random_state = 42)# Аугментация датасета путем поворота изображенийaug = ImageDataGenerator(rotation_range = 20, zoom_range = 0.15,width_shift_range = 0.2, height_shift_range = 0.2, shear_range=0.15, horizontal_flip = True, fill_mode = "nearest")# Загружаем базовую модель c предварительно обученными весамиpath_weights = mobilenet_v2_weights_tf_dim_ordering_tf_kernels_1.0_224_no_top.h5'    baseModel = MobileNetV2(weights=path_weights, include_top=False, input_tensor=Input(shape=(224, 224, 3))Запишем в нашу модель внешний слой из базовой моделиheadModel = baseModel.outputheadModel = AveragePooling2D(pool_size = (7, 7))(headModel)headModel = Flatten(name = "flatten")(headModel)headModel = Dense(128, activation = "relu")(headModel)headModel = Dropout(0.5)(headModel)headModel = Dense(2, activation = "softmax")(headModel)model = Model(inputs = baseModel.input, outputs = headModel)# Заморозка слоев базовой модели for layer in baseModel.layers:layer.trainable = False# Скомпилируем нашу модельopt = Adam(lr = INIT_LR, decay = INIT_LR / EPOCHS)model.compile(loss = "binary_crossentropy", optimizer = opt, metrics = ["accuracy"])# Тренируем нашу сетьH = model.fit( aug.flow(trainX, trainY, batch_size = BS), steps_per_epoch = len(trainX) // BS,validation_data = (testX, testY), validation_steps = len(testX) // BS, epochs = EPOCHS)# Делаем предсказание на тестовой выборкеpredIdxs = model.predict(testX, batch_size = BS)#  Для каждого изображения в тестовом наборе, ищем максимальную вероятностьpredIdxs = np.argmax(predIdxs, axis=1)# Показать отчет обучения print(classification_report(testY.argmax(axis = 1), predIdxs, target_names = lb.classes_))

# Сохраняем модель на диск и загружаем её

model.save('model_mask_FACE', save_format = "h5")model_mask = tf.keras.models.load_model('model_mask_FACE)

Поиск маски на лице, на примере

# Найдем лицо на изображении, используя библиотеку MTCNN

frame = cv2.cvtColor(cv2.imread(house.png'), cv2.COLOR_BGR2RGB)frame_image = Image.fromarray(frame)boxes, probs, landmarks = mtcnn.detect(frame_image, landmarks = True)x1, y1, x2, y2 = [int(bx) for bx in boxes[0]]image = Image.fromarray(frame[y1:y2, x1:x2]).resize((224,224))face = img_to_array(image)

# Обработка изображения для загрузки в модель

face = preprocess_input(face)face = np.expand_dims(face, axis=0)

# Загрузка лица в нашу модель

(mask, withoutMask) = model_mask.predict(face)[0]image = cv2.imread(house.png)

# Прорисовка рамки и вероятности

if mask > withoutMask and max(mask, withoutMask) > 0.8: # уверенность    label = "Mask" if mask > withoutMask else "No Mask"    color = (0, 122, 0) if label == "Mask" else (0, 0, 122)    label = "{}: {:.2f}%".format(label, max(mask, withoutMask) * 100)    cv2.putText(image, label, (x1, y1 - 10),cv2.FONT_HERSHEY_SIMPLEX, 2, color, 5)    cv2.rectangle(image, (x1, y1), (x2, y2), color, 5)     y = Image.fromarray(cv2.cvtColor(image, cv2.COLOR_BGR2RGB)

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

Подробнее..

Обнаружение объектов с помощью YOLOv3 на Tensorflow 2.0

08.05.2021 14:13:54 | Автор: admin
Кадр из аниме "Жрица и медведь"Кадр из аниме "Жрица и медведь"

До появления YOLO большинство способов обнаружения объектов пытались адаптировать классификаторы для детекции. В YOLO же, обнаружение объектов было сформулировано как задача регрессии на пространственно разделенных ограничивающих рамок (bounding boxes) и связанных с ними вероятностей классов.

В данной статье мы узнаем о системе YOLO Object Detection и как реализовать подобную систему в Tensorflow 2.0

О YOLO:

Наша унифицированная архитектура чрезвычайно быстра. Базовая модель YOLO обрабатывает изображения в режиме реального времени со скоростью 45 кадров в секунду. Уменьшенная версия сети, Fast YOLO, обрабатывает аж 155 кадра в секунду

You Only Look Once: Unified, Real-Time Object Detection, 2015

Что такое YOLO?

YOLO это новейшая (на момент написания оригинальной статьи) система (сеть) обнаружения объектов. Она была разработана Джозефом Редмоном (Joseph Redmon). Наибольшим преимуществом YOLO над другими архитектурами является скорость. Модели семейства YOLO исключительно быстры и намного превосходят R-CNN (Region-Based Convolutional Neural Network) и другие модели. Это позволяет добиться обнаружения объектов в режиме реального времени.

На момент первой публикации (в 2016 году) по сравнению с другими системами, такими как R-CNN и DPM (Deformable Part Model), YOLO добилась передового значения mAP (mean Average Precision). С другой стороны, YOLO испытывает трудности с точной локализацией объектов. Однако в новой версии были внесены улучшения в скорости и точности системы.

Альтернативы (на момент публикации статьи): Другие архитектуры в основном использовали метод скользящего окна по всему изображению, и классификатор использовался для определенной области изображения (DPM). Также, R-CNN использовал метод предложения регионов (region proposal method). Описываемый метод сначала создает потенциальные bounding boxы. Затем, на области, ограниченные bounding boxами, запускается классификатор и следующее удаление повторяющихся распознаваний, и уточнение границ рамок.

YOLO переосмыслила задачу обнаружения объектов в задачу регрессии.Она идет от пикселей изображения к координатам bounding boxов и вероятностей классов. Тем самым, единая сверточная сеть предсказывает несколько bounding boxов и вероятности классов для содержания этих областей.

Теория

Так как YOLO необходимо только один взгляд на изображение, то метод скользящего окна не подходит в данной ситуации. Вместо этого, изображение будет поделено на сетку с ячейками размером S x S. Каждая ячейка может содержать несколько разных объектов для распознавания.

Во-первых, каждая ячейка отвечает за прогнозирование количества bounding boxов. Также, каждая ячейка прогнозирует доверительное значение (confidence value) для каждой области, ограниченной bounding boxом. Иными словами, это значение определяет вероятность нахождения того или иного объекта в данной области. То есть в случае, если какая-то ячейка сетки не имеет определенного объекта, важно, чтобы доверительное значение для этой области было низким.

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

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

Давайте подробней опишем вывод модели.

В YOLO используются anchor boxes (якорные рамки / фиксированные рамки) для прогнозирования bounding boxов. Идея anchor boxов сводится к предварительному определению двух различных форм. И таким образом, мы можем объединить два предсказания с двумя anchor boxами (в целом, мы могли бы использовать даже большее количество anchor boxов). Эти якоря были рассчитаны с помощью датасета COCO (Common Objects in Context) и кластеризации k-средних (K-means clustering).

У нас есть сетка, где каждая ячейка предсказывает:

  • Для каждого bounding box'а:

    • 4 координаты (tx , ty , tw , th)

    • 1 objectness error (ошибка объектности), которая является показателем уверенности в присутствии того или иного объекта

  • Некоторое количество вероятностей классов

Если же присутствует некоторое смещение от верхнего левого угла на cx , cy то прогнозы будут соответствовать:

b_{x} = \sigma(t_{x}) + c_{x}\\ b_{y} = \sigma(t_{y}) + c_{y}\\ b_{w} = p_{w}e^{t_{w}}\\ b_{h} = p_{h}e^{t_{h}}

где pw (ширина) и ph (высота) соответствуют ширине и высоте bounding box'а. Вместо того, чтобы предугадывать смещение как в прошлой версии YOLOv2, авторы прогнозируют координаты местоположения относительно местоположения ячейки.

Этот вывод является выводом нашей нейронной сети. В общей сложности здесьS x S x [B * (4+1+C)] выводов, где B это количество bounding box'ов, которое может предсказать ячейка на карте объектов, C это количество классов, 4 для bounding box'ов, 1 для objectness prediction (прогнозирование объектности). За один проход мы можем пройти от входного изображения к выходному тензору, который соответствует обнаруженным объектам на картинке. Также стоит отметить, что YOLOv3 прогнозирует bounding box'ы в трех разных масштабах.

Теперь, если мы возьмем вероятность и умножим их на доверительные значения, мы получим все bounding box'ы, взвешенные по вероятности содержания этого объекта.

Простое нахождение порогового значения избавит нас от прогнозов с низким доверительным значением. Для следующего шага важно определить метрику IoU (Intersection over Union / Пересечение над объединением). Эта метрика равняется соотношению площади пересекающихся областей к площади областей объединенных.

После этого все равно могут остаться дубликаты, и чтобы от них избавиться нужно использовать подавление не-максимумов (non-maximum suppression). Подавление не-максимумов заключается в следующем: алгоритм берёт bounding box с наибольшей вероятностью принадлежности к объекту, затем, среди остальных граничащих bounding box'ов с данной области, возьмёт один с наивысшим IoU и подавляет его.

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

Yolov3Yolov3

Мы также рекомендуем прочитать следующие статьи о YOLO:

Реализация в Tensorflow

Первым шагом в реализации YOLO это подготовка ноутбука и импортирование необходимых библиотек. Целиком ноутбук с кодом вы можете на Github или Kaggle:

Следуя этой статье, мы сделаем полную сверточную сеть (fully convolutional network / FCN) без обучения. Для того, чтобы применить эту сеть для определения объектов, нам необходимо скачать готовые веса от предварительно обученной модели. Эти веса были получены от обучения YOLOv3 на датасете COCO (Common Objects in Context). Файл с весами можно скачать по ссылке официального сайта.

# Создаем папку для checkpoint'ов с весами.# !mkdir checkpoints# Скачиваем файл с весами для YOLOv3 с официального сайта.# !wget https://pjreddie.com/media/files/yolov3.weights# Импортируем необходимые библиотеки.import cv2import numpy as np import tensorflow as tf from absl import loggingfrom itertools import repeatfrom PIL import Imagefrom tensorflow.keras import Modelfrom tensorflow.keras.layers import Add, Concatenate, Lambdafrom tensorflow.keras.layers import Conv2D, Input, LeakyReLUfrom tensorflow.keras.layers import MaxPool2D, UpSampling2D, ZeroPadding2Dfrom tensorflow.keras.regularizers import l2from tensorflow.keras.losses import binary_crossentropyfrom tensorflow.keras.losses import sparse_categorical_crossentropyyolo_iou_threshold = 0.6 # Intersection Over Union (iou) threshold.yolo_score_threshold = 0.6 # Score threshold.weightyolov3 = 'yolov3.weights' # Путь до файла с весами.size = 416 # Размер изображения. checkpoints = 'checkpoints/yolov3.tf' # Путь до файла с checkpoint'ом.num_classes = 80 # Количество классов в модели.# Список слоев в YOLOv3 Fully Convolutional Network (FCN).YOLO_V3_LAYERS = [    'yolo_darknet',    'yolo_conv_0',    'yolo_output_0',    'yolo_conv_1',    'yolo_output_1',    'yolo_conv_2',    'yolo_output_2']

По причине того, что порядок слоев в Darknet (open source NN framework) и tf.keras разные, то загрузить веса с помощью чистого функционального API будет проблематично. В этом случае, наилучшим решением будет создание подмоделей в keras. TF Checkpoints рекомендованы для сохранения вложенных подмоделей и они официально поддерживаются Tensorflow.

# Функция для загрузки весов обученной модели.def load_darknet_weights(model, weights_file):    wf = open(weights_file, 'rb')    major, minor, revision, seen, _ = np.fromfile(wf, dtype=np.int32, count=5)    layers = YOLO_V3_LAYERS    for layer_name in layers:        sub_model = model.get_layer(layer_name)        for i, layer in enumerate(sub_model.layers):            if not layer.name.startswith('conv2d'):                continue            batch_norm = None            if i + 1 < len(sub_model.layers) and \                sub_model.layers[i + 1].name.startswith('batch_norm'):                    batch_norm = sub_model.layers[i + 1]            logging.info("{}/{} {}".format(                sub_model.name, layer.name, 'bn' if batch_norm else 'bias'))                        filters = layer.filters            size = layer.kernel_size[0]            in_dim = layer.input_shape[-1]            if batch_norm is None:                conv_bias = np.fromfile(wf, dtype=np.float32, count=filters)            else:                bn_weights = np.fromfile(wf, dtype=np.float32, count=4*filters)                bn_weights = bn_weights.reshape((4, filters))[[1, 0, 2, 3]]            conv_shape = (filters, in_dim, size, size)            conv_weights = np.fromfile(wf, dtype=np.float32, count=np.product(conv_shape))            conv_weights = conv_weights.reshape(conv_shape).transpose([2, 3, 1, 0])            if batch_norm is None:                layer.set_weights([conv_weights, conv_bias])            else:                layer.set_weights([conv_weights])                batch_norm.set_weights(bn_weights)    assert len(wf.read()) == 0, 'failed to read weights'    wf.close()

На этом же этапе, мы должны определить функцию для расчета IoU. Мы используем batch normalization (пакетная нормализация) для нормализации результатов, чтобы ускорить обучение. Так как tf.keras.layers.BatchNormalization работает не очень хорошо для трансферного обучения (transfer learning), то мы используем другой подход.

# Функция для расчета IoU.def interval_overlap(interval_1, interval_2):    x1, x2 = interval_1    x3, x4 = interval_2    if x3 < x1:        return 0 if x4 < x1 else (min(x2,x4) - x1)    else:        return 0 if x2 < x3 else (min(x2,x4) - x3)def intersectionOverUnion(box1, box2):    intersect_w = interval_overlap([box1.xmin, box1.xmax], [box2.xmin, box2.xmax])    intersect_h = interval_overlap([box1.ymin, box1.ymax], [box2.ymin, box2.ymax])    intersect_area = intersect_w * intersect_h    w1, h1 = box1.xmax-box1.xmin, box1.ymax-box1.ymin    w2, h2 = box2.xmax-box2.xmin, box2.ymax-box2.ymin    union_area = w1*h1 + w2*h2 - intersect_area    return float(intersect_area) / union_area class BatchNormalization(tf.keras.layers.BatchNormalization):    def call(self, x, training=False):        if training is None: training = tf.constant(False)        training = tf.logical_and(training, self.trainable)        return super().call(x, training)# Определяем 3 anchor box'а для каждой ячейки.   yolo_anchors = np.array([(10, 13), (16, 30), (33, 23), (30, 61), (62, 45),                        (59, 119), (116, 90), (156, 198), (373, 326)], np.float32) / 416yolo_anchor_masks = np.array([[6, 7, 8], [3, 4, 5], [0, 1, 2]])

В каждом масштабе мы определяем 3 anchor box'а для каждой ячейки. В нашем случае если маска будет:

  • 0, 1, 2 означает, что будут использованы первые три якорные рамки

  • 3, 4 ,5 означает, что будут использованы четвертая, пятая и шестая

  • 6, 7, 8 означает, что будут использованы седьмая, восьмая, девятая

# Функция для отрисовки bounding box'ов.def draw_outputs(img, outputs, class_names, white_list=None):    boxes, score, classes, nums = outputs    boxes, score, classes, nums = boxes[0], score[0], classes[0], nums[0]    wh = np.flip(img.shape[0:2])    for i in range(nums):        if class_names[int(classes[i])] not in white_list:            continue        x1y1 = tuple((np.array(boxes[i][0:2]) * wh).astype(np.int32))        x2y2 = tuple((np.array(boxes[i][2:4]) * wh).astype(np.int32))        img = cv2.rectangle(img, x1y1, x2y2, (255, 0, 0), 2)        img = cv2.putText(img, '{} {:.4f}'.format(            class_names[int(classes[i])], score[i]),            x1y1, cv2.FONT_HERSHEY_COMPLEX_SMALL, 1, (0, 0, 255), 2)    return img

Теперь пришло время для реализации YOLOv3. Идея заключается в том, чтобы использовать только сверточные слои. Так как их здесь 53, то самым простым способом является создание функции, в которую мы будем передавать важные параметры, меняющиеся от слоя к слою.

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

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

def DarknetConv(x, filters, size, strides=1, batch_norm=True):    if strides == 1:        padding = 'same'    else:        x = ZeroPadding2D(((1, 0), (1, 0)))(x)        padding = 'valid'    x = Conv2D(filters=filters, kernel_size=size,              strides=strides, padding=padding,              use_bias=not batch_norm, kernel_regularizer=l2(0.0005))(x)    if batch_norm:        x = BatchNormalization()(x)        x = LeakyReLU(alpha=0.1)(x)    return xdef DarknetResidual(x, filters):    previous = x    x = DarknetConv(x, filters // 2, 1)    x = DarknetConv(x, filters, 3)    x = Add()([previous , x])    return xdef DarknetBlock(x, filters, blocks):    x = DarknetConv(x, filters, 3, strides=2)    for _ in repeat(None, blocks):        x = DarknetResidual(x, filters)           return xdef Darknet(name=None):    x = inputs = Input([None, None, 3])    x = DarknetConv(x, 32, 3)    x = DarknetBlock(x, 64, 1)    x = DarknetBlock(x, 128, 2)    x = x_36 = DarknetBlock(x, 256, 8)    x = x_61 = DarknetBlock(x, 512, 8)    x = DarknetBlock(x, 1024, 4)    return tf.keras.Model(inputs, (x_36, x_61, x), name=name)  def YoloConv(filters, name=None):    def yolo_conv(x_in):        if isinstance(x_in, tuple):            inputs = Input(x_in[0].shape[1:]), Input(x_in[1].shape[1:])            x, x_skip = inputs            x = DarknetConv(x, filters, 1)            x = UpSampling2D(2)(x)            x = Concatenate()([x, x_skip])        else:            x = inputs = Input(x_in.shape[1:])        x = DarknetConv(x, filters, 1)        x = DarknetConv(x, filters * 2, 3)        x = DarknetConv(x, filters, 1)        x = DarknetConv(x, filters * 2, 3)        x = DarknetConv(x, filters, 1)        return Model(inputs, x, name=name)(x_in)    return yolo_conv  def YoloOutput(filters, anchors, classes, name=None):    def yolo_output(x_in):        x = inputs = Input(x_in.shape[1:])        x = DarknetConv(x, filters * 2, 3)        x = DarknetConv(x, anchors * (classes + 5), 1, batch_norm=False)        x = Lambda(lambda x: tf.reshape(x, (-1, tf.shape(x)[1], tf.shape(x)[2],                                        anchors, classes + 5)))(x)        return tf.keras.Model(inputs, x, name=name)(x_in)    return yolo_outputdef yolo_boxes(pred, anchors, classes):    grid_size = tf.shape(pred)[1]    box_xy, box_wh, score, class_probs = tf.split(pred, (2, 2, 1, classes), axis=-1)    box_xy = tf.sigmoid(box_xy)    score = tf.sigmoid(score)    class_probs = tf.sigmoid(class_probs)    pred_box = tf.concat((box_xy, box_wh), axis=-1)    grid = tf.meshgrid(tf.range(grid_size), tf.range(grid_size))    grid = tf.expand_dims(tf.stack(grid, axis=-1), axis=2)    box_xy = (box_xy + tf.cast(grid, tf.float32)) /  tf.cast(grid_size, tf.float32)    box_wh = tf.exp(box_wh) * anchors    box_x1y1 = box_xy - box_wh / 2    box_x2y2 = box_xy + box_wh / 2    bbox = tf.concat([box_x1y1, box_x2y2], axis=-1)        return bbox, score, class_probs, pred_box

Теперь определим функцию подавления не-максимумов.

def nonMaximumSuppression(outputs, anchors, masks, classes):    boxes, conf, out_type = [], [], []    for output in outputs:        boxes.append(tf.reshape(output[0], (tf.shape(output[0])[0], -1, tf.shape(output[0])[-1])))        conf.append(tf.reshape(output[1], (tf.shape(output[1])[0], -1, tf.shape(output[1])[-1])))        out_type.append(tf.reshape(output[2], (tf.shape(output[2])[0], -1, tf.shape(output[2])[-1])))    bbox = tf.concat(boxes, axis=1)    confidence = tf.concat(conf, axis=1)    class_probs = tf.concat(out_type, axis=1)    scores = confidence * class_probs      boxes, scores, classes, valid_detections = tf.image.combined_non_max_suppression(        boxes=tf.reshape(bbox, (tf.shape(bbox)[0], -1, 1, 4)),        scores=tf.reshape(            scores, (tf.shape(scores)[0], -1, tf.shape(scores)[-1])),        max_output_size_per_class=100,        max_total_size=100,        iou_threshold=yolo_iou_threshold,        score_threshold=yolo_score_threshold)      return boxes, scores, classes, valid_detections

Основная функция:

def YoloV3(size=None, channels=3, anchors=yolo_anchors,            masks=yolo_anchor_masks, classes=80, training=False):    x = inputs = Input([size, size, channels])    x_36, x_61, x = Darknet(name='yolo_darknet')(x)    x = YoloConv(512, name='yolo_conv_0')(x)    output_0 = YoloOutput(512, len(masks[0]), classes, name='yolo_output_0')(x)    x = YoloConv(256, name='yolo_conv_1')((x, x_61))    output_1 = YoloOutput(256, len(masks[1]), classes, name='yolo_output_1')(x)    x = YoloConv(128, name='yolo_conv_2')((x, x_36))    output_2 = YoloOutput(128, len(masks[2]), classes, name='yolo_output_2')(x)    if training:        return Model(inputs, (output_0, output_1, output_2), name='yolov3')    boxes_0 = Lambda(lambda x: yolo_boxes(x, anchors[masks[0]], classes),                  name='yolo_boxes_0')(output_0)    boxes_1 = Lambda(lambda x: yolo_boxes(x, anchors[masks[1]], classes),                  name='yolo_boxes_1')(output_1)    boxes_2 = Lambda(lambda x: yolo_boxes(x, anchors[masks[2]], classes),                  name='yolo_boxes_2')(output_2)    outputs = Lambda(lambda x: nonMaximumSuppression(x, anchors, masks, classes),                  name='nonMaximumSuppression')((boxes_0[:3], boxes_1[:3], boxes_2[:3]))    return Model(inputs, outputs, name='yolov3')

Функция потерь:

def YoloLoss(anchors, classes=80, ignore_thresh=0.5):    def yolo_loss(y_true, y_pred):        pred_box, pred_obj, pred_class, pred_xywh = yolo_boxes(            y_pred, anchors, classes)        pred_xy = pred_xywh[..., 0:2]        pred_wh = pred_xywh[..., 2:4]        true_box, true_obj, true_class_idx = tf.split(            y_true, (4, 1, 1), axis=-1)        true_xy = (true_box[..., 0:2] + true_box[..., 2:4]) / 2        true_wh = true_box[..., 2:4] - true_box[..., 0:2]        box_loss_scale = 2 - true_wh[..., 0] * true_wh[..., 1]        grid_size = tf.shape(y_true)[1]        grid = tf.meshgrid(tf.range(grid_size), tf.range(grid_size))        grid = tf.expand_dims(tf.stack(grid, axis=-1), axis=2)        true_xy = true_xy * tf.cast(grid_size, tf.float32) - \            tf.cast(grid, tf.float32)        true_wh = tf.math.log(true_wh / anchors)        true_wh = tf.where(tf.math.is_inf(true_wh),                      tf.zeros_like(true_wh), true_wh)        obj_mask = tf.squeeze(true_obj, -1)        true_box_flat = tf.boolean_mask(true_box, tf.cast(obj_mask, tf.bool))        best_iou = tf.reduce_max(intersectionOverUnion(            pred_box, true_box_flat), axis=-1)        ignore_mask = tf.cast(best_iou < ignore_thresh, tf.float32)        xy_loss = obj_mask * box_loss_scale * \            tf.reduce_sum(tf.square(true_xy - pred_xy), axis=-1)        wh_loss = obj_mask * box_loss_scale * \            tf.reduce_sum(tf.square(true_wh - pred_wh), axis=-1)        obj_loss = binary_crossentropy(true_obj, pred_obj)        obj_loss = obj_mask * obj_loss + \            (1 - obj_mask) * ignore_mask * obj_loss        class_loss = obj_mask * sparse_categorical_crossentropy(            true_class_idx, pred_class)        xy_loss = tf.reduce_sum(xy_loss, axis=(1, 2, 3))        wh_loss = tf.reduce_sum(wh_loss, axis=(1, 2, 3))        obj_loss = tf.reduce_sum(obj_loss, axis=(1, 2, 3))        class_loss = tf.reduce_sum(class_loss, axis=(1, 2, 3))        return xy_loss + wh_loss + obj_loss + class_loss    return yolo_loss

Функция "преобразовать цели" возвращает кортеж из форм:

(    [N, 13, 13, 3, 6],    [N, 26, 26, 3, 6],    [N, 52, 52, 3, 6])

Где N число меток в пакете, а число 6 означает [x, y, w, h, obj, class] bounding box'а.

@tf.functiondef transform_targets_for_output(y_true, grid_size, anchor_idxs, classes):    N = tf.shape(y_true)[0]    y_true_out = tf.zeros(      (N, grid_size, grid_size, tf.shape(anchor_idxs)[0], 6))    anchor_idxs = tf.cast(anchor_idxs, tf.int32)    indexes = tf.TensorArray(tf.int32, 1, dynamic_size=True)    updates = tf.TensorArray(tf.float32, 1, dynamic_size=True)    idx = 0    for i in tf.range(N):        for j in tf.range(tf.shape(y_true)[1]):            if tf.equal(y_true[i][j][2], 0):                continue            anchor_eq = tf.equal(                anchor_idxs, tf.cast(y_true[i][j][5], tf.int32))            if tf.reduce_any(anchor_eq):                box = y_true[i][j][0:4]                box_xy = (y_true[i][j][0:2] + y_true[i][j][2:4]) / 2                anchor_idx = tf.cast(tf.where(anchor_eq), tf.int32)                grid_xy = tf.cast(box_xy // (1/grid_size), tf.int32)                indexes = indexes.write(                    idx, [i, grid_xy[1], grid_xy[0], anchor_idx[0][0]])                updates = updates.write(                    idx, [box[0], box[1], box[2], box[3], 1, y_true[i][j][4]])                idx += 1    return tf.tensor_scatter_nd_update(        y_true_out, indexes.stack(), updates.stack())def transform_targets(y_train, anchors, anchor_masks, classes):    outputs = []    grid_size = 13    anchors = tf.cast(anchors, tf.float32)    anchor_area = anchors[..., 0] * anchors[..., 1]    box_wh = y_train[..., 2:4] - y_train[..., 0:2]    box_wh = tf.tile(tf.expand_dims(box_wh, -2),                    (1, 1, tf.shape(anchors)[0], 1))    box_area = box_wh[..., 0] * box_wh[..., 1]    intersection = tf.minimum(box_wh[..., 0], anchors[..., 0]) * \    tf.minimum(box_wh[..., 1], anchors[..., 1])    iou = intersection / (box_area + anchor_area - intersection)    anchor_idx = tf.cast(tf.argmax(iou, axis=-1), tf.float32)    anchor_idx = tf.expand_dims(anchor_idx, axis=-1)    y_train = tf.concat([y_train, anchor_idx], axis=-1)    for anchor_idxs in anchor_masks:        outputs.append(transform_targets_for_output(            y_train, grid_size, anchor_idxs, classes))        grid_size *= 2    return tuple(outputs) # [x, y, w, h, obj, class]def preprocess_image(x_train, size):    return (tf.image.resize(x_train, (size, size))) / 255

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

yolo = YoloV3(classes=num_classes)load_darknet_weights(yolo, weightyolov3)yolo.save_weights(checkpoints)class_names =  ["person", "bicycle", "car", "motorbike", "aeroplane", "bus", "train", "truck",    "boat", "traffic light", "fire hydrant", "stop sign", "parking meter", "bench",    "bird", "cat", "dog", "horse", "sheep", "cow", "elephant", "bear", "zebra", "giraffe",    "backpack", "umbrella", "handbag", "tie", "suitcase", "frisbee", "skis", "snowboard",    "sports ball", "kite", "baseball bat", "baseball glove", "skateboard", "surfboard",    "tennis racket", "bottle", "wine glass", "cup", "fork", "knife", "spoon", "bowl",    "banana","apple", "sandwich", "orange", "broccoli", "carrot", "hot dog", "pizza", "donut",    "cake","chair", "sofa", "pottedplant", "bed", "diningtable", "toilet", "tvmonitor", "laptop",     "mouse","remote", "keyboard", "cell phone", "microwave", "oven", "toaster", "sink",    "refrigerator","book", "clock", "vase", "scissors", "teddy bear", "hair drier", "toothbrush"]def detect_objects(img_path, white_list=None):    image = img_path     # Путь к изображению.    img = tf.image.decode_image(open(image, 'rb').read(), channels=3)    img = tf.expand_dims(img, 0)    img = preprocess_image(img, size)    boxes, scores, classes, nums = yolo(img)    img = cv2.imread(image)    img = draw_outputs(img, (boxes, scores, classes, nums), class_names, white_list)    cv2.imwrite('detected_{:}'.format(img_path), img)    detected = Image.open('detected_{:}'.format(img_path))    detected.show()    detect_objects('test.jpg', ['bear'])

Итог

В этой статье мы поговорили об отличительных особенностях YOLOv3 и её преимуществах перед другими моделями. Мы рассмотрели способ реализации с использованием TensorFlow 2.0 (TF должен быть не менее версией 2.0).

Ссылки

Подробнее..

3D реконструкция лица, или как получить своего цифрового двойника (Часть 1)

24.03.2021 20:12:56 | Автор: admin
Фотография (слева) и рендеринг 3D модели лица (справа)Фотография (слева) и рендеринг 3D модели лица (справа)

Поговорим об одном интересном методе восстановления 3D лица человека, которое почти не отличить от фотографий.

На хабре уже 2 года не появлялись статьи про лицевую 3D реконструкцию, и в Twin3D мы хотим постепенно заполнять этот пробел и регулярно выкладывать обзоры интересных статей, методов и наших собственных результатов на тему 3D digital human в целом.

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

Стоит отметить, что предыдущие статьи на хабре фокусировались на методах легкого создания 3D моделей лиц. Как обычно, тут есть trade-off между качеством и простотой получения 3D модели. В нашем цикле статей мы расскажем про 3 метода в порядке убывания сложности процесса сканирования: от специального сетапа с 24 камерами и 6 вспышками (об этом методе поговорим сейчас) до фотографии со смартфона.

Исторически реконструкция лица начиналась со стандартных методов multi-view stereo (об этом можно почитать в википедии, а также есть классная брошюра от Google), и понятно, что для таких методов требуется большое число фотографий с разных ракурсов. Эти методы основаны на математической оптимизации.

Терминология

Результатом базовой 3D реконструкции лица является следующее сочетание: геометрия + текстура альбедо + отражаемость и нормали (картинки будут ниже).

  • Геометрия это просто меш, т.е. упорядоченный набор связанных между собой точек в 3D.

  • Текстура альбедо это по сути набор пикселей, которые покрывают этот меш, настоящий цвет кожи.

  • Отражаемость и карта нормалей информация про каждый пиксель о том, как он отражает падающий свет (как сильно и в каком направлении).

Только при наличии всех эти трех компонент можно получить качественную фотореалистичную 3D модель лица.

Пара слов о методе

Метод реконструкции лица, о котором мы сейчас поговорим, описан в статье "Near-Instant Capture of High-Resolution Facial Geometry and Reflection", которая написана G. Fyffe, P. Graham, B. Tunwattanapong, A. Ghosh, P. Debevec и представлена на Eurographics 2016. Ее можно почитать здесь (дальше все картинки взяты оттуда). Эта работа примечательна тем, что авторам впервые удалось получить качество восстановления с точностью до пор кожи при почти мгновенном сканировании (66 мс). На заставке вы увидели результаты именно этой статьи. Статье уже 5 лет, но она стала своего рода классикой, да и авторы у нее широко известны в узких кругах (тот же Дебевек из Google). Статья написана довольно специфичным языком и с опусканием многих неочевидных деталей, так что пришлось немного поломать голову, чтобы ее понять и написать этот текст.

Как это работает

Для начала, авторы собрали весьма интересный риг из камер и вспышек. В нем 24 DSLR камеры CanonEOS 600D и 6 профессиональных вспышекSigma EM-140. Вспышки эти включаются последовательно, а вместе с ними одновременно фотографируют какое-то подмножество камер, так что в итоге каждая камера фотографирует ровно один раз. Камеры установлены и разбиты на группы так, чтобы оптимально покрыть всю область лица и для каждой точки увидеть хотя бы 3 разных отражения (дальше увидим, зачем). Реализована съемка с помощью микроконтроллера 80MHz Microchip PIC32. Авторы отдельно продумали, что весь этот процесс должен занимать меньше скорости моргания человека (~100 мс), так что от первой до последней фотографии проходит 66 мс, согласно статье.

Риг для съемки лицаРиг для съемки лица

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

Пайплайн алгоритмаПайплайн алгоритма

Исходный меш получается через обычный multiview stereo (например, Metashape). Но его качество довольно низкое (+- 2 мм), так что на основе карты нормалей этот меш в конце уточняется.

Поэтапное улучшение исходного мешаПоэтапное улучшение исходного меша

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

Диффузная и спекулярная карты нормалейДиффузная и спекулярная карты нормалей

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

\bf{L} \beta = \bf{P},

где L матрица направлений света для всех видов камер, \beta искомая нормаль (3-мерный вектор), P условные значения пикселей для этих точек и видов камер. После пристального взгляда на эту систему становится понятным, зачем нужно видеть точку хотя бы с трех ракурсов в противном случае систему однозначно не решить. Если хочется иметь карту разрешения 4096x4096, то соответственно нужно решить 16 млн таких систем, так что эффективное использование GPU здесь must have. Параллелизация таких вычислений отдельная нетривиальная задача.

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

Результаты

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

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

Сравнение фотографии (слева) и рендеринга 3D модели под тем же углом и освещением (справа)Сравнение фотографии (слева) и рендеринга 3D модели под тем же углом и освещением (справа)

Если же мы посмотрим на рендеринг под новым ракурсом и освещением, то тут тоже всё весьма прилично.

Уточненный меш и рендеринг под новым ракурсом и освещениемУточненный меш и рендеринг под новым ракурсом и освещением

Итоги

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

  • Волосы на всех результатах люди просто в шапочках, ни у кого нет щетины

  • Отдельные артефакты уши, ноздри носа и другие слабо видимые места никаким специальным образом не процессятся

  • Глаза они, конечно, не подойдут для игр или кино :)

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

Подробнее..

Эволюция методов mesh denoising от простых фильтров до 3D глубокого обучения

14.05.2021 20:06:19 | Автор: admin

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

Зачем нужен mesh denoising?

С помощью технологии трехмерного сканирования можно получить 3D-модель реального объекта. Но знаете ли вы, что почти всегда такие объекты содержат шумы и неточности? В Twin3d мы сканируем людей (и не только) и с помощью фотограмметрии получаем 3D-модели, которые дальше необходимо обрабатывать в зависимости от конечной цели их использования. Естественно, от шумов надо избавляться, чтобы применять виртуальную модель человека в кино/играх/рекламе. Нужно много чего еще делать, но об этом мы поговорим потом.

Сканирование объектов и получение их 3D-моделей используется не только для создания виртуальных людей. Это популярно в reverse engineering для получения CAD-деталей без чертежей, где нужна большая точность реконструкции и шумы недопустимы. Также как людей и CAD-детали, можно сканировать реальные объекты одежду, обувь, аксессуары, что сейчас очень распространено в связи с созданием виртуальных примерочных. В таких случаях тоже хочется сделать вещь визуально идеальной, поэтому отсканированный объект необходимо обрабатывать.

Процесс устранения шума с 3D-моделей, полученных после сканирования, получил название mesh denoising. Иногда можно встретить слово smoothing, что означает просто сглаживание. 3D-моделлеры пользуются профессиональным ПО для решения данной задачи, но при этом они тратят достаточно много времени, чтобы убрать все неровности и шероховатости поверхности вручную. А как это делается без вмешательства 3D-моделлера? С помощью методов, которые мы рассмотрим далее.

С чего все начиналось

Когда-то были фильтры Просто сглаживающие фильтры, которые берут координаты вершин меша и усредняют по соседним вершинам (Laplacian smoothing, Taubin smoothing).

Laplacian smoothingLaplacian smoothing

В 2003 году появляется Bilateral mesh denoising расширение билатерального фильтра (который использовался для сглаживания шума на 2D картинках) на трехмерные полигональные сетки. Суть остается та же усредняются координаты вершин, но уже немного умнее: используются при этом как координаты вершин, так и нормали вершин. Еще через 7 лет придумали применять такой билатеральный фильтр не к вершинам, а к нормалям граней (Bilateral normal filtering for mesh denoising), что значительно увеличило качество сглаживания.

Итеративный процесс вычисления новой нормали с помощью Bilateral Normal Filtering заключается в следующем:

n_i^k=\Lambda(\sum_{f_j\in N_i}A_jW_s(||c_i-c_j||)W_r(||n_i-n_j||)n_j),

где N_i набор соседних граней для грани f_i , n_j нормаль грани f_j , A_j площадь грани f_j , c_j центроид грани f_j (точка пересечения медиан треугольника), W(x)=exp(-x^2/(2\sigma^2)) гауссиана, \Lambda оператор нормализации.

Билатеральный фильтр является средним взвешенным с весом, состоящим из нескольких частей. W_s(x) для грани f_i определяет значимость грани f_j в терминах удаленности друг от друга чем больше расстояние, тем меньше вес. Аналогично с W_r(x) , только на вес влияет не расстояние между гранями, а разница между векторами нормалей грани. Также учитывается значение площади грани A_j

В 2015 году улучшают подход с билатеральными фильтрами с появлением Guided Mesh Normal Filtering, где используется направляющая нормаль для сглаживания.

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

Наряду с фильтрами развивались подходы mesh denoising, основанные на оптимизации. Так, например, в работе Mesh Denoising via L0 minimization авторы максимизируют плоские поверхности меша и постепенно устраняют шум, не сглаживая при этом углы. Данный подход хорош в случаях, когда все отсканированные объекты CAD-модели с правильными геометрическими формами.

Первые попытки обучения алгоритмов для mesh denoising

Настоящим прорывом в 2016 году стала работа Mesh Denoising via Cascaded Normal Regression, в которой впервые использовались данные для обучения алгоритма. Прежде всего, авторы создали соответствующий датасет из noisy и ground truth (GT) 3D-моделей с разным шумом (доступен по ссылке проекта).

Датасет состоит из синтетических данных (Synthetic) и полученных с помощью различных сканеров (Kinect v1, Kinect v2, Kinect Fusion). В Synthetic в качестве noisy моделей используются модели с искусственно сгенерированным шумом (используется небольшое случайное смещение координат). Отсканированные данные уже содержат шум, зависящий от параметров сканеров и используемых в них технологий. Для получения GT моделей для реальных сканов использовался сканер Artec Spider c порядком точности, превышающим Microsoft Kinect.

Примеры из датасетаПримеры из датасета

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

Cascaded Normal Regression PipelineCascaded Normal Regression Pipeline

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

После этого появилась работа NormalNet: Learning based Guided Normal Filtering for Mesh Denoising. Основная идея обучать сверточную нейронную сеть для определения направляющей нормали, после применять Guided Mesh Normal Filtering. Для определения направляющей нормали происходит вокселизация локальной структуры каждой грани меша (voxel объемный пиксель), чтобы привести его к упорядоченному представлению и иметь возможность использовать CNN. Общий пайплайн работы представлен на картинке ниже.

NormalNet pipelineNormalNet pipeline

Следующий рисунок иллюстрирует архитектуру сетки в NormalNet. На вход каждой CNN подаются 4-мерные массивы, полученные после вокселизации, содержащие информацию о координатах вершин набора граней и усредненной нормали этого набора. Наборы граней (патчи) в терминах данной работы это некоторое количество соседних граней для одной грани. При создании нейронной сети авторы вдохновлялись архитектурой ResNet, которая позволяет учитывать информацию, полученную с предыдущих слоев сетки. Для составления карты признаков для каждого патча входные данные обрабатываются тремя блоками с конволюциями. Выходные полносвязные слои преобразовывают карту признаков к трехмерному вектору, который является направляющей нормалью для одного патча.

Архитектура CNN в NormalNetАрхитектура CNN в NormalNet

Таким образом, итеративно получая с помощью вокселизации и CNN направляющие нормали, а после применяя Guided Normal Filtering, авторы строят процесс устранения шума. Грубо говоря, в данном подходе происходит очередное улучшение качества сглаживающего фильтра Guided Normal Filtering.

Наконец, от использования билатеральных фильтров решили отказаться в работе DNF-Net: a Deep Normal Filtering Network for Mesh Denoising и предоставили полное управление процессом фильтрации шума нейронным сетям. Это первая работа, которая делает end-to-end процесс фильтрации без ручного составления признаков. С помощью отдельных логических частей нейронной сетки на основе ResNet авторы формируют карты признаков для всего меша с шумом и отдельно для шума, а после их обработки получают обновленные нормали граней.

Архитектура DNF-NetАрхитектура DNF-Net

На вход DNF-Net принимает патчи граней: нормали и индексы соседних граней. С помощью блока multi-scale feature embedding unit составляется карта признаков для каждого патча. В этом блоке анализируется локальная геометрическая структура меша на разных масштабах. На трех уровнях (см. картинку ниже) составляются три разные локальные карты признаков , которые учитывают разное количество соседних граней (по возрастанию). После их конкатенации и прогона через полносвязные слои нейронной сети получается глобальная карта признаков F для каждой грани меша.

Multi-scale Feature Embedding UnitMulti-scale Feature Embedding Unit

Впоследствии с помощью residual learning unit аналогичным образом извлекается карта признаков для шума. С помощью KNN (K-Nearest Neighbors) происходит поиск k похожих граней в представлении созданных признаков. Из продублированной k раз исходной карты признаков вычитаются карты признаков похожих граней. Аналогично используются полносвязные слои нейронной сетки формирования шумной карты признаков.

Residual learning unitResidual learning unit

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

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

Deep learning и свертки на графах

Чтобы избавиться от ограничений в виде числа параметров и соседей, необходимо работать с целым мешем, а не отдельно составленными патчами. А для построения сложных архитектур нейронных сетей с хорошей обобщающей способностью, надо как-то привести нерегулярное представление меша к регулярному. Во всей области 3D DL существует несколько популярных подходов для этого: вокселизация меша и представление меша как графа.

Так, в работе Mesh Denoising with Facet Graph Convolutions был предложен еще один end-to-end pipeline для устранения шума с помощью представления мешей как графов, только вместо натурального представления графа полигональной сетки (вершины, связанные с вершинами) используется другое грани, связанные с гранями. Основная идея сгенерировать граф и запустить на нем сверточную нейронную сеть.

Facet Graph Convolution pipelineFacet Graph Convolution pipeline

В основе этого метода лежит архитектура U-Net, которой на вход подаются координаты граней и соответствующих нормалей созданного графа, а на выходе получаются скорректированные нормали граней.

Архитектура Graph Convolution NetworkАрхитектура Graph Convolution NetworkСвертка графаСвертка графа

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

Заключение

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

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

В Twin3d мы занимаемся разными задачами, и mesh denoising входит в их число. В будущих статьях будем рассказывать уже о своих разработках, не пропустите ;)

Подробнее..

Как машины учатся эмоциональному поведению

09.04.2021 10:09:31 | Автор: admin
Нередко при взаимодействии с техникой люди проявляют эмоции: мы можем злиться на сломавшийся банкомат или умиляться пронырливости робота-пылесоса. Да, мы общаемся с роботами, но не стоит оценивать это общение как одностороннее: в логику аватаров, которые компании используют для взаимодействия с пользователем, часто бывает встроен навык понимания эмоций, и даже их проявления. Обычно это нужно, чтобы сделать общение приятным для клиента. Как же это всё работает?


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

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

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

Что такое эмоциональный интеллект?


Эмоциональный интеллект это умение распознавать свои и чужие эмоции и управлять ими. Корни этой концепции можно найти в трудах Чарльза Дарвина, который считал, что умение управлять эмоциями появилось в результате эволюции. Следующая попытка научного рассмотрения эмоций приходится на начало 1920-х годов: тогда появились первые работы, в которых способность людей выстраивать социальные взаимодействия рассматривалась как особый вид интеллекта. Тогда же американский психолог и педагог Эдвард Торндайк (Edward Lee Thorndike), который, кстати, изобрёл кривую обучения, ввёл понятие социальный интеллект, который определил как способность понимать людей, мужчин и женщин, мальчиков и девочек, умение обращаться с людьми и разумно действовать в отношениях с ними. Если задуматься, социальный интеллект имеет много общего с эмоциональным интеллектом, потому что существование последнего возможно только в обществе. Несмотря на то, что социальный интеллект это многомерное явление, исследователи постарались создать линейную шкалу, чтобы сравнивать людей по его уровню (да, люди часто стараются квантифицировать встречающиеся им явления, но в социальной сфере это особенно сложно). В 1926 году был создан один из первых тестов для измерения социального интеллекта Тест университета Джорджа Вашингтона на социальный интеллект. В последующее десятилетие предпринимались и другие попытки создания подобных тестов.

Термин эмоциональный интеллект (emotional intellect) впервые появился в работе Майкла Белдока (Michael Beldoch), написанной в 1964 году. Расцвет теории эмоционального интеллекта пришёлся на 1980-е и 1990-е годы. В 1983 году Говард Гарднер (Howard Earl Gardner) описал популярную модель интеллекта, где разделил навыки на внутриличностные и межличностные. С тех пор концепция интеллекта, связанного с социальными взаимодействиями, глубоко укоренилась в научном сообществе. В 1985 году Уэйн Пэйн (Wayne Leon Payne) защитил свою диссертацию, на основе которой написал статью, посвящённую развитию эмоционального интеллекта. В 1988 году психолог Рувен Бар-Он (Reuven Bar-On) ввёл понятие эмоционального коэффициента (EQ, emotional quotient), по аналогии с популярным показателем IQ. Современное представление об эмоциональном интеллекте окончательно оформилось в статье Эмоциональный интеллект американских социальных психологов Питера Саловея (Peter Salovey) и Джона Майера (John D. Mayer), увидевшей свет в 1990 году.

Эмоциональные вычисления


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

Считается, что направление появилось в 1995 году с выходом в свет работы профессора Розалинды Пикард (Rosalind Wright Picard) из Медиа-лаборатории MIT.


Розалинда Пикард. Источник изображения.

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

Модели, предназначенные для распознавания эмоций, в наши дни обычно представляют собой рекуррентные, свёрточные или свёрточно-рекуррентные нейронные сети. А после 2017-го года к ним добавились ещё и трансформеры. Причём стоит заметить, что характерными маркерами эмоций могут быть не только лингвистические показатели (смысл слов и выражений), но и экстралингвистические, такие как тон голоса, интонации, выражение лица, динамика тела и жестикуляция. Эмоциональная информация разбросана по разным каналам: мы можем найти её и в звуке, и в видео. А если рассматривать сенсорную сферу, проявление эмоций можно найти в касаниях, и это тоже может быть важно для роботов. Ещё один канал, в котором эмоции проявляются на уровне активности мозга энцефалографический. Его можно использовать, например, при проектировании продвинутого детектора лжи. Одним из подразделов эмоционального ИИ является сентимент-анализ, цель которого определить, какая смысловая окраска у высказывания: негативная, нейтральная или позитивная. В наши дни эту задачу решают при помощи трансформерных моделей, подобных BERT. К сожалению, объём поста не позволяет рассказать об этом сейчас, и мы посвятим сентимент-анализу один из следующих материалов. В этом посте из всех каналов, которые задействуются при общении человека с машиной или людей друг с другом, мы рассмотрим только аудиальный канал.


Автор изображения: ioat/Shutterstock.com

Распознавание эмоций в речевом канале это одна из наиболее распространенных задач в области эмоционального ИИ. Чаще всего для построения модели применяются глубокие сети, которым на вход подаются различные представления звукового сигнала (спектрограммы, хромаграммы, последовательности наборов мел-кепстральных коэффициентов и т.п.). Такие модели решают задачу классификации или регрессии. Чтобы обучить модель, распознающую эмоциональную окраску речи, нужно подготовить обучающую выборку. А для этого нужно условиться, какое представление эмоций мы будем использовать. Возможные классификации предоставляет язык разметки EmotionML 1.0. Он содержит несколько эмоциональных словарей, основывающихся на научных классификациях. Одна из них это большая шестёрка эмоций (отвращение, печаль, гнев, страх, счастье и удивление) предложена в 1972 году в работе американского психолога Пола Экмана (Paul Ekman). Другой эмоциональный словарь, предусмотренный EmotionML 1.0, основан на концепции соответствия эмоций тенденциям действия [action tendencies], разработанной голландским психологом Нико Фрейдой (Nico Henri Frijda). Этот словарь включает в себя 12 эмоций: безразличие, высокомерие, гнев, желание, интерес, наслаждение, отвращение, покорность, смирение, страх, удивление и шок.

Есть много разных словарей, но наивным было бы считать, что их авторы просто соревновались друг с другом в составлении бессистемных списков эмоций. В основе больших эмоциональных словарей обычно лежит анализ лингвистических данных (статистики использования слов, используемых для передачи эмоциональной информации в различных языках). При этом сами словари нередко являются лишь побочным продуктом исследований, цель которых построить эмоциональное пространство, то есть такое представление, в котором каждая эмоция будет разделена на несколько независимых друг от друга компонент. Одну из попыток построить такое
пространство предпринял Джеймс Рассел (James A. Russell) в 1980 году. Он разложил эмоции по двум шкалам: первая, удовольствие-неудовольствие, характеризует позитивный или негативный характер эмоции, и вторая, возбуждение-сон, характеризует активность или пассивность психического состояния. Эта работа вызвала закономерную критику: мир эмоций не сводим к двумерному пространству. Критики предложили свою модель, уже не двухмерную, а в виде сетки, под названием GRID [сетка, решётка].

Так как у нас есть эмоциональный континуум, вместо задачи классификации, когда у нас есть несколько классов эмоций, мы сталкиваемся с задачей регрессии. В данном случае от модели требуется не предсказание метки конкретного эмоционального класса в соответствии с выбранным эмоциональным словарём, а оценка величины каждой из выбранных компонент эмоции. Для этой цели в стандарте EmotionML 1.0 введены системы измерений эмоций. Кроме упомянутой нами системы GRID (FRSE) с четырьмя шкалами, стандартом предусмотрена возможность использования пространства Удовольствие-Возбуждение-Доминирование (Pleasure, Arousal, and Dominance, PAD), основанного на трёх соответствующих шкалах, а также плоской шкалы интенсивности эмоции.

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

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

К счастью, на данный момент сформировано уже некоторое количество эмоциональных датасетов, на 2009-й год их было порядка сотни. Однако таких же объёмных, как ImageNet или LibriSpeech, для эмоциональной речи в публичном доступе так и не появилось.

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

1. RAVDESS состоит из записей 24 профессиональных актёров (12 мужчин и 12 женщин), озвучивающих две фразы (у двери разговаривают дети, собаки сидят у двери) на английском языке с североамериканским акцентом в двух вариантах: речь и пение, по две озвучки на каждый вариант. В качестве эмоционального словаря разметки использована большая шестёрка эмоций, к которой было добавлено спокойствие. Каждая фраза представлена в датасете двумя уровнями эмоциональной интенсивности для каждой из эмоций, а также однократно с нейтральной окраской. Каждая запись присутствует в датасете в трёх модальностях (только видео, только звук, звук вместе с видео). RAVDESS считается одним из наиболее качественных датасетов эмоциональной речи, но лексически он крайне беден.

2. SAVEE состоит из записей четырёх актёров-мужчин, говорящих на родном для них британском английском. В качестве эмоционального словаря снова выбрана большая шестёрка, при этом фразы с нейтральной эмоциональной окраской записывались дважды. Сами фразы были выбраны из корпуса TIMIT (датасет с записями 630 дикторов), для каждой эмоции было взято 15 фраз, при этом из них три были общими для всех эмоций, десять разными для разных эмоций, но без эмоциональной специфики, а ещё две фразы были основаны на текстах, имеющих специфическую эмоциональную окраску для данной эмоции (например, Кто одобрил счёт с неограниченным расходным лимитом? для эмоции гнев). К сожалению, объём этого датасета крайне мал, что создаёт проблемы для разработчиков.

3. SEMAINE это аудиовизуальная база данных, ставшая одним из продуктов исследовательской программы по созданию Чувствующего искусственного слушателя (Sensitive Artificial Listener, SAL) аудиовизуальной диалоговой системы, способной вовлечь человека в длительный эмоционально окрашенный разговор. По сути разговор с агентом SAL для человека напоминает обычный разговор при помощи системы видеосвязи с той лишь разницей, что собеседником является виртуальный персонаж, внешний облик которого (лицо, мимика, движения губ во время речи) в реальном времени генерируется при помощи библиотеки для трёхмерной визуализации. Данные, содержащиеся в базе SEMAINE, были получены в результате взаимодействия между пользователями и человеком-оператором, имитирующим чувствующего искушённого слушателя, а затем и ассистентом на базе нейросетевой модели. База включает записи 959 диалогов, в которых участвовало 150 человек. Длина каждой записи составляет около 5 минут. Все диалоги были расшифрованы и размечены при помощи эмоциональных меток (использовалась система с пятью шкалами и 27 эмоциональными классами). Для части записей присутствует разметка при помощи Системы кодирования лицевых движений (FACS). Используя FACS, можно с лёгкостью отличить, например, дежурную улыбку Pan-Am (называется в честь авиакомпании Pan-American Airways, стюардессы которой должны были улыбаться каждому пассажиру) от искренней улыбки Дюшена. Один из недостатков этого датасета в том, что различные эмоции представлены в SEMAINE крайне неравномерно, также никак не был сбалансирован ни состав участников исследования, ни лексическая основа диалогов. Тем не менее, нельзя не отметить удивительную детальность разметки.

4. TESS. В 1966 году исследователи из Северо-Западного университета разработали так называемый Слуховой тест 6, предназначенный для измерения чувствительности слуха пациентов. Набор фраз, используемых в тесте, состоит из так называемой фразы-носителя Скажи слово... и набора из 200 различных слов, которые добавляются к фразе-носителю. Исследователи из Университета Торонто использовали этот же набор текстов, при этом каждая из фраз произносилась двумя актрисами (26 и 64 лет; обе были из региона Торонто, являлись носительницами английского языка, имели высшее и высшее музыкальное образования) с семью различными типами эмоциональной окраски (использовалась всё та же большая шестёрка эмоций с добавлением нейтральной окраски). Таким образом, в сумме было получено 200 7 2 = 2 800 записей. Этот весьма скромный по размерам датасет, тем не менее, нередко используется исследователями и в наши дни.

5. EMO-DB это германоязычный массив данных, впервые представленный на конференции InterSpeech-2005. На протяжении многих лет он пользовался большой популярностью у исследователей эмоциональной речи. Десять актёров (5 женщин и 5 мужчин) имитировали эмоции, произнося по 10 предложений (5 коротких и 5 более длинных), относящихся к повседневному лексикону. Помимо звука были записаны электроглоттограммы. Электроглоттография основана на измерении динамики электрического сопротивления гортани во время произнесения фраз, что достигается при помощи пары электродов, располагаемых на передней поверхности шеи по обе стороны щитовидного хряща. 10 актёров 10 предложений 7 эмоций (включая нейтральную) дают нам 700 записей, однако часть записей была выполнена повторно, поэтому в базе содержится на 100 записей больше. Все записи были подвергнуты оценке с привлечением 20 оценщиков. После этого в записях со средним уровнем узнавания эмоции более 80% и средней оценкой убедительности более 60% разметчики дополнительно оценили интенсивность проявления эмоции. По современным меркам этот датасет невелик и может быть использован разве что в учебных целях.

6. IEMOCAP это массив, созданный Лабораторией анализа и интерпретации речи Университета Южной Калифорнии, включающий в себя записи диалогов (спонтанных и на основе заранее подготовленных сценариев) десяти участников. Данные состоят из аудиозаписи с расшифровкой, видео, а также подробной информации о выражении лица и движениях рук, а также эмоциональной разметки (большая шестёрка + другая эмоция + нейтральная окраска, а также оценка эмоций по трём шкалам валентность, активация и доминирование). Общий объём корпуса составляет около 12 часов.

7. RUSLANA первая открытая русскоязычная база данных эмоциональной речи. Была создана в 2002 году. RUSLANA содержит записи 61 человека (12 мужчин и 49 женщин), которые произносили десять предложений с выражением следующих эмоциональных состояний: удивление, счастье, гнев, грусть, страх и нейтрально (без эмоциональной окраски). Таким образом, база содержит в сумме 61 10 6 = 3 660 записей. Хотя с момента появления RUSLANA свет увидели ещё несколько открытых русскоязычных эмоциональных датасетов, например, аудиовизуальный RAMAS и весьма внушительный по объёму (более 20 000 записей) набор эмоциональной детской речи EmoChildRu, открытых датасетов взрослой эмоциональной речи, превосходящих RUSLANA по объёму, до сегодняшнего дня так и не создано.

Стоит заметить, что на игрушечных эмоциональных датасетах, как RAVDESS, TESS, EMO-DB, IEMOCAP результаты улучшаются по несколько раз в год. Вы можете сами убедиться в этом, набрав в поисковой системе название соответствующего датасета и аббревиатуру SOTA (state-of-the-art, уровень развития, лучший результат по какому-либо критерию). Однако у этих улучшений иногда бывает проблема с воспроизводимостью, ввиду чего к результатам без публикации исходного кода следует относиться с осторожностью. Чтобы избежать возможных ошибок или неоднозначностей, многие исследователи предпочитают публиковать не только статьи, но и кодовую базу своих проектов. Крупнейшим каталогом таких публикаций является ресурс paperswithcode.com, позволяющий найти работы, устанавливающие SOTA для самых разных задач машинного обучения, в том числе и для задачи распознавания эмоций.

Сферы применения эмоционального ИИ


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

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

Эмоциональным может быть не только смысл и синтаксис фразы, эмоции могут быть у интонационной окраски произносимого. Были предложены модели, способные в режиме обучения без учителя выучивать для каждой фразы некоторые стилистические векторы. К числу таких моделей относятся такие модели, как Tacotron TP-GST (Глобальные стилевые токены, предсказанные на основе текста) и GMVAE-Tacotron (Вариационный автокодировщик на основе смеси гауссовских распределений). Используя векторы, выученные моделью для фраз обучающей выборки, в качестве библиотеки стилей, можно добиться неплохой управляемости стилистикой синтеза. (Мы уже рассказывали, как добивались человечности в звучании наших виртуальных ассистентов Салют при помощи связки Tacotron 2 и LPCNet.) При этом отдельная модель может быть использована для того, чтобы построить стилистический вектор фразы на основе семантической информации. То есть, проще говоря, обучить модель, которая будет, исходя из смысла фразы, выбирать для неё правильную интонацию.

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

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

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

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

16.04.2021 18:11:03 | Автор: admin

Оптимизаторы важный компонент архитектуры нейронных сетей. Они играют важную роль в процессе тренировки нейронных сетей, помогая им делать всё более точные прогнозы. Специально к старту нового потока расширенного курса помашинному и глубокому обучению, делимся с вами простым описанием основных методик, используемых оптимизаторами градиентного спуска, такими как SGD, Momentum, RMSProp, Adam и др.


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

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

Большинство популярных библиотек глубокого обучения, например PyTorch и Keras, имеют множество встроенных оптимизаторов, базирующихся на использовании алгоритма градиентного спуска, например SGD, Adadelta, Adagrad, RMSProp, Adam и пр.

Но почему алгоритмов оптимизации так много? Как выбрать из них правильный?

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

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

Обзор методов оптимизации на базе алгоритма градиентного спуска

Кривая потерь

Начнём с того, что рассмотрим на 3D-изображении, как работает стандартный алгоритм градиентного спуска.

Кривая потерь для алгоритма градиентного спускаКривая потерь для алгоритма градиентного спуска

На рисунке показана сеть с двумя весовыми параметрами:

  • На горизонтальной плоскости размещаются две оси для весов w1 и w2, соответственно.

  • На вертикальной оси откладываются значения потерь для каждого сочетания весов.

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

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

  • Работа алгоритма начинается с выбора некоторых случайных значений обоих весов и вычисления значения потери.

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

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

Вычисление градиента

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

Обновление параметра градиентного спускаОбновление параметра градиентного спуска

Градиент измеряет уклон и рассчитывается как значение изменения в вертикальном направлении (dL), поделённое на значение изменения горизонтальном направлении (dW). Другими словами, для крутых уклонов значение градиента большое, для пологих маленькое.

Вычисление градиентаВычисление градиента

Практическое применение алгоритма градиентного спуска

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

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

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

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

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

  • Весь ландшафт представляется алгоритму плоским во всех направлениях?

  • Алгоритм попадает в глубокую канаву? Как ему оттуда выбраться?

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

Трудности при оптимизации градиентного спуска

Локальные минимумы

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

Локальный минимум и глобальный минимумЛокальный минимум и глобальный минимум

Седловые точки

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

Седловая точкаСедловая точка

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

Алгоритм градиентного спуска при этом ошибочно полагает, что минимум им найден.

Овраги

Ещё одна головная боль алгоритма градиентного спуска пересечение оврагов. Овраг это протяжённая узкая долина, имеющая крутой уклон в одном направлении (т.е. по сторонам долины) и плавный уклон в другом (т.е. вдоль долины). Довольно часто овраг приводит к минимуму. Поскольку навигация по такой форме кривой затруднена, такую кривую часто называют патологическим искривлением (Pathological Curvature).

ОврагиОвраги

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

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

Первое улучшение алгоритма градиентного спуска стохастический градиентный спуск (SGD)

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

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

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

Ранее мы говорили, что кривая потерь получается за счёт изменения параметров модели с сохранением фиксированного набора входных данных. Однако если начать изменять входные данные, выбирая различные данные в каждом мини-пакете, значения потерь и градиентов также будут меняться. Другими словами, изменяя набор входных данных, для каждого мини-пакета мы получим собственную кривую потерь, которая немного отличается от других.

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

Второе усовершенствование алгоритма градиентного спуска накопление импульса (Momentum)

Динамическая корректировка количества обновлений

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

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

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

  • Скорректировать градиент.

  • Скорректировать значение скорости обучения.

SGD с функцией накопления импульса и обычный SGD

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

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

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

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

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

Функция накопления импульса использует при работе экспоненциальное скользящее среднее, а не текущее значение градиента.

Переходы через овраги с помощью функции накопления импульса

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

Функция накопления импульса помогает преодолевать оврагиФункция накопления импульса помогает преодолевать овраги

С помощью функции накопления импульса можно сгладить зигзагообразные скачки алгоритма SGD.

  • Для первого параметра с крутым уклоном большое значение градиента приведёт к резкому повороту от одной стороны оврага к другой. Однако на следующем шаге такое перемещение будет скомпенсировано резким поворотом в обратном направлении.

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

Вот некоторые примеры алгоритмов оптимизации, использующих функцию накопления импульса в разных формулах:

  • SGD с накоплением импульса

  • Ускоренный градиент Нестерова

Третье усовершенствование алгоритма градиентного спуска изменение скорости обучения (на базе значения градиента)

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

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

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

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

Данная функциональность реализована в нескольких алгоритмах оптимизации, использующих разные, но похожие методы, например Adagrad, Adadelta, RMS Prop.

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

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

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

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

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

Четвёртое усовершенствование алгоритма градиентного спуска изменение скорости обучения (на базе тренировочной выборки)

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

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

Заключение

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

Узнайте, как прокачаться и в других специальностях или освоить их с нуля:

Другие профессии и курсы
Подробнее..

Распознавание дорожных знаков

24.04.2021 18:16:03 | Автор: admin

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

Набор данных дорожных знаков

В рамках этой статьи используется общедоступный набор данных, доступный вKaggle:GTSRB это мультиклассовая задача классификации одного изображения, которая проводилась на Международной совместной конференции по нейронным сетям (IJCNN) 2011. Набор данных содержит более 50 000 изображений различных дорожных знаков и классифицируется на 43 различных класса. Он весьма разнообразен: некоторые классы содержат много изображений, а некоторые классы - несколько изображений.

Изучение набора данных

В начале импортируем все необходимые библиотеки.

import osimport matplotlibimport numpy as npfrom PIL import Imagefrom tensorflow.keras.preprocessing.image import img_to_arrayfrom sklearn.model_selection import train_test_splitfrom keras.utils import to_categoricalfrom keras.models import Sequential, load_modelfrom keras.layers import Conv2D, MaxPool2D, Dense, Flatten, Dropoutfrom tensorflow.keras import backend as Kimport matplotlib.pyplot as pltfrom sklearn.metrics import accuracy_score

Для тренировки нейронной сети будем использовать изображения из папки train, которая содержит 43 папки отдельных классов. Инициализируем два списка:dataи labels. Эти списки будут нести ответственность за хранение наших изображений, которые мы загружаем, вместе с соответствующими метками классов.

data = []labels = []

Далее, с помощью модуля os мы перебираем все классы и добавляем изображения и их соответствующие метки в списокdataиlabels. Для открытия содержимого изображения используется библиотекаPIL.

for num in range(0, classes):    path = os.path.join('train',str(num))    imagePaths = os.listdir(path)    for img in imagePaths:      image = Image.open(path + '/'+ img)      image = image.resize((30,30))      image = img_to_array(image)      data.append(image)      labels.append(num)

Этот цикл просто загружает и изменяет размер каждого изображения до фиксированных 3030 пикселей и сохраняет все изображения и их метки в спискахdataиlabels.

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

data = np.array(data)labels = np.array(labels)

Форма данных - (39209, 30, 30, 3), означает, что имеется 39 209 изображений размером 3030 пикселей, а последние 3 означают, что данные содержат цветные изображения (значение RGB).

print(data.shape, labels.shape)(39209, 30, 30, 3) (39209,)

Из пакета sklearn мы используем метод train_test_split() для разделения данных обучения и тестирования, используя 80% изображений для обучения и 20% для тестирования. Это типичное разделение для такого объема данных.

X_train, X_test, y_train, y_test = train_test_split(data, labels, test_size=0.2, random_state=42)print(X_train.shape, X_test.shape, y_train.shape, y_test.shape)(31367, 30, 30, 3) (7842, 30, 30, 3) (31367,) (7842,) 

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

def cnt_img_in_classes(labels):    count = {}    for i in labels:        if i in count:            count[i] += 1        else:            count[i] = 1    return countsamples_distribution = cnt_img_in_classes (y_train)def diagram(count_classes):    plt.bar(range(len(dct)), sorted(list(count_classes.values())), align='center')    plt.xticks(range(len(dct)), sorted(list(count_classes.keys())), rotation=90, fontsize=7)    plt.show()diagram(samples_distribution)
Диаграмма распределенияДиаграмма распределения

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

def aug_images(images, p):    from imgaug import augmenters as iaa    augs =  iaa.SomeOf((2, 4),          [              iaa.Crop(px=(0, 4)),               iaa.Affine(scale={"x": (0.8, 1.2), "y": (0.8, 1.2)}),              iaa.Affine(translate_percent={"x": (-0.2, 0.2), "y": (-0.2, 0.2)}),              iaa.Affine(rotate=(-45, 45))              iaa.Affine(shear=(-10, 10))])        seq = iaa.Sequential([iaa.Sometimes(p, augs)])    res = seq.augment_images(images)    return resdef augmentation(images, labels):    min_imgs = 500    classes = cnt_img_in_classes(labels)    for i in range(len(classes)):        if (classes[i] < min_imgs):            add_num = min_imgs - classes[i]            imgs_for_augm = []            lbls_for_augm = []            for j in range(add_num):                im_index = random.choice(np.where(labels == i)[0])                imgs_for_augm.append(images[im_index])                lbls_for_augm.append(labels[im_index])            augmented_class = augment_imgs(imgs_for_augm, 1)            augmented_class_np = np.array(augmented_class)            augmented_lbls_np = np.array(lbls_for_augm)            imgs = np.concatenate((images, augmented_class_np), axis=0)            lbls = np.concatenate((labels, augmented_lbls_np), axis=0)    return (images, labels)X_train, y_train = augmentation(X_train, y_train)

После увеличения наш обучающий набор данных имеет следующую форму.

print(X_train.shape, X_test.shape, y_train.shape, y_test.shape)(36256, 30, 30, 3) (7842, 30, 30, 3) (36256,) (7842,)

Давайте еще раз проверим распределение данных.

augmented_samples_distribution = cnt_img_in_classes(y_train)diagram(augmented_samples_distribution)
Диаграмма распределения после аугментацииДиаграмма распределения после аугментации

На графика видно, что наш набор стал более сбалансирован. Далее из пакета keras.utils мы используем метод to_categorical для преобразования меток, присутствующих вy_trainиt_test, в one-hot encoding.

y_train = to_categorical(y_train, 43)y_test = to_categorical(y_test, 43)

Построение нейронной сети

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

Архитектура нашей модели:

  • 2 Conv2D слоя (filter=32, kernel_size=(5,5), activation=relu)

  • MaxPool2D слой ( pool_size=(2,2))

  • Dropout слой (rate=0.25)

  • 2 Conv2D слоя (filter=64, kernel_size=(3,3), activation=relu)

  • MaxPool2D слой ( pool_size=(2,2))

  • Dropout слой (rate=0.25)

  • Flatten слой, чтобы сжать слои в 1 измерение

  • Dense слой (500, activation=relu)

  • Dropout слой (rate=0.5)

  • Dense слой (43, activation=softmax)

class Net:  @staticmethod  def build(width, height, depth, classes):    model = Sequential()    inputShape = (height, width, depth)    if K.image_data_format() == 'channels_first':      inputShape = (depth, heigth, width)    model = Sequential()    model.add(Conv2D(filters=32, kernel_size=(5,5), activation='relu', input_shape=inputShape))    model.add(Conv2D(filters=32, kernel_size=(5,5), activation='relu'))    model.add(MaxPool2D(pool_size=(2, 2)))    model.add(Dropout(rate=0.25))    model.add(Conv2D(filters=64, kernel_size=(3, 3), activation='relu'))    model.add(Conv2D(filters=64, kernel_size=(3, 3), activation='relu'))    model.add(MaxPool2D(pool_size=(2, 2)))    model.add(Dropout(rate=0.25))    model.add(Flatten())    model.add(Dense(500, activation='relu'))    model.add(Dropout(rate=0.5))    model.add(Dense(classes, activation='softmax'))    return model

Обучение и проверка модели

Мы строим нашу модель вместе с оптимизатором Adam, а функция потерь это categorical_crossentropy, потому что у нас есть несколько классов для категоризации. Затем обучаем модель с помощью функции model.fit().

epochs = 25model = Net.build(width=30, height=30, depth=3, classes=43)model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])history = model.fit(X_train, y_train, batch_size=64, validation_data=(X_test, y_test), epochs=epochs)

Как вы можете видеть, наша модель обучалась в течении 25 эпох и достигла 93% точности на тренировочном наборе данных. С помощью matplotlib мы строим график для точности и потерь.

plt.style.use("plot")plt.figure()N = epochsplt.plot(np.arange(0, N), history.history["loss"], label="train_loss")plt.plot(np.arange(0, N), history.history["val_loss"], label="val_loss")plt.plot(np.arange(0, N), history.history["accuracy"], label="train_acc")plt.plot(np.arange(0, N), history.history["val_accuracy"], label="val_acc")plt.title("Training Loss and Accuracy")plt.xlabel("Epoch")plt.ylabel("Loss/Accuracy")plt.legend(loc="lower left")plt.show()
Training Loss and AccuracyTraining Loss and Accuracy

Тестирование модели на тестовом наборе

Набор данных содержит папку Test, а в файле Test.csv есть сведения, связанные с путем к изображению и метками классов. Мы извлекаем путь к изображению и метки из файла Test.csv с помощью фреймворка Pandas. Затем, мы изменяем размер изображения до 3030 пикселей и делаем массив numpy, содержащий все данные изображения. С помощью accuracy_score из sklearn metrics проверяем точность предсказаний нашей модели. Мы достигли 96% точности на этой модели.

y_test = pd.read_csv('Test.csv')labels = y_test["ClassId"].valuesimgs = y_test["Path"].valuesimages=[]for img in imgs:    image = Image.open(img)    image = image.resize((30,30))    images.append(img_to_array(image))X_test=np.array(images)pred = model.predict_classes(X_test)print(accuracy_score(labels, pred))0.958590657165479
Подробнее..

О том как мы научили машину определять пол человека по его почерку

16.06.2021 16:13:28 | Автор: admin

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

Однако не будем углубляться в историю

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

Для начала кратко разберем понятие почерк:

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

В свою очередь, он имеет следующие основные свойства:

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

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

  3. Временная изменчивость почерка (возможность изменения письменно двигательного функционального динамического комплекса видоизменяться в зависимости от возраста);

  4. Типологическое своеобразие.

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

Но как понять устойчив ли тот или иной признак?

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

Но что мы понимаем под понятием признака?

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

  1. Общие (относительное размещение текста, форма линий письма, наклон, разгон, размер и степень связанности почерка, нажим и так далее);

  2. Диагностические. Разделяются на:

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

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

    - Специфические диагностические признаки (зеркальность движений, выполнение букв по типу печатных и так далее);

  3. Частные. Делятся на:

    - Сложность движения при выполнении,

    - Форма движений при выполнении,

    - Направление движений при выполнении,

    - Протяженность при выполнении,

    - Количество движений при выполнении

    - Вид движений при выполнении,

    - Последовательность движений при выполнении,

    - Относительное размещения;

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

Узнав какие признаки существуют, нужно выделить устойчивые признаки, которые тем или иным образом могут быть связанны с полом исполнителя рукописи. К счастью мы можем подсмотреть в уже существующую методику дифференциации рукописей на мужские и женские по высоковыработанным почеркам, основанная на вероятностном моделировании (см. Судебно-почерковедческая экспертиза Ч 2, М., ВНИИСЭ, 1971г., с. 223-236) (P. S. это не единственная методика подобного рода). В данной методике изложены 208 признаков почерка с различными коэффициентами. Проще говоря, находим в тексте как можно больше перечисленных в методике признаков, суммируем их коэффициент и получаем определенную величину, по которой мы с определенной долей вероятности можем определить пол исполнителя рукописи.

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

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

Для решения мы будем использовать Keras и CoreML для удобного использования.

Начнем со сбора данных!

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

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

Пробную архитектуру возьмем VGG19, а суммарный объем данных 1400 изображений.

Результатом обучения стала 92% точность определения признака.

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

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

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

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

Рабочая область программного компелкса ФросяРабочая область программного компелкса Фрося

Список источников и литературы

  1. Судебно-почерковедческая экспертиза. Общая часть. Вып. I, II (Методическое пособие для экспертов, следователей, судей), М., ВНИИСЭ, 1988-1989.

  2. Почерковедение и почерковедческая экспертиза. Учебник / под ред. В. В. Серегина. Волгоград: ВА МВД России, 2012.

  3. Судебно-почерковедческая экспертиза. Особенная часть. Исследование рукописных текстов / под ред. В.Ф. Орловой. М., Наука, 2007.

  4. Аверьянова, Т.В. Судебная экспертиза: курс общей теории / Т.В. Аверьянова. М.: Норма, 2006. 479 с.

  5. Кошманов П.М. Компьютерные технологии в судебно-почерковедческой экспертизе: учеб, пособие / П.М. Кошманов. Волгоград: ВА МВД России, 2008. 72 с.: ил.

  6. Бобовкин М. В. Теория и практика судебно-диагностической экспертизы письма лиц, находящихся в психопатологическом состоянии. Диссертация доктора юридических наук. Волгоград, 2005. 466 с.

Подробнее..

Как мы сделали акселератор инференса нейронных сетей для ЦОД с 64 чипами Intel Movidius

11.05.2021 10:09:42 | Автор: admin

Некоторое время назад мы искали оптимальное аппаратное и программное обеспечение для исполнения нейронных сетей в ЦОД и "на краю" (edge computing). В рамках нашего исследования мы протестировали множество устройств, от процессоров до встроенной графики iGPU и GPGPU различных производителей. С результатами исследования можно ознакомиться по ссылке.

В рамках этого исследования нас заинтересовал VPU Intel Movidius (MyriadX). При вычислениях "на краю" и использовании фреймворка Intel OpenVINO он позволял нам увеличивать число потоков или каналов путем дооснащения существующих устройств без какой-либо модификации аппаратной и программной базы. По умолчанию мы использовали встроенную графику, например, Intel HD или Iris Plus 655, но если FPS и число потоков необходимо было увеличивать, то промышленные ПК можно было дооснастить VPU. Это давало возможность сохранить единообразие множества устройств при изменяемом числе потоков. В качестве примера можно привести транспортную отрасль и подсчет пассажиров на борту автобусов. Автобусы бывают с 2, 3 и 4 дверьми. И если для двух дверей достаточно встроенной графики, то для четырех необходимо увеличение FPS, что достигалось расширением готового решения при помощи VPU формата M.2.

Вот так выглядело наше устройство для исполнения нейронных сетей "на краю" с Intel Movidius:

ComBox Outdoor Box SquaredComBox Outdoor Box Squared

Сегодня для инференса "на краю" интерес представляют решения от компании AAEON, в частности VPC-3350S, VPC-3350AI:

AAEON VPC-3350SAAEON VPC-3350S

Они отличаются расширенным температурным диапазоном эксплуатации -20+70 градусов, наличием возможности расширения двумя VPU Movidius, широкой линейкой поддерживаемых процессоров от Intel Atom x5 E3940 до Pentium N4200 или Atom x7 E3950, а также наличием 4 PoE Ethernet портов для подключения камер или иного оборудования.

С использованием Movidius в IoT все было более или менее понятно, но нас заинтересовала хотя бы теоретическая возможность масштабирования и применения этих компактных энергоэффективных чипов в ЦОД в виде ускорителей инференса PCIe формата.

Суммарный объем рынка публичных и частных облаков в России по данным IDC с 2019 года растет минимум на 25% в год, что на 2019 год составляло $1,72 млрд., а на 2020 год увеличилось до $2,2 млрд. Доля публичных облаков в общем объеме рынка в 2019 году 84,6%. Несмотря на то, что облачный рынок претерпел ряд структурных изменений в 2020 году, рост продолжается с частичным, но постоянным увеличением объемов облачных вычислений в системах искусственного интеллекта прикладного уровня, например, видеоаналитике для обработки ранее сформированных видеоархивов.

После предварительной оценки рынка мы провели поиск имеющихся решений в формате PCIe. Все найденные на тот момент устройства содержали 4 или 8 Movidius на одну плату. Например, решения от AAEON:

AAEON AI CORE XP4/ XP8AAEON AI CORE XP4/ XP8

Общее назначение имеющихся устройств - инференса "на краю". И вот здесь родилась идея реализации собственного ускорителя инференса нейронных сетей для ЦОД с чипами Movidius высокой плотности.

Сейчас в этой сфере используются два основных устройства: GPGPU nVidia Tesla T4 и ускорители инференса Huawei Atlas 300. Альтернатив по производительности от компании Intel для дооснащения существующих систем или внедрения новых серверных решений нет. Возможное решение, сопоставимое по производительности и стоимости - это ускоритель на основе VPU Movidius (MyriadX) высокой плотности в форм-факторе PCIe с плотностью не менее 64 Movidius на каждой несущей плате.

Требования:

  • плотность чипов Movidius не менее 64 штук на каждую плату

  • наличие возможности изменения числа VPU на плате

  • минимально возможное энергопотребление

  • форм-фактор PCIe x4/x8

  • работа конечного устройства под управлением фреймворка Intel OpenVINO без каких-либо значимых доработок

  • исполнение под использование в серверных платформах

Концепт не заставил себя долго ждать:

ComBox x64 Movidius Blade BoardComBox x64 Movidius Blade BoardComBox x64 Movidius Blade BoardComBox x64 Movidius Blade Board

Результатом проектирования платы получилось устройство PCIe с размещенными на несущей плате кастомными разъемами для подключения дочерних плат с нанесенными на них VPU. Таким образом конечную плату можно использовать с числом VPU до 64 штук, кратно 8. На каждый разъем отведена 1 линия PCIe, а в рамках каждой дочерней платы устройства подключены через USB хаб.

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

Первые образцы прототипа:

ComBox x64 Movidius Blade BoardComBox x64 Movidius Blade Board

Дочерние платы (по 8 Movidius на каждой):

x8 Movidius blades for ComBox x64 Movidius boardx8 Movidius blades for ComBox x64 Movidius board

Для тестирования и отладки мы использовали платформу Supermicro SYS-1029TRT и рекомендуем ее по следующим причинам:

  • хорошее соотношения цена/качество

  • форм-фактора 1U (занимает 1 место в стойке)

  • наличие 4 портов PCIe x8/x16

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

Supermicro SYS-1029TRT с установленной платой ComBox x64 Movidius Blade BoardSupermicro SYS-1029TRT с установленной платой ComBox x64 Movidius Blade Board

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

Вид готового изделия:

ComBox x64 Movidius Blade BoardComBox x64 Movidius Blade Board

И первые первые промышленные образцы платы:

Каких итогов мы добились:

  1. Максимальная плотность VPU Movidius на одной плате в мире.

  2. Показатель в инференсе сверточных нейронных сетей (на примере Mobilenet v.2 SSD) - 2800 FPS.

  3. Энергопотребление платы не более 120 Вт при полной загрузке.

  4. Возможность использовать произвольное число дочерних плат и устанавливать по 8, 16, 24 и т.д. VPU в рамках одной несущей платы.

  5. Возможность запуска инференса под управлением фреймворка Intel OpenVINO с использованием MDL и HDDL плагинов.

Следующие планируемые шаги:

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

  2. Предоставление облачных мощностей для инференса в аренду на базе ComBox x64 Movidius Blade board.

Подробнее..

Математика за оптимизаторами нейронных сетей

07.06.2021 16:20:09 | Автор: admin
В этой статье мы поговорим о математике градиентного спуска, почему при обучении нейронных сетей применяется стохастический градиентный спуск и о вариации SGD (Stochastic Gradient Descent) с использованием скользящего среднего (SGD с momentum и Nesterov Accelerated Gradient).





Градиентный спуск



Предполагается, что вы знакомы с понятием нейронной сети, вы имеете представление, какие задачи можно решать с помощью этого алгоритма машинного обучения и что такое параметры (веса) нейронной сети. Также, важно понимать, что градиент функции это направление наискорейшего роста функции, а градиент взятый с минусом это направление наискорейшего убывания. - \nabla_\theta J(\theta) это антиградиент функционала,
где \theta параметры функции (веса нейронной сети), J(\theta) функционал ошибки.



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

\Delta \theta(t) = - \eta\nabla_\theta J(\theta(t)),

\theta(t + 1) = \theta(t) + \Delta \theta(t) = \theta(t)- \eta\nabla_\theta J(\theta(t)),

где t это номер шага, \eta размер шага обучения (learning rate). В результате шага оптимизации веса нейронной сети принимают новые значения.



Виды градиентного спуска


  • Пакетный градиентный спуск (batch gradient descent).

    При этом подходе градиент функционала обычно вычисляется как сумма градиентов, учитывая каждый элемент обучения сразу. Это хорошо работает в случае выпуклых и относительно гладких функционалов, как например в задаче линейной или логистической регрессии, но не так хорошо, когда мы обучаем многослойные нейронные сети. Поверхность, задаваемая функционалом ошибки нейронной сети, зачастую негладкая и имеет множество локальных экстремумов, в которых мы обречены застрять, если двигаться пакетным градиентным спуском. Также обилие обучающих данных, делает задачу поиска градиента по всем примерам затратной по памяти.
  • Стохастический градиентный спуск (stochastic gradient descent)

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

  • Mini-batch градиентный спуск

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


imageИсточник

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

SGD с импульсом и Nesterov Accelerated Gradient



Следующие две модификации SGD призваны помочь в решении проблемы попадания в локальные минимумы при оптимизации невыпуклого функционала.
image
Гладкая выпуклая функция
image
Функция с множеством локальных минимумов (источник)

Первая модификация



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

v(t) = \alpha v(t-1) + \eta \nabla_\theta J(\theta(t)),

\theta(t+1) = \theta(t) - v(t),

v(t) это накопленное среднее градиентов на шаге t, коэфициент \alpha \in [0,1] требуется для сохранения истории значений среднего (обычно выбирается близким к 0.9.



Вторая модификация


Nesterov accelerated gradient отличается от метода с импульсом, его особенностью является вычисление градиента при обновлении v(t) в отличной точке. Эта точка берётся впереди по направлению движения накопленного градиента:


v(t) = \alpha v(t-1) + \eta \nabla_\theta J( \theta(t) - \alpha v(t-1)),

\theta(t+1) = \theta(t) - v(t).


На картинке изображены различия этих двух методов.

image
источник

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

Заключение



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

Данная статья была написана в преддверии старта курса Математика для Data Science от OTUS.

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

ЗАПИСАТЬСЯ НА DEMO DAY
Подробнее..

Log-Sum-Exp Trick как свойства функций делают работу классификаторов реальной

19.06.2021 00:09:06 | Автор: admin


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

Логистическая регрессия


В классическом машинном обучение существует метод классификации под названием логистическая регрессия. Классификатор на основе логистической регрессии, в обычном случае, призван разделять данные на два класса. Например, классификатор электронной почты может выдавать значение 0.9, что может означать: 90% вероятность, письмо спам, и 10% не спам. При этом сумма вероятностей всегда даёт единицу. Логистическая регрессия выдаёт такие вероятности на основе выхода сигмоидной функции.

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

Мультиклассовые нейронные сети


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

\frac{e^{x_i}}{\sum_{j=0}^N{e^{x_j}}} = p_i, \, i \in [0, N].

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



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

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

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

Проблема переполнения при вычислении Softmax


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

import numpy as npx = np.array([-100, 1000, -100, 5, 10, 0.001])exp_x = np.exp(x)print("Возведение в степень X: ", "\n", exp_x)print("Сумма экспонент: ", "\n", np.sum(exp_x))print("Третья вероятность: ", np.exp(x[3]) / np.sum(exp_x))

Вывод:

Возведение в степень X:   [3.72007598e-44            inf 3.72007598e-44 1.48413159e+02 2.20264658e+04 1.00100050e+00]Сумма экспонент:  infТретья вероятность:  0.0

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

Log-Sum-Exp


Для решения проблемы поставленной выше предлагается воспользоваться функцией

LSE(X) = \log\left(\sum_{j=0}^N e^{x_j}\right) Log-Sum-Exp функция.


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

image (источник)
Как видно на картинке выше, lse(x) всюду гладкая, в отличии от max(x) функция.

Вариант Log-Sum-Exp без переполнения


Если попытаться вычислить функцию LSE от вектора x, то мы так же столкнемся с проблемой переполнения, поскольку в сумме участвуют экспоненты произвольной, возможно большой, степени. Путём преобразований, основанных на свойствах функции можно избавиться от проблемы переполнения.

Пусть результат выполнения LSE(x) имеет значение y, тогда мы можем записать следующее уравнение:


y = LSE(x) = \log{\sum_{j=0}^N{e^{x_j}}}.

Применим экспоненту к обоим частя уравнения:


e^y = \sum_{j=0}^N{e^{x_j}}.

Время трюка. Пусть c = \max_{j}(x_j), тогда вынесем из каждого слагаемого e^c:


e^y = e^c \sum_{j=0}^N{e^{x_j- c}}, \, \, | \,\, \log(\cdot)

y = c + \log\left(\sum_{j=0}^N{e^{x_j- c}} \right) = LSE(x).


Теперь проверим на практике новую формулу:

import numpy as npx = np.array([11, 12, -1000, 5, 10, 0.001])y = np.array([-1000, 1000, -1000, 5, 10, 0.001])def LSE_initial(x):  return np.log(np.sum(np.exp(x)))def LSE_modified(x):  c = np.max(x)  return c + np.log(np.sum(np.exp(x - c)))# с экспонентами, не приводящими к переполнениюprint('Исходная LSE(x): ', LSE_initial(x)) print('Преобразованная LSE(x): ', LSE_modified(x))# с экспонентой в 1000й степениprint('Исходная LSE(y): ', LSE_initial(y))print('Преобразованная LSE(y): ', LSE_modified(y))

Вывод:

Исходная LSE(x):  12.408216490736713Преобразованная LSE(x):  12.408216490736713Исходная LSE(y):  infПреобразованная LSE(y):  1000.0

Видно, что даже в случае, когда одно из слагаемых обращается в float inf, модифицированный вариант lse(x) даёт верный результат.

Log-Sum-Exp Trick


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

Чтобы этого добиться, давайте приведем функцию софтмакс к виду, зависящему от LSE.


Пусть p_i в формуле софтмакс равно 1, тогда наше равенство примет следующий вид:


\frac{e^{x_i}}{\sum_{j=0}^N{e^{x_j}}} = 1.

Применим ряд преобразований:


\frac{e^{x_i}}{\sum_{j=0}^N{e^{x_j}}} = 1, \,\, | \,\, * \sum_{j=0}^N{e^{x_j}}

e^{x_i} = \sum_{j=0}^N{e^{x_j}}, \,\, | \,\, \log(\cdot)

0 = x_i - \log{\sum_{j=0}^N{e^{x_j}}}, \,\, | \,\, \exp(\cdot)

1 = \exp\left( x_i - \log{\sum_{j=0}^N{e^{x_j}}} \right).

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


\frac{e^{x_i}}{\sum_{j=0}^N{e^{x_j}}} = \exp\left( x_i - LSE(x) \right).


Применим новую формулу для вычисления софтмакса:

import numpy as npx = np.array([11, 12, -1000, 5, 10, 0.001])y = np.array([-1000, 1000, -1000, 5, 10, 0.001])def softmax_initial(x):  return np.exp(x) / np.sum(np.exp(x))def LSE(x):  c = np.max(x)  return c + np.log(np.sum(np.exp(x - c)))def softmax_modified(x):  return np.exp(x - LSE(x))# с экспонентами, не приводящими к переполнениюprint('Исходный Softmax(x): ', softmax_initial(x)) print('Преобразованный Softmax(x): ', softmax_modified(x))print('Суммы вероятностей: {} {}\n'.format(np.sum(softmax_initial(x)), np.sum(softmax_modified(x))))# с экспонентой в 1000й степениprint('Исходный Softmax(y): ', softmax_initial(y))print('Преобразованный Softmax(y): ', softmax_modified(y))print('Суммы вероятностей: {} {}'.format(np.sum(softmax_initial(y)), np.sum(softmax_modified(y))))

Вывод:

Исходный Softmax(x):  [2.44579103e-01 6.64834933e-01 0.00000000e+00 6.06250985e-04 8.99756239e-02 4.08897394e-06]Преобразованный Softmax(x):  [2.44579103e-01 6.64834933e-01 0.00000000e+00 6.06250985e-04 8.99756239e-02 4.08897394e-06]Суммы вероятностей: 0.9999999999999999 1.0000000000000004Исходный Softmax(y):  [ 0. nan  0.  0.  0.  0.]Преобразованный Softmax(y):  [0. 1. 0. 0. 0. 0.]Суммы вероятностей: nan 1.0

Здесь, аналогично результатам с преобразованным lse(x), видно, что модифицированная версия софтмакса стабильнее, и не страдает от переполнения при вычислении. Сумма вероятностей, полученных из софтмакс, даёт единицу, на всех примерах векторов. Такое поведение и ожидается от этой функции.

Заключение


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

Статья была подготовлена в рамках курса Математика для Data Science. Также предлагаю всем желающим посмотреть запись бесплатного демоурока про линейные пространства и отображения.

СМОТРЕТЬ ДЕМОУРОК
Подробнее..

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

18.03.2021 10:09:11 | Автор: admin
Приветствую всех! Меня зовут Ибрагим, я работаю в SberDevices и занимаюсь машинным обучением. Сегодня я расскажу о том, как мы находим и анализируем интересы и предпочтения пользователей наших виртуальных ассистентов Салют.

Также поделюсь видео с моего недавнего выступления на онлайн-конференции Применение ML в Digital-продуктах, которую проводили коллеги из AGIMA и Epoch8.


В этом посте мы разберём следующее:

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

Вступление


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

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

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

Как мы используем BERT для задач NLU


Для начала поговорим немного об обработке естественного языка в контексте задач его понимания (natural-language understanding).

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

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



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



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

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

Находим первичные данные для нашей задачи


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

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



Или же можно поискать дополнительные полезные источники информации. Этим способом воспользовались и мы. Помните, в детстве в журналах были такие разделы, где люди искали себе друзей по переписке? Они писали: Хей, меня зовут Ибрагим, я слушаю Blink-182, катаюсь на скейте. Давайте меняться напульсниками!. Мы нашли датасет подобных анкеточных писем, где не было личных данных или индексов, но предложения, реплики, где люди рассказывали о своих интересах, там присутствовали. Таким образом мы получили первую пару сотен реплик о том, где люди рассказывают что-то о себе, о каких-то своих предпочтениях.

Обогащаем датасет с помощью парафраз


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

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

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


Векторы, в которые мы кодируем наше предложение, по своей сути являются точками в признаковом пространстве. Что это значит? Это значит, что предложения, схожие по смыслу или по какому-то другому признаку, скорее всего, будут располагаться рядом. Например, предложения Мне нравится группа Metallica. и Я обожаю слушать тяжелую музыку. будут располагаться где-то рядом друг с другом. Это значит, что мы можем попробовать найти похожие по смыслу или синонимичные предложения к размеченным примерам, которые у нас уже есть. Такие предложения называют парафразами. Можно для каждого исходного предложения, для которого нам известен его класс, провести быстрый поиск ближайших соседей. Для этого можно воспользоваться библиотеками FAISS от Facebook, ScaNN от Google или другими и найти парафразы с определенным порогом по расстоянию. Таким образом можно обогатить изначальный датасет. И, условно, если у нас была пара сотен реплик, теперь мы можем получить пару тысяч реплик или предложений, где люди что-то рассказывали о своих интересах и предпочтениях.

Получаем полезную часть большого датасета


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



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

Кластеризуем данные, чтобы определить возможные будущие классы


Предложения, которые представлены точками в признаковом пространстве, могут образовывать группы, объединенные конкретным признаком. К примеру, группой может являться набор предложений, где люди рассказывают о любимой музыке, о любимых сериалах или книге. Поэтому можно взять все предложения, векторизовать их с помощью BERT, понизить размерность с помощью UMAP (потому что исходная размерность векторов BERT это 1024, если мы говорим про large модель). И затем кластеризовать полученные векторы с пониженной размерностью алгоритмом HDBSCAN. Таким образом можно получить группы и просмотреть глазами случайные предложения в них, чтобы понять, о чем люди рассказывают в диалогах. Теперь можно подумать, какие могут быть классы, подходящие под нашу задачу, и какое между этими классами распределение.



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

Немного о разметке данных


Полученный нами датасет со сформированным заданием можно отправить на разметку.

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

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

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

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

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

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

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

Внедрение модели и оценка метрик


Теперь нужно внедрять саму модель. Здесь мы активно используем практики MLOps. Во-первых, мы активно версионируем все наши модели не только с датасетом и кодом, но и между собой. Для этого мы используем DVC, потому что особенно важно, когда у вас двухступенчатая архитектура, версионировать модели между собой, чтобы была консистентность и между большой моделью-векторизатором (в нашем случае BERT), и между маленькими нейронными сетями, которые решают свои собственные задачи.

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

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

Итоги


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

собрать первичный датасет с корректной разметкой;

обогатить его с помощью поиска ближайших соседей;

построить первую модель простой бинарной классификации (поможет нам вычленить максимально полезный участок датасета);

кластеризовать данные и оценить полученные классы и распределения;

обучить конечную модель (завернув всё в пайплайны, версионировав);

оценить результаты по метрикам.



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

***
Оставлю здесь ссылки по теме:

пост про то, как мы обучали BERT и как сделали его устойчивым для парафраз;
лекция моего коллеги для Sberloga о том, какие трюки мы использовали, чтобы сделать нашу модель лучше;
одна из итераций модели Bert-large для русского языка, которую мы используем, выложена в open source и доступна любому;
рассказ о том, как мы с коллегами из Сбера обучили и выложили в открытый доступ русскоязычную модель GPT-3.
Подробнее..

Как построить свою систему поиска похожих изображений

04.04.2021 14:14:58 | Автор: admin

Представлюсь

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

Эта публикация предназначена для Machine Learning инженеров и написана по мотивам моего выступления Поиск похожих изображений - справочник от А до Я, который был опубликован сообществом Open Data Science на Data Fest Online 2020.

Данная статья содержит справочную информацию по зарекомендованным методам, применяемым в задаче Image Retireval. Прочитав статью, вы сможете построить систему поиска похожих изображений под вашу задачу с нуля (не включая процесс разработки production решения).

О задаче

Поиск похожих изображений (по-другому, Content-Based Image Retrieval или CBIR) - это любой поиск, в котором участвуют изображения.

Проще всего о задаче расскажет картинка сверху из статьи Recent Advance in Content-based Image Retrieval: A Literature Survey (2017).

Сейчас все активнее применяется подход "Поиск по фото", в частности, в e-commerce сервисах (AliExpress, Wildberries и др.). "Поиск по ключевому слову" (с пониманием контента изображений) уже давно осел в поисковых движках Google, Яндекс и пр., но вот до маркетплейсов и прочих частных поисковых систем еще не дошел. Думаю, с момента появления нашумевшего в кругах компьютерного зрения CLIP: Connecting Text and Images ускорится глобализация и этого подхода.

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

Базовые компоненты сервиса

Шаг 1. Обучение модели. Модель может быть сделана на классике CV или на базе нейронной сети. На вход модели - изображение, на выход - D-мерный дескриптор/эмбеддинг. В случае с классикой это может быть комбинация SIFT-дескриптора + Bag of Visual Words. В случае с нейронной сетью - стандартный бэкбон по типу ResNet, EfficientNet и пр. + замысловатые пулинг слои + хитрые техники обучения, о которых мы далее поговорим. Могу сказать, что при наличии достаточного объема данных или хорошего претрена нейронные сети сильно выиграют почти всегда (мы проверяли), поэтому сосредоточимся на них.

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

Шаг 3. Поиск. По загруженному пользователем изображению делается прогон модели, получение эмбеддинга и сравнение данного эмбеддинга с остальными в базе. Результатом поиска является отсортированная по релевантности выдача.

Нейросети и Metric Learning

Нейронная сеть в задаче поиска похожих используется как feature extractor (бэкбон). Выбор бэкбона зависит от объема и сложности данных - рассмотреть можно все от ResNet18 до Visual Transformer.

Первая особенность моделей в Image Retrieval - это магия в голове нейросети. На лидерборде по Image Retrieval борются за построение лучших дескрипторов - тут есть и Combined Global Descriptors с параллельными пулингами и Batch Drop Block для более равномерного распределения активации по выходной карте признаков.

Второй главной фишкой являются функции ошибок. Их очень много. Только в Deep Image Retrieval: A Survey представлено больше десятка зарекомендованных парных лоссов. Еще столько же есть классификационных. Главная суть всех этих лоссов - обучить нейросеть трансформировать изображение в вектор линейно разделимого пространства, так чтобы далее можно было сравнивать эти вектора по косинусному или евклидову расстоянию: похожие изображения будут иметь близкие эмбеддинги, непохожие - далекие. Рассмотрим подробнее.

Функции ошибок

Contrastive Loss

Самая простая для понимания функция ошибки - Contrastive Loss. Это парный лосс, т.е. объекты сравниваются по расстоянию между друг другом.

Нейросеть штрафуется за отдаленность друг от друга эмбеддингов изображений p и q, если эти изображения на самом деле похожи. Аналогично, возникает штраф за близость эмбеддингов, изображения которых на самом деле непохожи друг на друга. При этом в последнем случае мы ставим границу m (например, 0.5), преодолев которую, мы считаем, что нейросеть справилась с задачей "разъединения" непохожих изображений.

Triplet Loss

Triplet Loss берет во внимание три объекта - якорь, позитив (похожий на якорь) и негатив (отличный от якоря). Это также парный лосс.

Здесь мы нацелены на минимизацию расстояния от якоря до позитива и максимизацию расстояния от якоря до негатива. Впервые Triplet Loss был представлен в статье FaceNet от Google по распознаванию лиц и долгое время был state-of-the-art решением.

N-tupled Loss

N-tupled Loss - развитие Triplet Loss, в котором также берется якорь и позитив, но вместо одного негатива используется несколько негативов.

Angular Additive Margin (ArcFace)

Проблема парных лоссов заключается в выборе комбинаций позитивов, негативов и якорей - если их просто брать равномерно случайными из датасета, то возникнет проблема "легких пар". Это такие простые пары изображений, для которых лосс будет 0. Оказывается, сеть достаточно быстро сходится к состоянию, в котором большинство элементов в батче будут для нее "легкими", и лосс для них окажется нулевым - сеть перестанет учиться. Чтобы избежать этой проблемы, стали придумывать изощренные техники майнинга пар - hard negative и hard positive mining. Подробнее о проблеме можно почитать в этой статье. Существует также библиотека PML, в которой реализовано множество методов майнинга, да и вообще в библиотеке представлено много полезного по задаче Metric Learning на PyTorch.

Еще одним решением проблемы являются классификационные лоссы. Рассмотрим одну популярную функцию ошибки, которая привела к state-of-the-art в распознавании лиц три года назад - ArcFace.

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

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

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

Пулинги

Вернемся к архитектуре нейросети и рассмотрим парочку pooling слоев, применяемых в задачах Image Retrieval

R-MAC

Regional Maximum Activation of Convolutions (R-MAC) - пулинг слой, принимающий выходную карту нейронной сети (до глобального пулинга или слоев классификации) и возвращающий вектор-дескриптор, посчитанный как сумма активаций в различных окнах выходной карты. Здесь активацией окна является взятие максимума по этому окну для каждого канала независимо.

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

GeM

Generalized Mean (GeM) - простой пулинг, который может улучшить качество выходного дескриптора. Суть в том, что классический average pooling можно обобщить на lambda-норму. При увеличении lambda мы заставляем сеть фокусироваться на значимых частях изображения, что в определенных задачах может быть важно.

Ранжирование

Индексы

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

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

Датасет glove, размер эмбеддинга 100, расстояние - angularДатасет glove, размер эмбеддинга 100, расстояние - angular

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

Самые популярные: отечественная NMSLIB, Spotify Annoy, Facebook Faiss, Google Scann. Также, если хочется взять индексирование с REST API "из коробки", можно рассмотреть приложение Jina.

Переранжирование

Исследователи в области Information Retrieval давно поняли, что упорядоченная поисковая выдача может быть улучшена неким способом переупорядочивания элементов после получения исходной выдачи.

Одним из таких методов является Query Expansion. Идея состоит в том, чтобы использовать top-k ближайших элементов для генерации нового эмбеддинга. В самом простом случае можно взять усредненный вектор, как показано на картинке выше. Также можно взвесить эмбеддинги, например, по отдаленности в выдаче или косинусному расстоянию от запроса. Подобные улучшения описаны в едином фреймворке в статье Attention-Based Query Expansion Learning. По желанию можно применить Query Expansion рекурсивно.

k-reciprocal

k-reciprocal - множество элементов из top-k, в числе k ближайших которых присутствует сам запрос. На базе этого множества строят процесс переранжирования выдачи, один из которых описан в статье Re-ranking Person Re-identification with k-reciprocal Encoding. По определению, k-reciprocal ближе к запросу, чем k-nearest neighbors. Соответственно, можно грубо считать элементы, попавшие в множество k-reciprocal заведомо позитивными и изменять правило взвешивания, например, для Query Expansion. В данной статье разработан механизм пересчета дистанций с использований k-reciprocal множеств самих элементов в top-k. В статье много выкладок, это выходит за рамки данного поста, поэтому предлагаю читателю ознакомиться самостоятельно.

Валидация

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

Метрики

В первую очередь - метрики. Рассмотрим такие популярные метрики в задаче Image Retrieval: precision@k, recall@k, R-precision, mAP и nDCG.

precision@k

Показывает долю релевантных среди top-k ответов.

Плюсы:

  • показывает, насколько система избирательна в построении top-k

Минусы:

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

  • достичь значение 1 возможно только, если число релевантных >= k для всех запросов

R-precision

То же самое, что precision@k, где k устанавливается равным числу релевантных к данному запросу

Плюсы:

  • исчезает чувствительность к числу k в precision@k, метрика становится стабильной

Минусы:

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

recall@k

Показывает, какая доля релевантных была найдена в top-k

Плюсы:

  • отвечает на вопрос, найдены ли релевантные в принципе среди top-k

  • стабильна и хорошо усредняется по запросам

mAP (mean Average Precision)

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

Плюсы:

  • объективная стабильная оценка качества поиска

  • является одно-численным представлением precision-recall кривой, которая сама по себе богата информацией для анализа

Минусы

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

Подробнее про метрики в Information Retrieval, в том числе посмотреть вывод mAP, можно почитать здесь.

nDCG (Normalized Discounted Gain)

Данная метрика показывает, насколько корректно упорядочены элементы в top-k между собой. Плюсы и минусы этой метрики не будем рассматривать, поскольку в нашем списке это единственная метрика, учитывающая порядок элементов. Тем не менее, есть исследования, показывающие, что при необходимости учитывать порядок данная метрика является достаточно стабильной и может подойти в большинстве случаев.

Усреднение

Также важно отметить варианты усреднения метрик по запросам. Рассмотрим два варианта:

macro: для каждого запроса считается метрика, усредняем по всем запросам

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

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

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

Схемы валидации

Предлагаю рассмотреть два варианта валидации.

Валидация на множестве запросов и выбранных к ним релевантных

На вход: изображения-запросы и изображения, релевантные к ним. Имеется разметка в виде списка релевантных для данного запроса.

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

Валидация на полной базе

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

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

Пример реализованного проекта

Некоторые компании спорят с другими компаниями, чтобы вторые не использовали изобразительные элементы бренда первых. В таких случаях более слабый производитель пытается паразитировать на успехе крупного бренда, выдавая свои продукты и услуги под похожей символикой. От этого страдают и пользователи - вы можете по ошибке купить сыр не того производителя, которому вы уже доверяете, а взять подделанный товар, не прочитав внимательно этикетку. Пример из недавнего: Apple Is Attempting to Block a Pear Logo Trademark.

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

Устройство нашей системы

Для обучения, валидации и разработки поискового приложения мы разработали такую систему. Здесь Training pipeline, Benchmark, Indexer и Demo Web app - независимые репозитории, Logo Search app - поисковое приложение одного из клиентов. Объем индексируемой базы изображений: 1.5 млн товарных знаков.

Примеры работы

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

Заключение

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

Подробнее..

Искусственный интеллект в юриспруденции. Вебинар 1 Обзор последних достижений в области AI

14.04.2021 18:22:15 | Автор: admin

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



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


В частности:


  • в первой статье мы детально исследовали существующие инструменты процессинга русскоязычного текста;
  • во второй статье мы рассмотрели подходы к созданию продуктов на основе искусственного интеллекта, а также вопросы взаимодействия специалистов в области IT и юриспруденции;
  • в третьей статье мы рассказали о концептуализации знаний и провели сравнительный анализ доступных онтологий;
  • в четвертой статье мы опубликовали запись мероприятия, организованного Moscow Legal Hackers, на котором обсуждали тему обучения искусственного интеллекта.

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


  • что такое искусственный интеллект для юриста, а также как развивались данные технологии в последние годы?
  • какие существующие решения в области Legal Tech можно использовать в повседневной работе уже сегодня?
  • каким образом мы подходим к развитию искусственного интеллекта в юриспруденции, а также как создавать и внедрять решения на основе AI в свою деятельность?
  • какие тренды существуют в области искусственного интеллекта сегодня, а также как будут развиваться технологии в будущем?

И отрываем мы данный курс первым вебинаром, который раскрывает общие положения об искусственном интеллекте и рассказывает о последних достижениях в этой сфере.



Из данного вебинара Вы узнаете:


  • основные понятия и термины из мира AI (narrow AI/general AI/explainable AI и др.);
  • историю развития цифровых технологий и первые достижениях искусственного интеллекта (от побед над человеком на шахматной доске до генерации текста и предсказания 3d-модели белка);
  • основные мифы и заблуждения профессионального сообщества относительно искусственного интеллекта;
  • роль данных и информации в процессе цифровизации юридической работы.

P.S.:


Презентация доступна для скачивания по ссылке здесь.


Тайм-коды вебинара:


01:53 Структура курса
02:51 Что такое искусственный интеллект?
13:57 Сильный и слабый искусственный интеллект
17:39 Explainable AI
26:50 Machine learning / Deep learning
28:55 История искусственного интеллекта / Deep Blue vs Гари Каспаров
31:12 AlphaGo vs Lee Sedol
41:04 AlphaStar vs TLO/MaNa
44:03 Написание связанного текста (GPT-2)
47:31 AlphaFold vs CASP / предсказание 3D-модели белка
49:50 Голосовые ассистенты
51:56 Мифы и заблуждения об AI
54:55 AI зонтичный термин
01:01:07 Ценность данных и информации
01:03:26 AI это software
01:07:32 Заключение

Подробнее..

Искусственный интеллект в юриспруденции. Вебинар 3 Архитектура Legal AI

14.05.2021 16:12:54 | Автор: admin

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



Прошлые вебинары мы посвятили двум главным темам:


  • Что такое искусственный интеллект глазами юриста, как устроено машинное обучение и как исторически развивались технологии, позволяющие создавать компьютерные системы, которые приближены к возможностям человеческого интеллекта? (Вебинар 1)
  • Что такое LegalTech, какие инструменты автоматизации юридической функции существуют на текущий момент, какие возможности они предлагают, а также какие проблемы существуют в данной сфере? (Вебинар 2)

С учетом современного уровня развития цифровых технологий вопрос о том, можно ли создать полноценный Legal AI, уже не актуален. Текущих технологий достаточно для практической реализации подобных решений, поэтому данный вопрос трансформируется в иную плоскость: Как обучать Legal AI?. И с точки зрения методологии и подходов к решению данной задачи абсолютно все сталкиваются с одной и той же проблемой: у юристов отсутствует понимание инструментария и возможных результатов, а у программистов отсутствует глубокое понимание предметной области.


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



Из данного видео Вы узнаете:


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

P.S.:


Презентация доступна для скачивания по ссылке здесь.


Тайм-коды вебинара:


04:04 Как обучать Legal AI?
06:28 Шаг 1: постановка задачи
13:57 Задача 1: понимание естественного языка
21:54 Задача 2: разметка данных
30:30 Задача 3: оцифровка знаний
33:40 Переход от данных к смыслам
38:33 Трансформация в граф
45:15 Архитектура Legal AI
52:49 Гибридный AI
54:10 Индустриальные графы знаний
57:14 Методология внедрения Legal AI
01:05:10 Заключительные положения

Подробнее..

Разработка Computer Vision в онкологии почему всегда нужно еще больше сил, времени и денег

25.05.2021 16:13:10 | Автор: admin
image

Привет!

Я Жека Никитин, Head of AI в компании Celsus. Больше трех лет мы занимаемся разработкой системы для выявления патологий на медицинских снимках.

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

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

Собираем данные для обучения


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

image

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

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

Рассмотрим ситуацию на все том же примере детекции рака молочной железы. Более или менее качественные публичные датасеты можно пересчитать по пальцам одной руки: DDSM (порядка 2600 кейсов), InBreast (115), MIAS (161). Есть еще OPTIMAM и BCDR с достаточно сложной и запутанной процедурой получения доступа.

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

Итак, вы разослали запросы в медицинские учреждения, подняли все свои связи и контакты и получили в руки разношерстную коллекцию различных снимков. Не радуйтесь раньше времени, вы в самом начале пути! Ведь несмотря на наличие единого стандарта хранения медицинских изображений DICOM (Digital Imaging and Communications in Medicine), в реальной жизни все не так радужно. К примеру, информация о стороне (Left/Right) и проекции (CC/MLO) снимка молочной железы могут в разных источниках данных храниться в абсолютно разных полях. Решение тут единственное собирать данные из максимального числа источников и пытаться учесть в логике работы сервиса все возможные варианты.

Что разметишь, то и пожнешь


image

Мы наконец-то подобрались к самому интересному процессу разметки данных. Что же делает его таким особенным и незабываемым в сфере медицины? Во-первых, сам процесс разметки намного сложнее и дольше, чем в большинстве индустрий. Рентгеновские снимки невозможно загрузить в Яндекс.Толоку и за копейки получить размеченный датасет. Здесь требуется кропотливая работа медицинских специалистов, причем каждый снимок желательно отдавать для разметки нескольким врачам а это дорого и долго.

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

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

image

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

image

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

Конечно, если подходить к процессу с умом, то затраты можно и нужно сокращать например, с помощью активного обучения. В этом случае ML-система сама подсказывает врачам, какие снимки нужно доразметить для того, чтобы максимально улучшить качество распознавания патологий. Существуют разные способы оценки уверенности модели в своих предсказаниях Learning Loss, Discriminative Active Learning, MC Dropout, энтропия предсказанных вероятностей, confidence branch и многие другие. Какой из них лучше использовать, покажут только эксперименты на ваших моделях и датасетах.

Наконец, можно вовсе отказаться от разметки врачей и полагаться только на конечные, подтвержденные исходы например, смерть или выздоровление пациента. Возможно, это лучший подход (хотя и здесь есть куча нюансов), вот только начать работать он может в лучшем случае лет через десять-пятнадцать, когда повсеместно будут внедрены полноценные PACS (picture archiving and communication systems) и медицинские информационные системы (МИС) и когда будет накоплено достаточное количество данных. Но даже в этом случае чистоту и качество этих данных вам никто не гарантирует.

Хорошей модели хороший препроцессинг


image

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

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

При дальнейшем анализе выясняется, что в этом медицинском учреждении стоит какой-то незнакомый для вас рентгеновский аппарат со своими настройками, и снимки в результате внешне выглядят абсолютно по-другому. Нейронная сеть не обучалась на таких снимках, поэтому неудивительно, что на них она проваливается и ничего не детектирует. В мире машинного обучения такие случаи обычно называют Out-of-Distribution Data (данные не из исходного распределения). Модели обычно показывают значительно худшие результаты на таких данных, и это является одной из главных проблем машинного обучения.

Наглядный пример: наша команда протестировала публичную модель от исследователей из Нью-Йоркского университета, обученную на миллионе снимков. Авторы статьи утверждают, что модель продемонстрировала высокое качество детектирования онкологии на маммограммах, а конкретно они говорят о показателе точности ROC-AUC в районе 0.88-0.89. На наших данных эта же модель демонстрирует значительно худшие результаты от 0.65 до 0.70 в зависимости от датасета.

Самое простое решение этой проблемы на поверхности нужно собирать все возможные виды снимков, со всех аппаратов, со всеми настройками, размечать их и обучать на них систему. Минусы? Опять же, долго и дорого. В некоторых случаях можно обойтись и без разметки на помощь вам придет обучение без учителя (unsupervised learning). В нейронку определенным подаются неразмеченные снимки, и модель привыкает к их признакам, что позволяет ей успешно детектировать объекты на подобных изображениях в будущем. Это можно делать, например, с помощью псевдоразметки неразмеченных снимков или различных вспомогательных задач.

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

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


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

image
image

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

image

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

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

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

Искусственный интеллект в юриспруденции. Вебинар 4 Тренды и внедрение Legal AI

27.05.2021 14:07:09 | Автор: admin

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



В рамках предыдущих лекций мы рассказали зрителям о многих вопросах, связанных с юридическим искусственным интеллектом:


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

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



Из данного видео Вы узнаете:


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

P.S.:


Презентация доступна для скачивания по ссылке здесь.


Тайм-коды вебинара:


01:41 AI национальный приоритет
05:58 Мировое лидерство в области AI
12:11 Национальная программа Цифровая экономика
17:22 Перспективные сферы для внедрения AI
24:53 Актуальные проблемы государственных органов
29:22 Актуальные проблемы судебной системы
32:42 Специфика банковской сферы
39:14 Специфика процедуры закупок
44:04 AI цифровой помощник
47:05 Концепция ЭДО ФНС России (2020)
50:48 Машиночитаемое право
54:50 ML/DL vs Legal AI
57:22 Взгляд в будущее
01:06:14 Интеграция технологий и человека
01:11:08 Заключительные положения

Подробнее..

Как я Лигу Легенд парсил

19.04.2021 00:19:54 | Автор: admin

Привет, Хабр!

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

Шаг 0: Разбираемся, что к чему

Лига Легенд (League of Legends, LoL) - популярная MOBA игра, с ежемесячной аудиторией более чем в 100 млн игроков всему миру. LoL была разработана компанией Riot Games и выпущена в далёком 2009-м году.

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

Скриншот стрима матча континентальной лиги. LCL Летний Сплит 2020.Скриншот стрима матча континентальной лиги. LCL Летний Сплит 2020.

Для начала разберёмся с игровым HUD-ом (Heads-Up Display - визуальный интерфейс игры). На картинке выше цветами выделены основные его части:

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

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

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

  4. Нижняя панель (красный цвет) - наиболее нагруженная панель, показывающая более детальные статистики по каждому чемпиону, такие как: K/D/A (Kills/Deaths/Assists), кол-во убитых миньонов, а также набор предметов в инвентаре чемпиона.

Шаг 1: Парсинг верхней панели

  1. Первично была произведена аннотация данных. С использованием утилиты CVAT были выделены обрамляющими прямоугольниками необходимые поля, а также в ручном режиме выписаны их числовые значения. Хоть и задача распознавания чисел на картинке и не кажется сложной для современных нейронных сетей, в ручном режиме необходимость всё же была, т.к. эти самые современные нейронные сети (Google OCR, Yandex OCR) показали откровенно плохие результаты на некоторых типах полей при тестировании, не говоря уже об открытых движках для распознавания (Tesseract OCR, EasyOCR).

  2. Далее нам необходимо научиться выделять (детектировать) нужные нам поля. Для решения данной задачи было принято использовать segmentation-based подход. Я взял сеть Unet c предобученным efficientnet энкодером и обучил решать задачу instance сегментации для трех классов: башни (жёлтый цвет), золото (зелёный цвет) и кол-во убийств (фиолетовый цвет). Имплементация модели была взята из репозитория segmentation_models.pytorch. Хороший код для обучения таких моделей на Pytorch Lightning вы можете найти в репозитории Владимира Игловикова по сегментированию одежды.

  3. Теперь мы умеем получать маски с классами, а после применения watershed алгоритма даже целые отдельные области нахождения наших полей. Пора научиться их распознавать. Вдохновением для решения данной задачи послужили статьи по распознаванию номеров домов SVHN , а конкретно multihead архитектуры. Идея такова, что если мы имеем последовательности чисел фиксированной небольшой длины (а мы имеем), то мы можем не возиться с RNN или же детектировать отдельные символы. Мы можем взять энкодер и поставить за ним несколько отдельных голов по количеству цифр в числе, с 11-ю (11-ый для случаев, когда цифры нет) выходами в каждой. Каждая голова будет отвечать за предсказание отдельной цифры в числе, но учится все они будут вместе. Схожий подход можно найти имплементированным на Pytorch здесь.

  4. Но не будем забывать, что мы имеем дело с видео. Т.е. нам нужно научить модель работать с последовательностями кадров. Для этого был выбран простейший подход: а давайте просто заменим все 2D свёртки на трёхмерные. Как итог, практически без изменения архитектуры сети, мы добиваемся нужного результата, ведь размерности выходов у этих слоёв одинаковы. Добавление 3D свёрток сильно уменьшает количество выбросов распознавания, ведь сеть учится, что после числа N может идти либо само это чисто, либо же N+1.

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

Multihead OCR архитектураMultihead OCR архитектураНо зачем таймер-то парсить?

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

Шаг 2: Парсинг боковых панелей

  1. Размечаем маски необходимых нам полей, куда без этого: заклинания чемпиона (синий цвет), иконка чемпиона (красный цвет), здоровье и мана чемпиона (салатовый и фиолетовый цвета соотвественно). Обучаем Unet из прошлого шага.

  2. Начнём с самого простого: определение кол-ва здоровья и маны чемпиона. Для начала стоит предобработать вырезанные прямоугольные области . Переведём их в цветовую модель HSV (данный переход нужен для более простых манипуляций с цветами) и оставим лишь цвета из нужного диапазона: зелёного для здоровья и синего для маны. Теперь всё просто: итерируемся по матричному представлению одного из каналов изображения (или всех вместе) и ищем наиболее резкий переход между цветом и его подложкой. Разделив x-координату на размер всей области, из относительного перехода получаем значение кол-ва здоровья/маны чемпиона. Данный элементарный метод, как ни странно, показывает отличные результаты, являясь в достаточной мере робастным и точным.

  3. Далее нам нужно распознать классы заклинаний чемпиона и их статус(готовы заклинания к использованию или нет). О статусе заклинания можно узнать по наличию постепенно уходящего затемнения по часовой стрелке , чуть позже мы научимся определять прогресс этого отката. А пока вспомним, что такое multihead архитектура нейронной сети и применим ёё и здесь. Вместо предобученного энкодера возьмём простой набор из нескольких свёрточных и пуллинг слоёв в качестве кодировщика и пару голов с полносвязными слоями, одна из которых будет отвечать за предсказание класса заклинания, а другая за его состояние (активен/нет активен).

  4. К сожалению, свёрточные нейронные сети хуже приспособлены к тому, чтобы решать регрессионные задачи, а задача определения прогресса, когда заклинание будет готово является таковой. В процессе рисёрча я наткнулся на статью, в которой исследователи (да, кто-то публикует статьи про LoL) решили её за меня. Основная идея заключалась в том, чтобы взять неглубокую свёрточную нейросеть и решать ей задачу классификации, разделив набор вариантов отката заклинания на 20-ть интервалов воспринимая их как 20-ть отдельных классов и считая финальное число как взвешенную среднюю активаций выходных нейронов. Я слегка изменил данный подход, сделав не 20-ть, а 100 интервалов, вычисляя финальное число как argmax по выходам сети (в целом, так делать теоретически правильнее).

  5. На сладкое остаётся задача классификации чемпиона по его иконке. Её также можно решать обучив простую нейросеть классификации. На первое время так и сделаем, для того, чтобы набрать данных для обучения другой сети, более архитектурно подходящей для наших баранов. Подход с простой сетью-классификатором ограничен проблемой, которая называется OOD (Out-of-Domain), ведь разработчики достаточно часто добавляют новых чемпионов в игру, и чтобы не переобучать сеть каждый раз я решил обучить другую, основанную на подходе metric learning. Данный подход позволяет обучать сеть находить схожие изображения. Для этого я взял простую сеть-классификатор и вместо оптимизации кросс-энтропии и случайного сэмплирования оптимизировал hinge-loss с hard-negative triplet сэмплированием, чтобы сеть научалась выучивать эмбеддинги чемпионов.

Мяу?

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

Но зачем hinge-loss-то обучать?

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

Шаг 3: Парсинг мини-карты

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

  2. Yolo - anchor-based архитектура детекции, которая не очень хорошо справляется с коллизиями объектов. Мне же нравится идея применить segmentation-based подход. Для начала сгенерируем маски. Используя имеющуюся разметку будем генерировать два вида масок: маска чемпиона (синий цвет), граница изображения чемпиона (желтый цвет). Это довольно известный приём для instance сегментации, идея которого состоит в предсказании всего двух видов масок, а потом в вычитании границ.

  3. Вновь обучаем Unet.

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

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

Шаг N: Итоги

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

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

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

Подробнее..

Категории

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

  • Имя: Макс
    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