Этот пример иллюстрирует простую систему распознавания эмоций речи (SER), использующую сеть BiLSTM. Вы начинаете с загрузки набора данных и затем тестирования обученной сети на отдельных файлах. Сеть обучалась на небольшой базе данных на немецком языке [1].
В примере вы проходите через обучение сети, которое включает загрузку, увеличение и обучение набора данных. Наконец, вы выполняете 10-кратную перекрестную валидацию типа «left-one-speaker-out» (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;
Выберите динамик и эмоции, затем подставьте datastore, чтобы включить только выбранный динамик и эмоции. Прочтите из datastore и прослушайте файл.
speaker = categorical("03"); эмоции = categorical("Disgust"); adsSubset = подмножество (объявления, ads.Labels.Speaker = = динамик и объявления. Labels.Emotion = = эмоции); audio = read (adsSubset); звук (аудио, 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 среднее значение case 'mean' пробники = среднее (YPred, 1); case 'median' пробники = медиана (YPred, 1); case 'mode' пробники = режим (YPred, 1); end pie (probs ./sum (probs), строка (net.Layers (end) .Classes))
Остальная часть примера иллюстрирует, как сеть была обучена и проверена.
10-кратная точность перекрестной валидации первой попытки обучения составила около 60% из-за недостаточности обучающих данных. Модель, обученная на недостаточных данных, перегружает некоторые складки и подгоняет другие. Чтобы улучшить общую подгонку, увеличьте размер набора данных с помощью audioDataAugmenter
(Audio Toolbox). 50 увеличение на файл было выбрано эмпирически как хороший компромисс между временем вычислений и улучшением точности. Вы можете уменьшить количество увеличений, чтобы ускорить пример.
Создайте audioDataAugmenter
объект. Установите вероятность применения смещения тангажа на 0.5
и используйте область значений по умолчанию. Установите вероятность применения временного переключения на 1
и использовать область значений [-0.3,0.3]
секунд. Установите вероятность добавления шума в 1
и задайте область значений ОСШ следующим [-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)
Для каждого файла в audio datastore:
Создайте 50 дополнений.
Нормализуйте аудио, чтобы иметь максимальное абсолютное значение 1.
Запишите дополненный аудио данные как WAV- файла. Добавление _
augK
каждому из имен файлов, где K является номером увеличения. Чтобы ускорить обработку, используйте parfor
и разделите datastore.
Этот метод увеличения базы данных занимает много времени (примерно 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).
Создайте audio datastore, который указывает на дополненный набор данных. Реплицируйте строки таблицы меток исходного datastore NumAugmentations
время для определения меток дополненного datastore.
adsAug = audioDatastore(writeDirectory); adsAug.Labels = repelem(ads.Labels,augmenter.NumAugmentations,1);
Создайте audioFeatureExtractor
(Audio Toolbox) объект. Задайте Window
к периодической 30 мс Окна Хэмминга, OverlapLength
на 0
, и SampleRate
к частоте дискретизации базы данных. Задайте gtcc
, gtccDelta
, mfccDelta
, и spectralCrest
на true
извлечь их. Задайте SpectralDescriptorInput
на melSpectrum
так что spectralCrest
вычисляется для мел- спектр.
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);
Когда вы обучаете для развертывания, используйте все доступные динамики в наборе данных. Установите training datastore в дополненный datastore.
adsTrain = adsAug;
Преобразуйте обучающий audio datastore в длинный массив array. Если у вас есть Parallel Computing 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 нормализаторы. Mean = M; normalizers.StandardDeviation = S; сохранить'network_Audio_SER.mat','net','afe','normalizers') end
Чтобы предоставить точную оценку модели, которую вы создали в этом примере, train и проверьте, используя кросс- валидацию leave-one-speaker-out (LOSO) k-fold. В этом методе вы обучаете, используя а затем проверьте на левом динамике. Вы повторяете эту процедуру 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] Burkhardt, F., A. Paeschke, M. Rolfes, W.F. Sendlmeier, and B. Weiss, «A Database of Немецкая эмоциональная речь». В Proceedings Interspeech 2005. Лиссабон, Португалия: Международная ассоциация речевых коммуникаций, 2005 год.
bilstmLayer
| sequenceInputLayer
| trainingOptions
| trainNetwork