В этом примере показано, как классифицировать пол докладчика, использующего глубокое обучение. Пример использует сеть Gated Recurrent Unit (GRU) и Коэффициенты Gammatone Cepstral (gtcc), тангаж, гармоническое отношение и несколько спектральных дескрипторов формы.
Классификация полов на основе речевых сигналов является важной составляющей многих аудиосистем, таких как автоматическое распознавание речи, распознавание динамика и мультимедийная индексация на основе содержимого.
Этот пример использует сети ГРУ, тип рекуррентной нейронной сети (RNN), подходящей, чтобы изучить данные timeseries и последовательность. Сеть ГРУ может изучить долгосрочные зависимости между временными шагами последовательности.
Этот пример обучает сеть ГРУ с последовательностями gammatone коэффициентов кепстра (gtcc
), оценки тангажа (pitch
), гармоническое отношение (harmonicRatio
), и несколько спектральных дескрипторов формы (Спектральные Дескрипторы).
Чтобы ускорить учебный процесс, запустите этот пример на машине с помощью графического процессора. Если ваша машина имеет графический процессор и Parallel Computing Toolbox™, то MATLAB© автоматически использует графический процессор для обучения; в противном случае это использует центральный процессор.
Перед входом в учебный процесс подробно, вы будете использовать предварительно обученную сеть, чтобы классифицировать пол докладчика в двух тестовых сигналах.
Загрузите предварительно обученную сеть.
url = 'http://ssd.mathworks.com/supportfiles/audio/GenderClassification.zip'; downloadNetFolder = tempdir; netFolder = fullfile(downloadNetFolder,'GenderClassification'); if ~exist(netFolder,'dir') unzip(url,downloadNetFolder) end
Загрузите предварительно обученную сеть наряду с предварительно вычисленными векторами, используемыми для нормализации функции.
matFileName = fullfile(netFolder, 'genderIDNet.mat'); load(matFileName,'genderIDNet','M','S');
Загрузите тестовый сигнал со спикером.
[audioIn,Fs] = audioread('maleSpeech.flac');
sound(audioIn,Fs)
Изолируйте речевую область в сигнале.
boundaries = detectSpeech(audioIn,Fs); audioIn = audioIn(boundaries(1):boundaries(2));
Создайте audioFeatureExtractor
извлекать функции из аудиоданных. Вы будете использовать тот же объект извлечь функции обучения.
extractor = audioFeatureExtractor( ... "SampleRate",Fs, ... "Window",hamming(round(0.03*Fs),"periodic"), ... "OverlapLength",round(0.02*Fs), ... ... "gtcc",true, ... "gtccDelta",true, ... "gtccDeltaDelta",true, ... ... "SpectralDescriptorInput","melSpectrum", ... "spectralCentroid",true, ... "spectralEntropy",true, ... "spectralFlux",true, ... "spectralSlope",true, ... ... "pitch",true, ... "harmonicRatio",true);
Извлеките функции из сигнала и нормируйте их.
features = extract(extractor,audioIn); features = (features.' - M)./S;
Классифицируйте сигнал.
gender = classify(genderIDNet,features)
gender = categorical
male
Классифицируйте другой сигнал с женщиной-спикером.
[audioIn,Fs] = audioread('femaleSpeech.flac');
sound(audioIn,Fs)
boundaries = detectSpeech(audioIn,Fs); audioIn = audioIn(boundaries(1):boundaries(2)); features = extract(extractor,audioIn); features = (features.' - M)./S; classify(genderIDNet,features)
ans = categorical
female
Сеть ГРУ, используемая в этом примере, работает лучше всего при использовании последовательностей характеристических векторов. Чтобы проиллюстрировать конвейер предварительной обработки, этот пример идет через шаги для одного звукового файла.
Считайте содержимое звукового файла, содержащего речь. Пол докладчика является штекером.
[audioIn,Fs] = audioread('Counting-16-44p1-mono-15secs.wav'); labels = {'male'};
Постройте звуковой сигнал и затем слушайте его с помощью sound
команда.
timeVector = (1/Fs) * (0:size(audioIn,1)-1); figure plot(timeVector,audioIn) ylabel("Amplitude") xlabel("Time (s)") title("Sample Audio") grid on
sound(audioIn,Fs)
Речевой сигнал имеет сегменты тишины, которые не содержат полезную информацию, имеющую отношение к полу докладчика. Используйте detectSpeech
определять местоположение сегментов речи в звуковом сигнале.
speechIndices = detectSpeech(audioIn,Fs);
Создайте audioFeatureExtractor
извлекать функции из аудиоданных. Речевой сигнал является динамическим по своей природе и изменяется в зависимости от времени. Это принято, что речевые сигналы стационарные по кратковременным шкалам, и их обработка часто делается в окнах 20-40 мс. Задайте 30 MS Windows с перекрытием на 20 мс.
extractor = audioFeatureExtractor( ... "SampleRate",Fs, ... "Window",hamming(round(0.03*Fs),"periodic"), ... "OverlapLength",round(0.02*Fs), ... ... "gtcc",true, ... "gtccDelta",true, ... "gtccDeltaDelta",true, ... ... "SpectralDescriptorInput","melSpectrum", ... "spectralCentroid",true, ... "spectralEntropy",true, ... "spectralFlux",true, ... "spectralSlope",true, ... ... "pitch",true, ... "harmonicRatio",true);
Извлеките функции из каждого аудио сегмента. Выход от audioFeatureExtractor
numFeatureVectors
- numFeatures
массив. sequenceInputLayer
используемый в этом примере требует, чтобы время приехало второе измерение. Переставьте выходной массив так, чтобы время приехало второе измерение.
featureVectorsSegment = {}; for ii = 1:size(speechIndices,1) featureVectorsSegment{end+1} = ( extract(extractor,audioIn(speechIndices(ii,1):speechIndices(ii,2))) )'; end numSegments = size(featureVectorsSegment)
numSegments = 1×2
1 11
[numFeatures,numFeatureVectorsSegment1] = size(featureVectorsSegment{1})
numFeatures = 45
numFeatureVectorsSegment1 = 124
Реплицируйте метки так, чтобы они были во взаимно-однозначном соответствии с сегментами.
labels = repelem(labels,size(speechIndices,1))
labels = 1×11 cell
{'male'} {'male'} {'male'} {'male'} {'male'} {'male'} {'male'} {'male'} {'male'} {'male'} {'male'}
При использовании sequenceInputLayer
, часто выгодно использовать последовательности сопоставимой длины. Преобразуйте массивы характеристических векторов в последовательности характеристических векторов. Используйте 20 характеристических векторов на последовательность с 5 перекрытиями характеристического вектора.
featureVectorsPerSequence = 20; featureVectorOverlap = 5; hopLength = featureVectorsPerSequence - featureVectorOverlap; idx1 = 1; featuresTrain = {}; sequencePerSegment = zeros(numel(featureVectorsSegment),1); for ii = 1:numel(featureVectorsSegment) sequencePerSegment(ii) = max(floor((size(featureVectorsSegment{ii},2) - featureVectorsPerSequence)/hopLength) + 1,0); idx2 = 1; for j = 1:sequencePerSegment(ii) featuresTrain{idx1,1} = featureVectorsSegment{ii}(:,idx2:idx2 + featureVectorsPerSequence - 1); idx1 = idx1 + 1; idx2 = idx2 + hopLength; end end
Для краткости функция помощника HelperFeatureVector2Sequence инкапсулирует вышеупомянутую обработку и используется в остальной части примера.
Реплицируйте метки так, чтобы они были во взаимно-однозначном соответствии с набором обучающих данных.
labels = repelem(labels,sequencePerSegment);
Результатом конвейера предварительной обработки является NumSequence
- 1 массив ячеек NumFeatures
- FeatureVectorsPerSequence
матрицы. Метками является NumSequence
- 1 массив.
NumSequence = numel(featuresTrain)
NumSequence = 27
[NumFeatures,FeatureVectorsPerSequence] = size(featuresTrain{1})
NumFeatures = 45
FeatureVectorsPerSequence = 20
NumSequence = numel(labels)
NumSequence = 27
Фигура предоставляет обзор извлечения признаков, используемого на обнаруженную речевую область.
Этот пример использует подмножество набора данных Mozilla Common Voice [1]. Набор данных содержит записи на 48 кГц предметов говорящие короткие предложения. Загрузите набор данных и untar загруженный файл. Установите PathToDatabase
к местоположению данных.
url = 'http://ssd.mathworks.com/supportfiles/audio/commonvoice.zip'; downloadDatasetFolder = tempdir; dataFolder = fullfile(downloadDatasetFolder,'commonvoice'); if ~exist(dataFolder,'dir') disp('Downloading data set (956 MB) ...') unzip(url,downloadDatasetFolder) end
Downloading data set (956 MB) ...
Используйте audioDatastore
создать хранилища данных для наборов обучения и валидации. Используйте readtable
считать метаданные, сопоставленные со звуковыми файлами.
loc = fullfile(dataFolder); adsTrain = audioDatastore(fullfile(loc,'train'),'IncludeSubfolders',true); metadataTrain = readtable(fullfile(fullfile(loc,'train'),"train.tsv"),"FileType","text"); adsTrain.Labels = metadataTrain.gender; adsValidation = audioDatastore(fullfile(loc,'validation'),'IncludeSubfolders',true); metadataValidation = readtable(fullfile(fullfile(loc,'validation'),"validation.tsv"),"FileType","text"); adsValidation.Labels = metadataValidation.gender;
Используйте countEachLabel
смотреть гендерный отказ наборов обучения и валидации.
countEachLabel(adsTrain)
ans=2×2 table
Label Count
______ _____
female 1000
male 1000
countEachLabel(adsValidation)
ans=2×2 table
Label Count
______ _____
female 200
male 200
Чтобы обучить сеть с набором данных в целом и достигнуть максимально возможной точности, установите reduceDataset
к false
. Чтобы запустить этот пример быстро, установите reduceDataset
к true
.
reduceDataset = false; if reduceDataset % Reduce the training dataset by a factor of 20 adsTrain = splitEachLabel(adsTrain,round(numel(adsTrain.Files) / 2 / 20)); adsValidation = splitEachLabel(adsValidation,20); end
Определите частоту дискретизации звуковых файлов в наборе данных, и затем обновите частоту дискретизации, окно и длину перекрытия экстрактора функции аудио.
[~,adsInfo] = read(adsTrain);
Fs = adsInfo.SampleRate;
extractor.SampleRate = Fs;
extractor.Window = hamming(round(0.03*Fs),"periodic");
extractor.OverlapLength = round(0.02*Fs);
Чтобы ускорить обработку, распределите расчеты по нескольким рабочим. Если у вас есть Parallel Computing Toolbox™, пример делит datastore так, чтобы извлечение признаков произошло параллельно через доступных рабочих. Определите оптимальное количество разделов для вашей системы. Если у вас нет Parallel Computing Toolbox™, пример использует одного рабочего.
if ~isempty(ver('parallel')) && ~reduceDataset pool = gcp; numPar = numpartitions(adsTrain,pool); else numPar = 1; end
Starting parallel pool (parpool) using the 'local' profile ... Connected to the parallel pool (number of workers: 6).
В цикле:
Читайте из аудио datastore.
Обнаружьте области речи.
Извлеките характеристические векторы из областей речи.
Реплицируйте метки так, чтобы они были во взаимно-однозначном соответствии с характеристическими векторами.
labelsTrain = []; featureVectors = {}; % Loop over optimal number of partitions parfor ii = 1:numPar % Partition datastore subds = partition(adsTrain,numPar,ii); % Preallocation featureVectorsInSubDS = {}; segmentsPerFile = zeros(numel(subds.Files),1); % Loop over files in partitioned datastore for jj = 1:numel(subds.Files) % 1. Read in a single audio file audioIn = read(subds); % 2. Determine the regions of the audio that correspond to speech speechIndices = detectSpeech(audioIn,Fs); % 3. Extract features from each speech segment segmentsPerFile(jj) = size(speechIndices,1); features = cell(segmentsPerFile(jj),1); for kk = 1:size(speechIndices,1) features{kk} = ( extract(extractor,audioIn(speechIndices(kk,1):speechIndices(kk,2))) )'; end featureVectorsInSubDS = [featureVectorsInSubDS;features(:)]; end featureVectors = [featureVectors;featureVectorsInSubDS]; % Replicate the labels so that they are in one-to-one correspondance % with the feature vectors. repedLabels = repelem(subds.Labels,segmentsPerFile); labelsTrain = [labelsTrain;repedLabels(:)]; end
В приложениях классификации это - хорошая практика, чтобы нормировать все функции, чтобы иметь нулевое среднее значение и стандартное отклонение единицы.
Вычислите среднее и стандартное отклонение для каждого коэффициента и используйте их, чтобы нормировать данные.
allFeatures = cat(2,featureVectors{:}); allFeatures(isinf(allFeatures)) = nan; M = mean(allFeatures,2,'omitnan'); S = std(allFeatures,0,2,'omitnan'); featureVectors = cellfun(@(x)(x-M)./S,featureVectors,'UniformOutput',false); for ii = 1:numel(featureVectors) idx = find(isnan(featureVectors{ii})); if ~isempty(idx) featureVectors{ii}(idx) = 0; end end
Буферизуйте характеристические векторы в последовательности 20 характеристических векторов с 10 перекрытиями. Если последовательность имеет меньше чем 20 характеристических векторов, пропустите ее.
[featuresTrain,trainSequencePerSegment] = HelperFeatureVector2Sequence(featureVectors,featureVectorsPerSequence,featureVectorOverlap);
Реплицируйте метки так, чтобы они были во взаимно-однозначном соответствии с последовательностями.
labelsTrain = repelem(labelsTrain,[trainSequencePerSegment{:}]); labelsTrain = categorical(labelsTrain);
Создайте набор валидации с помощью тех же шагов, используемых, чтобы создать набор обучающих данных.
labelsValidation = []; featureVectors = {}; valSegmentsPerFile = []; parfor ii = 1:numPar subds = partition(adsValidation,numPar,ii); featureVectorsInSubDS = {}; valSegmentsPerFileInSubDS = zeros(numel(subds.Files),1); for jj = 1:numel(subds.Files) audioIn = read(subds); speechIndices = detectSpeech(audioIn,Fs); numSegments = size(speechIndices,1); features = cell(valSegmentsPerFileInSubDS(jj),1); for kk = 1:numSegments features{kk} = ( extract(extractor,audioIn(speechIndices(kk,1):speechIndices(kk,2))) )'; end featureVectorsInSubDS = [featureVectorsInSubDS;features(:)]; valSegmentsPerFileInSubDS(jj) = numSegments; end repedLabels = repelem(subds.Labels,valSegmentsPerFileInSubDS); labelsValidation = [labelsValidation;repedLabels(:)]; featureVectors = [featureVectors;featureVectorsInSubDS]; valSegmentsPerFile = [valSegmentsPerFile;valSegmentsPerFileInSubDS]; end featureVectors = cellfun(@(x)(x-M)./S,featureVectors,'UniformOutput',false); for ii = 1:numel(featureVectors) idx = find(isnan(featureVectors{ii})); if ~isempty(idx) featureVectors{ii}(idx) = 0; end end [featuresValidation,valSequencePerSegment] = HelperFeatureVector2Sequence(featureVectors,featureVectorsPerSequence,featureVectorOverlap); labelsValidation = repelem(labelsValidation,[valSequencePerSegment{:}]); labelsValidation = categorical(labelsValidation);
Сети ГРУ могут изучить долгосрочные зависимости между временными шагами данных о последовательности. Этот пример использует gruLayer
смотреть на последовательность и во вперед и в обратные направления.
Задайте входной размер, чтобы быть последовательностями размера NumFeatures
. Задайте слой ГРУ с выходным размером 75 и выведите последовательность. Затем задайте слой ГРУ с выходным размером 75 и выведите последний элемент последовательности. Эта команда дает слою ГРУ команду сопоставлять свой вход в 75 функций и затем готовит выход к полносвязному слою. Наконец, задайте два класса включением полносвязного слоя размера 2, сопровождаемый softmax слоем и слоем классификации.
layers = [ ... sequenceInputLayer(size(featuresTrain{1},1)) gruLayer(75,"OutputMode","sequence") gruLayer(75,"OutputMode","last") fullyConnectedLayer(2) softmaxLayer classificationLayer];
Затем задайте опции обучения для классификатора. Установите MaxEpochs
к 4
так, чтобы сеть сделала 4, проходит через обучающие данные. Установите MiniBatchSize
из 256 так, чтобы сеть посмотрела на 128 учебных сигналов за один раз. Задайте Plots
как "training-progress"
сгенерировать графики, которые показывают процесс обучения количеством увеличений итераций. Установите Verbose
к false
отключить печать таблицы выход, который соответствует данным, показанным в графике. Задайте Shuffle
как "every-epoch"
переставить обучающую последовательность в начале каждой эпохи. Задайте LearnRateSchedule
к "piecewise"
чтобы уменьшить скорость обучения заданным фактором (0.1) каждый раз, определенное число эпох (1) передало.
Этот пример использует адаптивную оценку момента (ADAM) решатель. ADAM выполняет лучше с рекуррентными нейронными сетями (RNNs) как GRUs, чем стохастический градиентный спуск по умолчанию с импульсом (SGDM) решатель.
miniBatchSize = 256; validationFrequency = floor(numel(labelsTrain)/miniBatchSize); options = trainingOptions("adam", ... "MaxEpochs",4, ... "MiniBatchSize",miniBatchSize, ... "Plots","training-progress", ... "Verbose",false, ... "Shuffle","every-epoch", ... "LearnRateSchedule","piecewise", ... "LearnRateDropFactor",0.1, ... "LearnRateDropPeriod",1, ... 'ValidationData',{featuresValidation,labelsValidation}, ... 'ValidationFrequency',validationFrequency);
Обучите сеть ГРУ с заданными опциями обучения и архитектурой слоя с помощью trainNetwork
. Поскольку набор обучающих данных является большим, учебный процесс может занять несколько минут.
net = trainNetwork(featuresTrain,labelsTrain,layers,options);
Главный подграфик графика процесса обучения представляет учебную точность, которая является точностью классификации на каждом мини-пакете. Когда обучение прогрессирует успешно, это значение обычно увеличивается к 100%. Нижний подграфик отображает учебную потерю, которая является потерей перекрестной энтропии на каждом мини-пакете. Когда обучение прогрессирует успешно, это значение обычно уменьшается по направлению к нулю.
Если обучение не сходится, графики могут колебаться между значениями, не отклоняясь в определенном восходящем или нисходящем направлении. Это колебание означает, что учебная точность не улучшается, и учебная потеря не уменьшается. Эта ситуация может произойти в начале обучения, или после некоторого предварительного улучшения учебной точности. Во многих случаях изменение опций обучения может помочь сети достигнуть сходимости. Уменьшение MiniBatchSize
или уменьшение InitialLearnRate
может закончиться в более длительное учебное время, но оно может помочь сети учиться лучше.
Вычислите учебную точность, которая представляет точность классификатора на сигналах, на которых это было обучено. Во-первых, классифицируйте обучающие данные.
prediction = classify(net,featuresTrain);
Постройте матрицу беспорядка. Отобразите точность и отзыв для этих двух классов при помощи сводных данных строки и столбца.
figure cm = confusionchart(categorical(labelsTrain),prediction,'title','Training Accuracy'); cm.ColumnSummary = 'column-normalized'; cm.RowSummary = 'row-normalized';
Вычислите точность валидации. Во-первых, классифицируйте обучающие данные.
[prediction,probabilities] = classify(net,featuresValidation);
Постройте матрицу беспорядка. Отобразите точность и отзыв для этих двух классов при помощи сводных данных строки и столбца.
figure cm = confusionchart(categorical(labelsValidation),prediction,'title','Validation Set Accuracy'); cm.ColumnSummary = 'column-normalized'; cm.RowSummary = 'row-normalized';
Пример сгенерировал несколько последовательностей из каждого учебного речевого файла. Более высокая точность может быть достигнута путем рассмотрения выходного класса всех последовательностей, соответствующих тому же файлу и применяющих решение "макс. правила", где класс с сегментом с самой высокой оценкой достоверности выбран.
Определите количество последовательностей, сгенерированных на файл в наборе валидации.
sequencePerFile = zeros(size(valSegmentsPerFile)); valSequencePerSegmentMat = cell2mat(valSequencePerSegment); idx = 1; for ii = 1:numel(valSegmentsPerFile) sequencePerFile(ii) = sum(valSequencePerSegmentMat(idx:idx+valSegmentsPerFile(ii)-1)); idx = idx + valSegmentsPerFile(ii); end
Предскажите пол из каждого учебного файла путем считания выходных классов всех последовательностей сгенерированными из того же файла.
numFiles = numel(adsValidation.Files); actualGender = categorical(adsValidation.Labels); predictedGender = actualGender; scores = cell(1,numFiles); counter = 1; cats = unique(actualGender); for index = 1:numFiles scores{index} = probabilities(counter: counter + sequencePerFile(index) - 1,:); m = max(mean(scores{index},1),[],1); if m(1) >= m(2) predictedGender(index) = cats(1); else predictedGender(index) = cats(2); end counter = counter + sequencePerFile(index); end
Визуализируйте матрицу беспорядка на предсказаниях принципа большинства.
figure cm = confusionchart(actualGender,predictedGender,'title','Validation Set Accuracy - Max Rule'); cm.ColumnSummary = 'column-normalized'; cm.RowSummary = 'row-normalized';
[1] Mozilla общий речевой набор данных
function [sequences,sequencePerSegment] = HelperFeatureVector2Sequence(features,featureVectorsPerSequence,featureVectorOverlap) if featureVectorsPerSequence <= featureVectorOverlap error('The number of overlapping feature vectors must be less than the number of feature vectors per sequence.') end hopLength = featureVectorsPerSequence - featureVectorOverlap; idx1 = 1; sequences = {}; sequencePerSegment = cell(numel(features),1); for ii = 1:numel(features) sequencePerSegment{ii} = max(floor((size(features{ii},2) - featureVectorsPerSequence)/hopLength) + 1,0); idx2 = 1; for j = 1:sequencePerSegment{ii} sequences{idx1,1} = features{ii}(:,idx2:idx2 + featureVectorsPerSequence - 1); %#ok<AGROW> idx1 = idx1 + 1; idx2 = idx2 + hopLength; end end end