- PVSM.RU - https://www.pvsm.ru -
Распознавание эмоций всегда было захватывающей задачей для ученых. В последнее время я работаю над экспериментальным SER-проектом (Speech Emotion Recognition), чтобы понять потенциал этой технологии – для этого я отобрал наиболее популярные репозитории на Github [1] и сделал их основой моего проекта.
Прежде чем мы начнем разбираться в проекте, неплохо будет вспомнить, какие узкие места есть у SER.
Использование сверточной нейронной сети для распознавания эмоций в аудиозаписях. И да, владелец репозитория не ссылался ни на какие источники.
Есть два датасета, которые использовались в репозиториях RAVDESS и SAVEE, я только лишь адаптировал RAVDESS в своей модели. В контекста RAVDESS есть два типа данных: речь (speech) и песня (song).
Датасет RAVDESS (The Ryerson Audio-Visual Database of Emotional Speech and Song) [2]:
Разбивка по эмоциям:
Диаграмма распределения эмоций:
Когда мы работаем с задачами распознавания речи, мел-кепстральные коэффициенты (MFCCs) [3] – это передовая технология, несмотря на то, что она появилась в 80-х.
Цитата из туториала по MFCC [4]:
Эта форма определяет, каков звук на выходе. Если мы можем точно обозначить форму, она даст нам точное представление прозвучавшей фонемы [5]. Форма речевого тракта [6] проявляет себя в огибающей короткого спектра, и работы MFCC – точно отобразить эту огибающую.
Форма сигнала
Спектрограмма
Мы используем MFCC как входной признак. Если вам интересно разобраться подробнее, что такое MFCC, то этот туториал [4] – для вас. Загрузку данных и их конвертацию в формат MFCC можно легко сделать с помощью Python-пакета librosa.
Автор разработал CNN-модель с помощь пакет Keras, создав 7 слоев – шесть Con1D слоев и один слой плотности (Dense).
model = Sequential()
model.add(Conv1D(256, 5,padding='same', input_shape=(216,1))) #1
model.add(Activation('relu'))
model.add(Conv1D(128, 5,padding='same')) #2
model.add(Activation('relu'))
model.add(Dropout(0.1))
model.add(MaxPooling1D(pool_size=(8)))
model.add(Conv1D(128, 5,padding='same')) #3
model.add(Activation('relu'))
#model.add(Conv1D(128, 5,padding='same')) #4
#model.add(Activation('relu'))
#model.add(Conv1D(128, 5,padding='same')) #5
#model.add(Activation('relu'))
#model.add(Dropout(0.2))
model.add(Conv1D(128, 5,padding='same')) #6
model.add(Activation('relu'))
model.add(Flatten())
model.add(Dense(10)) #7
model.add(Activation('softmax'))
opt = keras.optimizers.rmsprop(lr=0.00001, decay=1e-6)
Автор закомментировал слои 4 и 5 в последнем релизе (18 сентября 2018 года) и итоговый размер файла этой модели не подходит под предоставленную сеть, поэтому я не смогу добиться такого же результат по точности – 72%.
Модель просто натренирована с параметрами batch_size=16
и epochs=700
, без какого-либо графика обучения и пр.
# Compile Model
model.compile(loss='categorical_crossentropy', optimizer=opt,metrics=['accuracy'])
# Fit Model
cnnhistory=model.fit(x_traincnn, y_train, batch_size=16, epochs=700, validation_data=(x_testcnn, y_test))
Здесь categorical_crossentropy
это функция потерь, а мера оценки – точность.
В датасете RAVDESS каждый актер проявляет 8 эмоций, проговаривая и пропевая 2 предложения по 2 раза каждое. В итоге с каждого актера получается 4 примера каждой эмоции за исключением вышеупомянутых нейтральной эмоции, отвращения и удивления. Каждое аудио длится примерно 4 секунды, в первой и последней секундах чаще всего тишина.
Типичные предложения:
После того как я выбрал датасет из 1 актера и 1 актрисы, а затем прослушал все их записи, я понял, что мужчины и женщины выражают свои эмоции по-разному. Например:
Автор убрал классы neutral, disgust и surprised, чтобы сделать 10-классовое распознавание датасета RAVDESS. Пытаясь повторить опыт автора, я получил такой результат:
Однако я выяснил, что имеет место утечка данных, когда датасет для валидации идентичен тестовому датасету. Поэтому я повторил разделение данных, изолировав датасеты двух актеров и двух актрис, чтобы они не были видны во время теста:
Я заново обучил модель и вот результат:
Из графика Train Valid Gross видно, что не происходит схождение для выбранных 10 классов. Поэтому я решил понизить сложность модели и оставить только мужские эмоции. Я изолировал двух актеров в рамках test set, а остальных поместил в train/valid set, соотношение 8:2. Это гарантирует, что в датасете не будет дисбаланса. Затем я тренировал мужские и женские данные отдельно, чтобы провести тест.
Мужской датасет
Опорная линия: мужчины
Женский датасет
Опорная линия: женщины
Как можно заметить, матрицы ошибок отличаются.
Мужчины: злость (Angry) и радость (Happy) – основные предугаданные классы в модели, но они не похожи.
Женщины: расстройство (Sad) и радость (Happy) – основыне предугаданные классы в модели; злость (Angry) и радость (Happy) легко спутать.
Вспоминая наблюдения из Разведочного анализа данных, я подозреваю, что женские злость (Angry) и радость (Happy) похожи до степени смешения, потому что их способ выражения заключается просто в повышении голоса.
Вдобавок ко всему, мне интересно, что если я еще больше упрощу модель, остави только классы Positive, Neutral и Negative. Или только Positive и Negative. Короче, я сгруппировал эмоции в 2 и 3 класса соответственно.
2 класса:
3 класса:
До начала эксперимента я настроил архитектуру модели с помощью мужских данных, сделав 5-классовое распознавание.
# Указываем нужное кол-во классов
target_class = 5
# Модель
model = Sequential()
model.add(Conv1D(256, 8, padding='same',input_shape=(X_train.shape[1],1))) #1
model.add(Activation('relu'))
model.add(Conv1D(256, 8, padding='same')) #2
model.add(BatchNormalization())
model.add(Activation('relu'))
model.add(Dropout(0.25))
model.add(MaxPooling1D(pool_size=(8)))
model.add(Conv1D(128, 8, padding='same')) #3
model.add(Activation('relu'))
model.add(Conv1D(128, 8, padding='same')) #4
model.add(Activation('relu'))
model.add(Conv1D(128, 8, padding='same')) #5
model.add(Activation('relu'))
model.add(Conv1D(128, 8, padding='same')) #6
model.add(BatchNormalization())
model.add(Activation('relu'))
model.add(Dropout(0.25))
model.add(MaxPooling1D(pool_size=(8)))
model.add(Conv1D(64, 8, padding='same')) #7
model.add(Activation('relu'))
model.add(Conv1D(64, 8, padding='same')) #8
model.add(Activation('relu'))
model.add(Flatten())
model.add(Dense(target_class)) #9
model.add(Activation('softmax'))
opt = keras.optimizers.SGD(lr=0.0001, momentum=0.0, decay=0.0, nesterov=False)
Я добавил 2 слоя Conv1D, один слой MaxPooling1D и 2 слоя BarchNormalization; также я изменил значение отсева на 0.25. Наконец, я изменил оптимизатор на SGD со скоростью обучения 0.0001.
lr_reduce = ReduceLROnPlateau(monitor=’val_loss’, factor=0.9, patience=20, min_lr=0.000001)
mcp_save = ModelCheckpoint(‘model/baseline_2class_np.h5’, save_best_only=True, monitor=’val_loss’, mode=’min’)
cnnhistory=model.fit(x_traincnn, y_train, batch_size=16, epochs=700, validation_data=(x_testcnn, y_test), callbacks=[mcp_save, lr_reduce])
Для тренировки модели я применил уменьшение «плато обучения» и сохранил только лучшую модель с минимальным значением val_loss
. И вот каковы результаты для разных целевых классов.
Мужчины, 5 классов
Женщины, 5 классов
Мужчины, 2 класса
Мужчины, 3 класса
Когда я усилил архитектуру модели, оптимизатор и скорость обучения, выяснилось, что модель по-прежнему не сходится в режиме тренировки. Я предположил, что это проблема количества данных, так как у нас имеется только 800 семплов. Это привело меня к методам увеличения аудио, в итоге я увеличил датасеты ровно вдвое. Давайт взглянем на эти методы.
Динамическое увеличение значений
def dyn_change(data):
"""
Случайное изменение значений
"""
dyn_change = np.random.uniform(low=1.5,high=3)
return (data * dyn_change)
Настройка высоты звука
def pitch(data, sample_rate):
"""
Настройка высоты звука
"""
bins_per_octave = 12
pitch_pm = 2
pitch_change = pitch_pm * 2*(np.random.uniform())
data = librosa.effects.pitch_shift(data.astype('float64'),
sample_rate, n_steps=pitch_change,
bins_per_octave=bins_per_octave)
Смещение
def shift(data):
"""
Случайное смещение
"""
s_range = int(np.random.uniform(low=-5, high = 5)*500)
return np.roll(data, s_range)
Добавление белого шума
def noise(data):
"""
Добавление белого шума
"""
# можете взять любой дистрибутив отсюда: https://docs.scipy.org/doc/numpy-1.13.0/reference/routines.random.html
noise_amp = 0.005*np.random.uniform()*np.amax(data)
data = data.astype('float64') + noise_amp * np.random.normal(size=data.shape[0])
return data
Заметно, что аугментация сильно повышает точность, до 70+% в общем случае. Особенно в случае с добавлением белого, которое повышает точность до 87,19% – однако тестовая точность и F1-мера [8] падают более чем на 5%. И тут мне ко пришла идея комбинировать несколько методов аугментации для лучшего результата.
Белый шум + смещение
Белый шум + смещение
Для всех семплов
Белый шум + смещение
Только для позитивных семплов, так как 2-классовый сет дисбалансированный (в сторону негативных семплов).
Настройка высоты звука + белый шум
Для всех семплов
Настройка высоты звука + белый шум
Только для позитивных семплов
В конце концов, я смог поэкспериментировать только с мужским датасетом. Я заново разделил данные так, чтобы избежать дисбаланса и, как следствие, утечки данных. Я настроил модель на эксперименты с мужскими голосами, так как я хотел максимально упростить модель для начала. Также я провел тесты, используя разные методы аугментации; добавление белого шума и смещение хорошо зарекомендовали себя на дисбалансированных данных.
Автор: nvpushkarskiy2
Источник [9]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/programmirovanie/325149
Ссылки в тексте:
[1] Github: https://github.com/MITESHPUTHRANNEU/Speech-Emotion-Analyzer?source=post_page---------------------------
[2] RAVDESS (The Ryerson Audio-Visual Database of Emotional Speech and Song): https://zenodo.org/record/1188976?source=post_page---------------------------#.XN0fwnUzZhE
[3] мел-кепстральные коэффициенты (MFCCs): https://en.wikipedia.org/wiki/Mel-frequency_cepstrum
[4] туториала по MFCC: http://practicalcryptography.com/miscellaneous/machine-learning/guide-mel-frequency-cepstral-coefficients-mfccs/?source=post_page---------------------------
[5] фонемы: https://ru.wikipedia.org/wiki/%D0%A4%D0%BE%D0%BD%D0%B5%D0%BC%D0%B0
[6] речевого тракта: https://ru.wikipedia.org/wiki/%D0%A0%D0%B5%D1%87%D0%B5%D0%B2%D0%BE%D0%B9_%D1%82%D1%80%D0%B0%D0%BA%D1%82
[7] Разведочный анализ данных: https://ru.wikipedia.org/wiki/%D0%A0%D0%B0%D0%B7%D0%B2%D0%B5%D0%B4%D0%BE%D1%87%D0%BD%D1%8B%D0%B9_%D0%B0%D0%BD%D0%B0%D0%BB%D0%B8%D0%B7_%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85
[8] F1-мера: https://en.wikipedia.org/wiki/F1_score
[9] Источник: https://habr.com/ru/post/461435/?utm_source=habrahabr&utm_medium=rss&utm_campaign=461435
Нажмите здесь для печати.