Распознавание эмоций по речи

OpenVINO хакатон: распознаем голос и эмоции на Raspberry Pi

30 ноября — 1 декабря в Нижнем Новгороде прошел OpenVINO хакатон. Участникам предлагалось создать прототип продуктового решения с использованием Intel OpenVINO toolkit. Организаторами был предложен список примерных тем, на которые можно было ориентироваться при выборе задачи, но финальное решение оставалось за командами. Кроме этого, поощрялось использование моделей, которые не входят в продукт.

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

В хакатоне участвовало более 10 команд. Приятно, что некоторые из них приехали из других регионов. Местом проведения хакатона был выбран комплекс “Кремлевский на Почаине”, где внутри были развешаны старинные фотографии Нижнего Новгорода, антуражно! (напоминаю, что на данный момент центральный офис компании Intel расположен именно в Нижнем Новгороде). На написание кода участникам отводилось 26 часов, в конце было необходимо презентовать своё решение. Отдельным плюсом было наличие демо-сессии, чтобы убедиться, что всё задуманное правда реализовано, а не осталось идеями в презентации. Мерч, снеки, еда, всё тоже было!

Кроме этого, компания Intel по желанию предоставляла камеры, Raspberry PI, Neural Compute Stick 2.

Выбор задачи

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

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

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

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

Формируем требования к нашему решению:

  • Маленький размер целевого девайса
  • Работа в реальном времени
  • Низкая цена
  • Легкая масштабируемость

В итоге в качестве целевого девайса выбираем Raspberry Pi 3 c Intel NCS 2.

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

Дело за малым: нужно раздобыть микрофон. Подойдет и обычный USB-микрофон, правда он не будут смотреться хорошо вместе с RPI. Но и тут решение буквально “лежит под боком”. Для записи голоса решаем использовать плату Voice Bonnet из набора Google AIY Voice Kit, на которой есть распаянный стерео микрофон.

Скачиваем Raspbian из репозитория AIY projects и заливаем на флешку, тестируем, что микрофон работает с помощью следующей команды (она запишет аудио длиной в 5 секунд и сохранит в файлик):

Сразу отмечу, что микрофон очень чувствительный и хорошо ловит шумы. Чтобы исправить это, зайдем в alsamixer, выберем Capture devices и снизим уровень входного сигнала до 50-60%.


Дорабатываем корпус напильником и всё влезает, можно даже закрыть крышкой

Добавляем кнопку-индикатор

Во время разбора AIY Voice Kit на части вспоминаем, что там есть RGB-кнопка, подсветкой которой можно управлять программно. Ищем “Google AIY Led” и находим документацию: https://aiyprojects.readthedocs.io/en/latest/aiy.leds.html
Почему бы не использовать эту кнопку для отображения распознанной эмоции, у нас всего 7 классов, а в кнопке 8 цветов, как раз хватает!

Читайте также:  Хорошее настроение частушки такая вот досада

Подключаем кнопку по GPIO к Voice Bonnet, подгружаем нужные библиотеки (они уже установлены в диструбутиве от AIY projects)

Cоздадим dict, в котором каждой эмоции будет соответствовать цвет в виде RGB Tuple и объект класса aiy.leds.Leds, через который будем обновлять цвет:

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


Кнопочка, гори!

Работаем с голосом

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

Так как у webrtcvad есть ограничение на размер подаваемого фрагмента — он должен быть равен 10/20/30мс, а обучение модели для распознавания эмоций (как мы далее узнаем) проводилось на датасете 48кГц, будем захватывать чанки размера 48000×20мс/1000×1(моно)=960 байт. Webrtcvad будет возвращать True/False для каждого из таких чанков, что соответствует наличию или отсутствию голоса в чанке.

Реализуем следующую логику:

  • Будем добавлять в list те чанки, где есть голос, если голоса нет, то инкрементируем счетчик пустых чанков.
  • Если счетчик пустых чанков >=30 (600 мс), то смотрим на размер листа накопившихся чанков, если он >250, то добавляем в очередь, если же нет, считаем, что длины записи недостаточно, чтобы подать её на модель для идентификации говорящего.
  • Если же счетчик пустых чанков всё ещё —data_type позволяет выбрать формат данных, с которым будет работать модель. Поддерживаются FP32, FP16, INT8. Выбор оптимального типа данных может дать хороший прирост к производительности.
    —input_shape указывает на размерность входных данных. Возможность динамически её менять вроде бы присутствует в C++ API, но мы так далеко копать не стали и для одной из моделей просто зафиксировали её.
    Далее попробуем загрузить уже сконвертированную модель в IR формате через DNN модуль в OpenCV и сделать forward на неё.

Последняя строчка в данном случае позволяет перенаправить вычисления на Neural Compute Stick, базово вычисления выполняются на процессоре, но в случае с Raspberry Pi это не прокатит, понадобится стик.

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

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

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

Записываем голоса нескольких человек (в нашем случае троих членов команды)
Далее для каждого записанного голоса выполняем fast fourier transform, получаем спектрограмму и сохраняем в виде numpy array (.npy):

Подробнее в файле create_base.py
В итоге при запуске основного скрипта мы в самом начале получим эмбеддинги из этих спектрограмм:

После получения эмбеддинга из прозвучавшего отрезка сможем определить, кому он принадлежит, взяв cosine distance от отрывка до всех голосов в базе (чем меньше, тем вероятнее) — для демо мы выставили порог 0.3):

В конце отмечу то, что скорость инференса была быстрой и позволяла добавить ещё 1-2 модели (на сэмпл длиной 7 секунд на инференс уходило 2.5). Добавить новые модели мы уже не успевали и сфокусировались на написании прототипа веб-приложения.

Веб-приложение

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

Бэкенд представляет из себя сквозной канал сообщений между фронтом и Raspberry Pi, основанный на технологии websocket (http over tcp protocol).

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


Ожидаем коннект с бэка

Front-end представляет собой web-приложение, написанное на JavaScript с использованием библиотеки React для ускорения и упрощения процесса разработки. Целью данного приложения является визуализация данных, полученных при помощи алгоритмов, запущенных на back-end стороне и непосредственно Raspberry Pi. На странице имеется роутинг по разделам, реализованный при помощи react-router, но основной интерес представляет главная страница, где в режиме реального времени поступает непрерывный поток данных с сервера по технологии WebSocket. Raspberry Pi детектирует голос, определяет принадлежность к определённому человеку из зарегистрированной базы и высылает список probability клиенту. Клиент отображает последние актуальные данные, выводит аватарку человека, который с наибольшей вероятностью говорил в микрофон, а также эмоцию, с которой он произносит слова.


Главная страница с обновляемыми предиктами

Заключение

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

Отмечу, что общая стоимость нашего решения составила 150$:

35$
Google AIY Voice Bonnet (можно взять плату respeaker)

15$
Intel NCS 2

  • Использовать регистрацию с клиента — просить прочитать текст, который генерим случайно
  • Добавить ещё несколько моделей: по голосу можно определять пол и возраст
  • Разделять одновременно звучащие голоса (диаризация)


Уставшие, но счастливые мы

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

Источник

Распознавание эмоций с помощью сверточной нейронной сети

Распознавание эмоций всегда было захватывающей задачей для ученых. В последнее время я работаю над экспериментальным SER-проектом (Speech Emotion Recognition), чтобы понять потенциал этой технологии – для этого я отобрал наиболее популярные репозитории на Github и сделал их основой моего проекта.

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

Главные препятствия

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

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

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

Есть два датасета, которые использовались в репозиториях RAVDESS и SAVEE, я только лишь адаптировал RAVDESS в своей модели. В контекста RAVDESS есть два типа данных: речь (speech) и песня (song).

  • 12 актеров и 12 актрис записали свою речь и песни в своем исполнении;
  • у актера #18 нет записанных песен;
  • эмоции Disgust (отвращение), Neutral (нейтральная) и Surprises (удивленние) отсутствуют в «песенных» данных.

Разбивка по эмоциям:

Диаграмма распределения эмоций:

Извлечение признаков

Когда мы работаем с задачами распознавания речи, мел-кепстральные коэффициенты (MFCCs) – это передовая технология, несмотря на то, что она появилась в 80-х.

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

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

Архитектура модели по умолчанию

Автор разработал CNN-модель с помощь пакет Keras, создав 7 слоев – шесть Con1D слоев и один слой плотности (Dense).

Автор закомментировал слои 4 и 5 в последнем релизе (18 сентября 2018 года) и итоговый размер файла этой модели не подходит под предоставленную сеть, поэтому я не смогу добиться такого же результат по точности – 72%.

Модель просто натренирована с параметрами batch_size=16 и epochs=700 , без какого-либо графика обучения и пр.

Здесь categorical_crossentropy это функция потерь, а мера оценки – точность.

Мой эксперимент

Разведочный анализ данных

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

Наблюдение

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

  • мужская злость (Angry) просто громче;
  • мужские радость (Happy) и расстройство (Sad) – особенность в смеющемся и плачущем тонах во время «тишины»;
  • женские радость (Happy), злость (Angry) и расстройство (Sad) громче;
  • женское отвращение (Disgust) содержит в себе звук рвоты.

Повторение эксперимента

Автор убрал классы neutral, disgust и surprised, чтобы сделать 10-классовое распознавание датасета RAVDESS. Пытаясь повторить опыт автора, я получил такой результат:

Однако я выяснил, что имеет место утечка данных, когда датасет для валидации идентичен тестовому датасету. Поэтому я повторил разделение данных, изолировав датасеты двух актеров и двух актрис, чтобы они не были видны во время теста:

  • актеры с 1 по 20 используются для сетов Train / Valid в соотношении 8:2;
  • актеры с 21 по 24 изолированы от тестов;
  • параметры Train Set: (1248, 216, 1);
  • параметры Valid Set: (312, 216, 1);
  • параметры Test Set: (320, 216, 1) — (изолировано).

Я заново обучил модель и вот результат:

Тест производительности

Из графика Train Valid Gross видно, что не происходит схождение для выбранных 10 классов. Поэтому я решил понизить сложность модели и оставить только мужские эмоции. Я изолировал двух актеров в рамках test set, а остальных поместил в train/valid set, соотношение 8:2. Это гарантирует, что в датасете не будет дисбаланса. Затем я тренировал мужские и женские данные отдельно, чтобы провести тест.

  • Train Set – 640 семплов от актеров 1-10;
  • Valid Set – 160 семплов от актеров 1-10;
  • Test Set – 160 семплов от актеров 11-12.

Опорная линия: мужчины

  • Train Set – 608 семплов от актрис 1-10;
  • Valid Set – 152 семпла от актрис 1-10;
  • Test Set – 160 семплов от актрис 11-12.

Опорная линия: женщины

Как можно заметить, матрицы ошибок отличаются.

Мужчины: злость (Angry) и радость (Happy) – основные предугаданные классы в модели, но они не похожи.

Женщины: расстройство (Sad) и радость (Happy) – основыне предугаданные классы в модели; злость (Angry) и радость (Happy) легко спутать.

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

Вдобавок ко всему, мне интересно, что если я еще больше упрощу модель, остави только классы Positive, Neutral и Negative. Или только Positive и Negative. Короче, я сгруппировал эмоции в 2 и 3 класса соответственно.

  • Позитивные: радость (Happy), спокойствие (Calm);
  • Негативные: злость (Angry), страх (fearful), расстройство (sad).

3 класса:

  • Позитивные: радость (Happy);
  • Нейтральные: спокойствие (Calm), нейтральная (Neutral);
  • Негативные: злость (Angry), страх (fearful), расстройство (sad).

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

Я добавил 2 слоя Conv1D, один слой MaxPooling1D и 2 слоя BarchNormalization; также я изменил значение отсева на 0.25. Наконец, я изменил оптимизатор на SGD со скоростью обучения 0.0001.

Для тренировки модели я применил уменьшение «плато обучения» и сохранил только лучшую модель с минимальным значением val_loss . И вот каковы результаты для разных целевых классов.

Производительность новой модели

Мужчины, 5 классов

Мужчины, 2 класса

Мужчины, 3 класса

Увеличение (аугментация)

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

Мужчины, 5 классов

Динамическое увеличение значений

Настройка высоты звука

Добавление белого шума

Заметно, что аугментация сильно повышает точность, до 70+% в общем случае. Особенно в случае с добавлением белого, которое повышает точность до 87,19% – однако тестовая точность и F1-мера падают более чем на 5%. И тут мне ко пришла идея комбинировать несколько методов аугментации для лучшего результата.

Объединяем несколько методов

Белый шум + смещение

Тестируем аугментацию на мужчинах

Мужчины, 2 класса

Белый шум + смещение

Для всех семплов

Белый шум + смещение

Только для позитивных семплов, так как 2-классовый сет дисбалансированный (в сторону негативных семплов).

Настройка высоты звука + белый шум
Для всех семплов

Настройка высоты звука + белый шум

Только для позитивных семплов

Заключение

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

Источник

Читайте также:  Когда нужно открыть свои чувства мужчине
Оцените статью