exponenta event banner

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

Этот пример иллюстрирует простую систему распознавания речевых эмоций (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)

Для каждого файла в хранилище аудиоданных:

  1. Создайте 50 дополнений.

  2. Нормализуйте звук, чтобы получить максимальное абсолютное значение 1.

  3. Запишите дополненные аудиоданные как 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 год.

См. также

| | |

Связанные темы