Этот пример иллюстрирует простую систему распознавания речевых эмоций (SER) с использованием BiLSTM-сети. Сначала необходимо загрузить набор данных, а затем протестировать обученную сеть на отдельных файлах. Сеть обучалась по небольшой немецкоязычной базе данных [1].
В этом примере рассматривается обучение работе с сетью, включая загрузку, добавление и обучение набора данных. Наконец, для оценки сетевой архитектуры выполняется 10-кратная перекрестная проверка LOSO.
Функции, использованные в этом примере, были выбраны с использованием последовательного выбора функций, аналогично методу, описанному в разделе Последовательный выбор функций для аудиоприемников (Audio Toolbox).
Загрузите Берлинскую базу данных эмоциональной речи [1]. База данных содержит 535 высказываний, произнесенных 10 актерами, предназначенных для передачи одной из следующих эмоций: гнев, скука, отвращение, беспокойство/страх, счастье, печаль или нейтралитет. Эмоции не зависят от текста.
url = "http://emodb.bilderbar.info/download/download.zip"; downloadFolder = tempdir; datasetFolder = fullfile(downloadFolder,"Emo-DB"); if ~exist(datasetFolder,'dir') disp('Downloading Emo-DB (40.5 MB) ...') unzip(url,datasetFolder) end
Создание audioDatastore (Audio Toolbox) указывает на аудиофайлы.
ads = audioDatastore(fullfile(datasetFolder,"wav"));Имена файлов представляют собой коды, обозначающие идентификатор говорящего, текст, слова, эмоции и версию. Веб-сайт содержит ключ для интерпретации кода и дополнительную информацию о ораторах, такую как пол и возраст. Создание таблицы с переменными Speaker и Emotion. Декодируйте имена файлов в таблицу.
filepaths = ads.Files; emotionCodes = cellfun(@(x)x(end-5),filepaths,'UniformOutput',false); emotions = replace(emotionCodes,{'W','L','E','A','F','T','N'}, ... {'Anger','Boredom','Disgust','Anxiety/Fear','Happiness','Sadness','Neutral'}); speakerCodes = cellfun(@(x)x(end-10:end-9),filepaths,'UniformOutput',false); labelTable = cell2table([speakerCodes,emotions],'VariableNames',{'Speaker','Emotion'}); labelTable.Emotion = categorical(labelTable.Emotion); labelTable.Speaker = categorical(labelTable.Speaker); summary(labelTable)
Variables:
Speaker: 535×1 categorical
Values:
03 49
08 58
09 43
10 38
11 55
12 35
13 61
14 69
15 56
16 71
Emotion: 535×1 categorical
Values:
Anger 127
Anxiety/Fear 69
Boredom 81
Disgust 46
Happiness 71
Neutral 79
Sadness 62
labelTable находится в том же порядке, что и файлы в audioDatastore. Установите Labels имущества audioDatastore в labelTable.
ads.Labels = labelTable;
Загрузите и загрузите предварительно обученную сеть, audioFeatureExtractor Объект (Audio Toolbox), используемый для обучения сети, и коэффициенты нормализации для элементов. Эта сеть была обучена с использованием всех динамиков в наборе данных, кроме динамиков 03.
url = 'http://ssd.mathworks.com/supportfiles/audio/SpeechEmotionRecognition.zip'; downloadNetFolder = tempdir; netFolder = fullfile(downloadNetFolder,'SpeechEmotionRecognition'); if ~exist(netFolder,'dir') disp('Downloading pretrained network (1 file - 1.5 MB) ...') unzip(url,downloadNetFolder) end load(fullfile(netFolder,'network_Audio_SER.mat'));
Частота выборки, установленная на audioFeatureExtractor соответствует частоте выборки набора данных.
fs = afe.SampleRate;
Выберите динамик и эмоции, затем поднаберите хранилище данных, чтобы включить только выбранный динамик и эмоции. Чтение из хранилища данных и прослушивание файла.
speaker =categorical("03"); emotion =
categorical("Disgust"); adsSubset = subset(ads,ads.Labels.Speaker==speaker & ads.Labels.Emotion == emotion); audio = read(adsSubset); sound(audio,fs)
Используйте audioFeatureExtractor объект, чтобы извлечь элементы, а затем транспонировать их так, чтобы время было вдоль строк. Нормализуйте элементы, а затем преобразуйте их в 20-элементные последовательности с 10-элементным перекрытием, что соответствует окнам приблизительно 600 мс с перекрытием 300 мс. Используйте вспомогательную функцию HelperFeatureVector2Sequence для преобразования массива векторов элементов в последовательности.
features = (extract(afe,audio))';
featuresNormalized = (features - normalizers.Mean)./normalizers.StandardDeviation;
numOverlap =
10;
featureSequences = HelperFeatureVector2Sequence(featuresNormalized,20,numOverlap);Передача последовательностей функций в сеть для прогнозирования. Вычислите среднее предсказание и постройте график распределения вероятности выбранных эмоций в виде круговой диаграммы. Для проверки производительности сети можно использовать различные динамики, эмоции, совпадение последовательностей и среднее прогнозирование. Чтобы получить реалистичное приближение производительности сети, используйте динамик 03, на котором сеть не была обучена.
YPred = double(predict(net,featureSequences)); average ="mode"; switch average case 'mean' probs = mean(YPred,1); case 'median' probs = median(YPred,1); case 'mode' probs = mode(YPred,1); end pie(probs./sum(probs),string(net.Layers(end).Classes))

В оставшейся части примера показано, как была обучена и проверена сеть.
10-кратная точность перекрестной проверки первой попытки обучения составила около 60% из-за недостаточности данных обучения. Модель, обученная на недостаточных данных, перебирает одни складки и не подгоняет другие. Чтобы улучшить общую подгонку, увеличьте размер набора данных с помощью audioDataAugmenter (Панель звуковых инструментов). 50 дополнений на файл было выбрано эмпирически как хороший компромисс между временем обработки и улучшением точности. Для ускорения примера можно уменьшить количество дополнений.
Создание audioDataAugmenter объект. Установить вероятность применения сдвига основного тона к 0.5 и используйте диапазон по умолчанию. Установка вероятности применения сдвига времени к 1 и использовать диапазон [-0.3,0.3] секунд. Установка вероятности добавления шума к 1 и укажите диапазон SNR как [-20,40] дБ.
numAugmentations =50; augmenter = audioDataAugmenter('NumAugmentations',numAugmentations, ... 'TimeStretchProbability',0, ... 'VolumeControlProbability',0, ... ... 'PitchShiftProbability',0.5, ... ... 'TimeShiftProbability',1, ... 'TimeShiftRange',[-0.3,0.3], ... ... 'AddNoiseProbability',1, ... 'SNRRange', [-20,40]);
Создайте новую папку в текущей папке для хранения дополненного набора данных.
currentDir = pwd;
writeDirectory = fullfile(currentDir,'augmentedData');
mkdir(writeDirectory)Для каждого файла в хранилище аудиоданных:
Создайте 50 дополнений.
Нормализуйте звук, чтобы получить максимальное абсолютное значение 1.
Запишите дополненные аудиоданные как WAV-файл. Приложить _augK к каждому из имен файлов, где K - номер дополнения. Для ускорения обработки используйте parfor и разбиение хранилища данных на разделы.
Этот способ увеличения базы данных занимает много времени (около 1 часов) и места (около 26 ГБ). Однако при итерации при выборе сетевой архитектуры или конвейера извлечения признаков эта предварительная стоимость обычно является выгодной.
N = numel(ads.Files)*numAugmentations; myWaitBar = HelperPoolWaitbar(N,"Augmenting Dataset..."); reset(ads) numPartitions = 18; tic parfor ii = 1:numPartitions adsPart = partition(ads,numPartitions,ii); while hasdata(adsPart) [x,adsInfo] = read(adsPart); data = augment(augmenter,x,fs); [~,fn] = fileparts(adsInfo.FileName); for i = 1:size(data,1) augmentedAudio = data.Audio{i}; augmentedAudio = augmentedAudio/max(abs(augmentedAudio),[],'all'); augNum = num2str(i); if numel(augNum)==1 iString = ['0',augNum]; else iString = augNum; end audiowrite(fullfile(writeDirectory,sprintf('%s_aug%s.wav',fn,iString)),augmentedAudio,fs); increment(myWaitBar) end end end
Starting parallel pool (parpool) using the 'local' profile ... Connected to the parallel pool (number of workers: 6).
delete(myWaitBar)
fprintf('Augmentation complete (%0.2f minutes).\n',toc/60)Augmentation complete (6.28 minutes).
Создайте хранилище аудиоданных, указывающее на набор дополненных данных. Репликация строк таблицы меток исходного хранилища данных NumAugmentations время для определения меток дополненного хранилища данных.
adsAug = audioDatastore(writeDirectory); adsAug.Labels = repelem(ads.Labels,augmenter.NumAugmentations,1);
Создание audioFeatureExtractor (Звуковая панель инструментов). Набор Window к периодическому окну Хэмминга 30 мс, OverlapLength кому 0, и SampleRate к частоте выборки базы данных. Набор gtcc, gtccDelta, mfccDelta, и spectralCrest кому true чтобы извлечь их. Набор SpectralDescriptorInput кому melSpectrum чтобы spectralCrest вычисляется для спектра mel.
win = hamming(round(0.03*fs),"periodic"); overlapLength = 0; afe = audioFeatureExtractor( ... 'Window',win, ... 'OverlapLength',overlapLength, ... 'SampleRate',fs, ... ... 'gtcc',true, ... 'gtccDelta',true, ... 'mfccDelta',true, ... ... 'SpectralDescriptorInput','melSpectrum', ... 'spectralCrest',true);
При подготовке к развертыванию используйте все доступные динамики в наборе данных. Установите хранилище данных обучения в дополненное хранилище данных.
adsTrain = adsAug;
Преобразуйте хранилище данных обучающего звука в массив высокого уровня. При наличии Toolbox™ Parallel Computing извлечение выполняется автоматически параллельно. Если у вас нет Toolbox™ параллельных вычислений, код продолжает выполняться.
tallTrain = tall(adsTrain);
Извлеките обучающие элементы и переориентируйте их таким образом, чтобы время вдоль строк было совместимым с sequenceInputLayer.
featuresTallTrain = cellfun(@(x)extract(afe,x),tallTrain,"UniformOutput",false); featuresTallTrain = cellfun(@(x)x',featuresTallTrain,"UniformOutput",false); featuresTrain = gather(featuresTallTrain);
Evaluating tall expression using the Parallel Pool 'local': - Pass 1 of 1: Completed in 1 min 7 sec Evaluation completed in 1 min 7 sec
Используйте обучающий набор для определения среднего и стандартного отклонения каждой функции.
allFeatures = cat(2,featuresTrain{:});
M = mean(allFeatures,2,'omitnan');
S = std(allFeatures,0,2,'omitnan');
featuresTrain = cellfun(@(x)(x-M)./S,featuresTrain,'UniformOutput',false);Буферизируйте векторы признаков в последовательности таким образом, чтобы каждая последовательность состояла из 20 векторов признаков с перекрытиями из 10 векторов признаков.
featureVectorsPerSequence = 20; featureVectorOverlap = 10; [sequencesTrain,sequencePerFileTrain] = HelperFeatureVector2Sequence(featuresTrain,featureVectorsPerSequence,featureVectorOverlap);
Реплицируйте метки обучающих и проверочных наборов таким образом, чтобы они соответствовали последовательностям один к одному. Не у всех ораторов есть высказывания на все эмоции. Создать пустой categorical массив, который содержит все эмоциональные категории и добавляет его к меткам проверки, чтобы категориальный массив содержал все эмоции.
labelsTrain = repelem(adsTrain.Labels.Emotion,[sequencePerFileTrain{:}]);
emptyEmotions = ads.Labels.Emotion;
emptyEmotions(:) = [];Определение сети BiLSTM с помощью bilstmLayer. Разместить dropoutLayer до и после bilstmLayer чтобы предотвратить переоборудование.
dropoutProb1 = 0.3; numUnits = 200; dropoutProb2 = 0.6; layers = [ ... sequenceInputLayer(size(sequencesTrain{1},1)) dropoutLayer(dropoutProb1) bilstmLayer(numUnits,"OutputMode","last") dropoutLayer(dropoutProb2) fullyConnectedLayer(numel(categories(emptyEmotions))) softmaxLayer classificationLayer];
Определение параметров обучения с помощью trainingOptions.
miniBatchSize = 512; initialLearnRate = 0.005; learnRateDropPeriod = 2; maxEpochs = 3; options = trainingOptions("adam", ... "MiniBatchSize",miniBatchSize, ... "InitialLearnRate",initialLearnRate, ... "LearnRateDropPeriod",learnRateDropPeriod, ... "LearnRateSchedule","piecewise", ... "MaxEpochs",maxEpochs, ... "Shuffle","every-epoch", ... "Verbose",false, ... "Plots","Training-Progress");
Обучение сети с помощью trainNetwork.
net = trainNetwork(sequencesTrain,labelsTrain,layers,options);
Для сохранения сети настроено audioFeatureExtractorи коэффициенты нормализации, набор saveSERSystem кому true.
saveSERSystem =false; if saveSERSystem normalizers.Mean = M; normalizers.StandardDeviation = S; save('network_Audio_SER.mat','net','afe','normalizers') end
Чтобы обеспечить точную оценку модели, созданной в этом примере, обучайте и проверяйте с помощью перекрестной проверки «leave-one-speaker-out» (LOSO) в k раз. В этом методе вы тренируетесь с помощью k-1 динамиков, а затем проверяете на оставленном динамике. Вы повторяете эту процедуру k раз. Окончательная точность проверки - это среднее значение k складок.
Создайте переменную, содержащую идентификаторы динамиков. Определите количество складок: 1 для каждого говорящего. База данных содержит высказывания от 10 уникальных динамиков. Использовать summary для отображения идентификаторов говорящих (левый столбец) и количества высказываний, которые они вносят в базу данных (правый столбец).
speaker = ads.Labels.Speaker; numFolds = numel(speaker); summary(speaker)
03 49
08 58
09 43
10 38
11 55
12 35
13 61
14 69
15 56
16 71
Вспомогательная функция HelperTrainAndValidateNetwork выполняет описанные выше шаги для всех 10 складок и возвращает истинные и прогнозируемые метки для каждой складки. Звонить HelperTrainAndValidateNetwork с audioDatastore, увеличенный audioDatastore, и audioFeatureExtractor.
[labelsTrue,labelsPred] = HelperTrainAndValidateNetwork(ads,adsAug,afe);
Распечатайте точность по складкам и постройте график 10-кратной путаницы.
for ii = 1:numel(labelsTrue) foldAcc = mean(labelsTrue{ii}==labelsPred{ii})*100; fprintf('Fold %1.0f, Accuracy = %0.1f\n',ii,foldAcc); end
Fold 1, Accuracy = 73.5 Fold 2, Accuracy = 77.6 Fold 3, Accuracy = 74.4 Fold 4, Accuracy = 68.4 Fold 5, Accuracy = 76.4 Fold 6, Accuracy = 80.0 Fold 7, Accuracy = 73.8 Fold 8, Accuracy = 87.0 Fold 9, Accuracy = 69.6 Fold 10, Accuracy = 70.4
labelsTrueMat = cat(1,labelsTrue{:});
labelsPredMat = cat(1,labelsPred{:});
figure
cm = confusionchart(labelsTrueMat,labelsPredMat);
valAccuracy = mean(labelsTrueMat==labelsPredMat)*100;
cm.Title = sprintf('Confusion Matrix for 10-Fold Cross-Validation\nAverage Accuracy = %0.1f',valAccuracy);
sortClasses(cm,categories(emptyEmotions))
cm.ColumnSummary = 'column-normalized';
cm.RowSummary = 'row-normalized';
function [sequences,sequencePerFile] = HelperFeatureVector2Sequence(features,featureVectorsPerSequence,featureVectorOverlap) % Copyright 2019 MathWorks, Inc. if featureVectorsPerSequence <= featureVectorOverlap error('The number of overlapping feature vectors must be less than the number of feature vectors per sequence.') end if ~iscell(features) features = {features}; end hopLength = featureVectorsPerSequence - featureVectorOverlap; idx1 = 1; sequences = {}; sequencePerFile = cell(numel(features),1); for ii = 1:numel(features) sequencePerFile{ii} = floor((size(features{ii},2) - featureVectorsPerSequence)/hopLength) + 1; idx2 = 1; for j = 1:sequencePerFile{ii} sequences{idx1,1} = features{ii}(:,idx2:idx2 + featureVectorsPerSequence - 1); %#ok<AGROW> idx1 = idx1 + 1; idx2 = idx2 + hopLength; end end end
function [trueLabelsCrossFold,predictedLabelsCrossFold] = HelperTrainAndValidateNetwork(varargin) % Copyright 2019 The MathWorks, Inc. if nargin == 3 ads = varargin{1}; augads = varargin{2}; extractor = varargin{3}; elseif nargin == 2 ads = varargin{1}; augads = varargin{1}; extractor = varargin{2}; end speaker = categories(ads.Labels.Speaker); numFolds = numel(speaker); emptyEmotions = (ads.Labels.Emotion); emptyEmotions(:) = []; % Loop over each fold. trueLabelsCrossFold = {}; predictedLabelsCrossFold = {}; for i = 1:numFolds % 1. Divide the audio datastore into training and validation sets. % Convert the data to tall arrays. idxTrain = augads.Labels.Speaker~=speaker(i); augadsTrain = subset(augads,idxTrain); augadsTrain.Labels = augadsTrain.Labels.Emotion; tallTrain = tall(augadsTrain); idxValidation = ads.Labels.Speaker==speaker(i); adsValidation = subset(ads,idxValidation); adsValidation.Labels = adsValidation.Labels.Emotion; tallValidation = tall(adsValidation); % 2. Extract features from the training set. Reorient the features % so that time is along rows to be compatible with % sequenceInputLayer. tallTrain = cellfun(@(x)x/max(abs(x),[],'all'),tallTrain,"UniformOutput",false); tallFeaturesTrain = cellfun(@(x)extract(extractor,x),tallTrain,"UniformOutput",false); tallFeaturesTrain = cellfun(@(x)x',tallFeaturesTrain,"UniformOutput",false); %#ok<NASGU> [~,featuresTrain] = evalc('gather(tallFeaturesTrain)'); % Use evalc to suppress command-line output. tallValidation = cellfun(@(x)x/max(abs(x),[],'all'),tallValidation,"UniformOutput",false); tallFeaturesValidation = cellfun(@(x)extract(extractor,x),tallValidation,"UniformOutput",false); tallFeaturesValidation = cellfun(@(x)x',tallFeaturesValidation,"UniformOutput",false); %#ok<NASGU> [~,featuresValidation] = evalc('gather(tallFeaturesValidation)'); % Use evalc to suppress command-line output. % 3. Use the training set to determine the mean and standard % deviation of each feature. Normalize the training and validation % sets. allFeatures = cat(2,featuresTrain{:}); M = mean(allFeatures,2,'omitnan'); S = std(allFeatures,0,2,'omitnan'); featuresTrain = cellfun(@(x)(x-M)./S,featuresTrain,'UniformOutput',false); for ii = 1:numel(featuresTrain) idx = find(isnan(featuresTrain{ii})); if ~isempty(idx) featuresTrain{ii}(idx) = 0; end end featuresValidation = cellfun(@(x)(x-M)./S,featuresValidation,'UniformOutput',false); for ii = 1:numel(featuresValidation) idx = find(isnan(featuresValidation{ii})); if ~isempty(idx) featuresValidation{ii}(idx) = 0; end end % 4. Buffer the sequences so that each sequence consists of twenty % feature vectors with overlaps of 10 feature vectors. featureVectorsPerSequence = 20; featureVectorOverlap = 10; [sequencesTrain,sequencePerFileTrain] = HelperFeatureVector2Sequence(featuresTrain,featureVectorsPerSequence,featureVectorOverlap); [sequencesValidation,sequencePerFileValidation] = HelperFeatureVector2Sequence(featuresValidation,featureVectorsPerSequence,featureVectorOverlap); % 5. Replicate the labels of the train and validation sets so that % they are in one-to-one correspondence with the sequences. labelsTrain = [emptyEmotions;augadsTrain.Labels]; labelsTrain = labelsTrain(:); labelsTrain = repelem(labelsTrain,[sequencePerFileTrain{:}]); % 6. Define a BiLSTM network. dropoutProb1 = 0.3; numUnits = 200; dropoutProb2 = 0.6; layers = [ ... sequenceInputLayer(size(sequencesTrain{1},1)) dropoutLayer(dropoutProb1) bilstmLayer(numUnits,"OutputMode","last") dropoutLayer(dropoutProb2) fullyConnectedLayer(numel(categories(emptyEmotions))) softmaxLayer classificationLayer]; % 7. Define training options. miniBatchSize = 512; initialLearnRate = 0.005; learnRateDropPeriod = 2; maxEpochs = 3; options = trainingOptions("adam", ... "MiniBatchSize",miniBatchSize, ... "InitialLearnRate",initialLearnRate, ... "LearnRateDropPeriod",learnRateDropPeriod, ... "LearnRateSchedule","piecewise", ... "MaxEpochs",maxEpochs, ... "Shuffle","every-epoch", ... "Verbose",false); % 8. Train the network. net = trainNetwork(sequencesTrain,labelsTrain,layers,options); % 9. Evaluate the network. Call classify to get the predicted labels % for each sequence. Get the mode of the predicted labels of each % sequence to get the predicted labels of each file. predictedLabelsPerSequence = classify(net,sequencesValidation); trueLabels = categorical(adsValidation.Labels); predictedLabels = trueLabels; idx1 = 1; for ii = 1:numel(trueLabels) predictedLabels(ii,:) = mode(predictedLabelsPerSequence(idx1:idx1 + sequencePerFileValidation{ii} - 1,:),1); idx1 = idx1 + sequencePerFileValidation{ii}; end trueLabelsCrossFold{i} = trueLabels; %#ok<AGROW> predictedLabelsCrossFold{i} = predictedLabels; %#ok<AGROW> end end
[1] Буркхардт, Ф., А. Паешке, М. Рольфес, В. Ф. Сендлмайер и Б. Вайс, «База данных немецкой эмоциональной речи». В Proceedings Interspeech 2005. Лиссабон, Португалия: Международная ассоциация речевой коммуникации, 2005 год.
bilstmLayer | sequenceInputLayer | trainingOptions | trainNetwork