Этот пример показов, как классифицировать разговорные цифры с помощью как машинного, так и глубокого метода обучения. В примере вы выполняете классификацию, используя рассеяние вейвлета времени с помощью машины опорных векторов (SVM) и с помощью сети долгой краткосрочной памяти (LSTM). Вы также применяете байесовскую оптимизацию, чтобы определить подходящие гиперпараметры, чтобы улучшить точность сети LSTM. Кроме сложения, пример иллюстрирует подход, использующий глубокую сверточную нейронную сеть (CNN) и мел-частотные спектрограммы.
Клон или скачать бесплатный набор данных Spoken Digit (FSDD), доступный в https://github.com/Jakobovski/free-spoken-digit-dataset. FSDD является открытым набором данных, что означает, что он может расти с течением времени. Этот пример использует версию, совершенную 29 января 2019 года, которая состоит из 2000 записей на английском языке цифр от 0 до 9, полученных от четырёх дикторов. В этой версии два носителя являются носителями американского английского языка, один носитель является неонативным носителем английского языка с бельгийским французским акцентом, а один носитель является неонативным носителем английского с немецким акцентом. Данные отбираются с частотой дискретизации 8000 Гц.
Использование audioDatastore
управлять доступом к данным и обеспечивать случайное деление записей на обучающие и тестовые наборы. Установите location
свойство расположения папки записей FSDD на вашем компьютере, например:
pathToRecordingsFolder = fullfile(tempdir,'free-spoken-digit-dataset-master','recordings'); location = pathToRecordingsFolder;
Точечные audioDatastore
в это место.
ads = audioDatastore(location);
Функция помощника helpergenLabels
создает категориальный массив меток из файлов FSDD. Исходный код для helpergenLabels
приведено в приложении. Перечислите классы и количество примеров в каждом классе.
ads.Labels = helpergenLabels(ads); summary(ads.Labels)
0 300 1 300 2 300 3 300 4 300 5 300 6 300 7 300 8 300 9 300
Набор данных FSDD состоит из 10 сбалансированных классов с 200 записями каждый. Записи в FSDD не имеют равной длительности. FSDD не является запрещенно большим, поэтому считывайте файлы FSDD и создайте гистограмму длин сигнала.
LenSig = zeros(numel(ads.Files),1); nr = 1; while hasdata(ads) digit = read(ads); LenSig(nr) = numel(digit); nr = nr+1; end reset(ads) histogram(LenSig) grid on xlabel('Signal Length (Samples)') ylabel('Frequency')
Гистограмма показывает, что распределение длин записи положительно искажено. Для классификации в этом примере используется общая длина сигнала 8192 выборок, консервативное значение, которое гарантирует, что усечение более длинных записей не отсекает содержимое речи. Если длина сигнала превышает 8192 выборки (1,024 секунды), запись обрезается до 8192 выборки. Если длина сигнала меньше 8192 отсчетов, сигнал предварительно складывается и откладывается симметрично нулями до длины 8192 отсчетов.
Использование waveletScattering
(Wavelet Toolbox), чтобы создать среду вейвлет рассеяния с помощью инвариантной шкалы 0,22 секунды. В этом примере вы создаете векторы функций путем усреднения преобразования рассеяния во всех временных выборках. Чтобы иметь достаточное количество коэффициентов рассеяния в каждом временном окне для среднего значения, установите OversamplingFactor
2 получить четырехкратное увеличение числа коэффициентов рассеяния для каждого пути относительно критически пониженного значения.
sf = waveletScattering('SignalLength',8192,'InvarianceScale',0.22,... 'SamplingFrequency',8000,'OversamplingFactor',2);
Разделите FSDD на обучающие и тестовые наборы. Выделите 80% данных наборов обучающих данных и сохраните 20% для тестового набора. Обучающие данные предназначены для настройки классификатора на основе преобразования рассеяния. Тестовые данные предназначены для валидации модели.
rng default;
ads = shuffle(ads);
[adsTrain,adsTest] = splitEachLabel(ads,0.8);
countEachLabel(adsTrain)
ans=10×2 table
Label Count
_____ _____
0 240
1 240
2 240
3 240
4 240
5 240
6 240
7 240
8 240
9 240
countEachLabel(adsTest)
ans=10×2 table
Label Count
_____ _____
0 60
1 60
2 60
3 60
4 60
5 60
6 60
7 60
8 60
9 60
Функция помощника helperReadSPData
обрезает или заполняет данные до длины 8192 и нормализует каждую запись по ее максимальному значению. Исходный код для helperReadSPData
приведено в приложении. Создайте матрицу 8192 на 1600, где каждый столбец является записью разговорных цифр.
Xtrain = []; scatds_Train = transform(adsTrain,@(x)helperReadSPData(x)); while hasdata(scatds_Train) smat = read(scatds_Train); Xtrain = cat(2,Xtrain,smat); end
Повторите процесс для тестового набора. Получившаяся матрица 8192 на 400.
Xtest = []; scatds_Test = transform(adsTest,@(x)helperReadSPData(x)); while hasdata(scatds_Test) smat = read(scatds_Test); Xtest = cat(2,Xtest,smat); end
Примените преобразование вейвлет к обучающим и тестовым наборам.
Strain = sf.featureMatrix(Xtrain); Stest = sf.featureMatrix(Xtest);
Получите средние функции рассеяния для обучающих и тестовых наборов. Исключить коэффициенты рассеяния нулевого порядка.
TrainFeatures = Strain(2:end,:,:); TrainFeatures = squeeze(mean(TrainFeatures,2))'; TestFeatures = Stest(2:end,:,:); TestFeatures = squeeze(mean(TestFeatures,2))';
Теперь, когда данные были сведены к вектору функций для каждой записи, следующим шагом является использование этих функций для классификации записей. Создайте шаблон SVM с квадратичным полиномиальным ядром. Подбор SVM к обучающим данным.
template = templateSVM(... 'KernelFunction', 'polynomial', ... 'PolynomialOrder', 2, ... 'KernelScale', 'auto', ... 'BoxConstraint', 1, ... 'Standardize', true); classificationSVM = fitcecoc(... TrainFeatures, ... adsTrain.Labels, ... 'Learners', template, ... 'Coding', 'onevsone', ... 'ClassNames', categorical({'0'; '1'; '2'; '3'; '4'; '5'; '6'; '7'; '8'; '9'}));
Используйте перекрестную валидацию k-fold, чтобы предсказать точность обобщения модели на основе обучающих данных. Разделите набор обучающих данных на пять групп.
partitionedModel = crossval(classificationSVM, 'KFold', 5); [validationPredictions, validationScores] = kfoldPredict(partitionedModel); validationAccuracy = (1 - kfoldLoss(partitionedModel, 'LossFun', 'ClassifError'))*100
validationAccuracy = 97.4167
Предполагаемая точность обобщения составляет приблизительно 97%. Используйте обученный SVM, чтобы предсказать классы разговорных цифр в тестовом наборе.
predLabels = predict(classificationSVM,TestFeatures); testAccuracy = sum(predLabels==adsTest.Labels)/numel(predLabels)*100
testAccuracy = 97.1667
Результирующая эффективность модели на тестовом наборе с графиком неточностей. Отображение точности и отзыва для каждого класса с помощью сводных данных по столбцам и строкам. Таблица в нижней части графика неточностей показывает значения точности для каждого класса. Таблица справа от графика неточностей показывает значения отзыва.
figure('Units','normalized','Position',[0.2 0.2 0.5 0.5]); ccscat = confusionchart(adsTest.Labels,predLabels); ccscat.Title = 'Wavelet Scattering Classification'; ccscat.ColumnSummary = 'column-normalized'; ccscat.RowSummary = 'row-normalized';
Преобразование рассеяния, связанное с классификатором SVM, классифицирует разговорные цифры в тестовом наборе с точностью 98% (или частотой ошибок 2%).
Сеть LSTM является типом рекуррентной нейронной сети (RNN). RNN являются нейронными сетями, которые специализируются на работе с последовательными или временными данными, такими как речевые данные. Поскольку вейвлеты коэффициенты рассеяния являются последовательностями, они могут использоваться как входы к LSTM. Используя функции рассеяния в отличие от необработанных данных, можно уменьшить изменчивость, которую должна изучать сеть.
Измените функции обучения и тестирования рассеяния, которые будут использоваться в сети LSTM. Исключить коэффициенты рассеяния нулевого порядка и преобразовать функции в массивы ячеек.
TrainFeatures = Strain(2:end,:,:); TrainFeatures = squeeze(num2cell(TrainFeatures,[1 2])); TestFeatures = Stest(2:end,:,:); TestFeatures = squeeze(num2cell(TestFeatures, [1 2]));
Создайте простую сеть LSTM с 512 скрытыми слоями.
[inputSize, ~] = size(TrainFeatures{1}); YTrain = adsTrain.Labels; numHiddenUnits = 512; numClasses = numel(unique(YTrain)); layers = [ ... sequenceInputLayer(inputSize) lstmLayer(numHiddenUnits,'OutputMode','last') fullyConnectedLayer(numClasses) softmaxLayer classificationLayer];
Установите гиперпараметры. Используйте оптимизацию Adam и мини-пакет размером 50. Установите максимальное количество эпох равным 300. Используйте скорость обучения 1e-4. Можно выключить график процесса обучения, если вы не хотите отслеживать прогресс с помощью графиков. Обучение по умолчанию использует графический процессор, если он доступен. В противном случае используется центральный процессор. Для получения дополнительной информации смотрите trainingOptions
.
maxEpochs = 300; miniBatchSize = 50; options = trainingOptions('adam', ... 'InitialLearnRate',0.0001,... 'MaxEpochs',maxEpochs, ... 'MiniBatchSize',miniBatchSize, ... 'SequenceLength','shortest', ... 'Shuffle','every-epoch',... 'Verbose', false, ... 'Plots','training-progress');
Обучите сеть.
net = trainNetwork(TrainFeatures,YTrain,layers,options);
predLabels = classify(net,TestFeatures); testAccuracy = sum(predLabels==adsTest.Labels)/numel(predLabels)*100
testAccuracy = 96.3333
Определение подходящих установок гиперпараметров часто является одной из самых сложных частей обучения глубокой сети. Чтобы уменьшить это, можно использовать байесовскую оптимизацию. В этом примере вы оптимизируете количество скрытых слоев и начальную скорость обучения с помощью байесовских методов. Создайте новую директорию для хранения MAT-файлов, содержащих информацию о установках гиперпараметров и сети вместе с соответствующей частотой ошибок.
YTrain = adsTrain.Labels; YTest = adsTest.Labels; if ~exist("results/",'dir') mkdir results end
Инициализируйте оптимизированные переменные и их области значений значений. Поскольку количество скрытых слоев должно быть целым числом, задайте 'type'
на 'integer'
.
optVars = [ optimizableVariable('InitialLearnRate',[1e-5, 1e-1],'Transform','log') optimizableVariable('NumHiddenUnits',[10, 1000],'Type','integer') ];
Байесовская оптимизация является вычислительно интенсивной и может занять несколько часов, чтобы закончить. В целях этого примера задайте optimizeCondition
на false
загрузку и использование предопределенных оптимизированных установок гиперпараметров. Если вы задаете optimizeCondition
на true
, целевая функция helperBayesOptLSTM
минимизируется с помощью байесовской оптимизации. Целевой функцией, перечисленной в приложении, является вероятность ошибок сети при заданных установках гиперпараметров. Загруженные настройки предназначены для целевой функции минимум 0,02 (2% вероятность ошибки).
ObjFcn = helperBayesOptLSTM(TrainFeatures,YTrain,TestFeatures,YTest); optimizeCondition = false; if optimizeCondition BayesObject = bayesopt(ObjFcn,optVars,... 'MaxObjectiveEvaluations',15,... 'IsObjectiveDeterministic',false,... 'UseParallel',true); else url = 'http://ssd.mathworks.com/supportfiles/audio/SpokenDigitRecognition.zip'; downloadNetFolder = tempdir; netFolder = fullfile(downloadNetFolder,'SpokenDigitRecognition'); if ~exist(netFolder,'dir') disp('Downloading pretrained network (1 file - 12 MB) ...') unzip(url,downloadNetFolder) end load(fullfile(netFolder,'0.02.mat')); end
Downloading pretrained network (1 file - 12 MB) ...
Если вы выполняете байесовскую оптимизацию, генерируются цифры, подобные следующим, чтобы отследить значения целевой функции с соответствующими значениями гиперзначений параметров и количеством итераций. Можно увеличить количество итераций оптимизации Байеса, чтобы убедиться, что достигнут глобальный минимум целевой функции.
Используйте оптимизированные значения для количества скрытых модулей и начальной скорости обучения и переобучите сеть.
numHiddenUnits = 768; numClasses = numel(unique(YTrain)); layers = [ ... sequenceInputLayer(inputSize) lstmLayer(numHiddenUnits,'OutputMode','last') fullyConnectedLayer(numClasses) softmaxLayer classificationLayer]; maxEpochs = 300; miniBatchSize = 50; options = trainingOptions('adam', ... 'InitialLearnRate',2.198827960269379e-04,... 'MaxEpochs',maxEpochs, ... 'MiniBatchSize',miniBatchSize, ... 'SequenceLength','shortest', ... 'Shuffle','every-epoch',... 'Verbose', false, ... 'Plots','training-progress'); net = trainNetwork(TrainFeatures,YTrain,layers,options); predLabels = classify(net,TestFeatures); testAccuracy = sum(predLabels==adsTest.Labels)/numel(predLabels)*100
testAccuracy = 97.5000
Как показывает график, использование байесовской оптимизации приводит к LSTM с более высокой точностью.
В качестве другого подхода к задаче распознавания разговорных цифр используйте глубокую сверточную нейронную сеть (DCNN), основанную на спектрограммах мел-частоты, для классификации набора данных FSDD. Используйте ту же процедуру усечения/заполнения сигнала, что и в преобразовании рассеяния. Точно так же нормализуйте каждую запись путем деления каждой выборки сигнала на максимальное абсолютное значение. Для консистенции используйте те же обучающие и тестовые наборы, что и для преобразования рассеяния.
Установите параметры для спектрограмм мел-частоты. Используйте то же окно, или систему координат, длительность, что и в преобразовании рассеяния, 0,22 секунды. Установите скачок между окнами на 10 мс. Используйте 40 полосы частот.
segmentDuration = 8192*(1/8000); frameDuration = 0.22; hopDuration = 0.01; numBands = 40;
Сбросьте обучающие и тестовые хранилища данных.
reset(adsTrain); reset(adsTest);
Функция помощника helperspeechSpectrograms
, заданный в конце этого примера, использует melSpectrogram
получить спектрограмму мель-частоты после стандартизации длины записи и нормализации амплитуды. Используйте логарифм спектрограмм mel-частоты в качестве входов для DCNN. Чтобы избежать взятия логарифма нуля, добавьте небольшой эпсилон к каждому элементу.
epsil = 1e-6; XTrain = helperspeechSpectrograms(adsTrain,segmentDuration,frameDuration,hopDuration,numBands);
Computing speech spectrograms... Processed 500 files out of 2400 Processed 1000 files out of 2400 Processed 1500 files out of 2400 Processed 2000 files out of 2400 ...done
XTrain = log10(XTrain + epsil); XTest = helperspeechSpectrograms(adsTest,segmentDuration,frameDuration,hopDuration,numBands);
Computing speech spectrograms... Processed 500 files out of 600 ...done
XTest = log10(XTest + epsil); YTrain = adsTrain.Labels; YTest = adsTest.Labels;
Создайте небольшой DCNN как массив слоев. Используйте сверточные и пакетные слои нормализации и понижающее отображение карт признаков с помощью максимальных слоев объединения. Чтобы уменьшить возможность запоминания сетью специфических функций обучающих данных, добавьте небольшое количество отсева на вход к последнему полностью подключенному слою.
sz = size(XTrain); specSize = sz(1:2); imageSize = [specSize 1]; numClasses = numel(categories(YTrain)); dropoutProb = 0.2; numF = 12; layers = [ imageInputLayer(imageSize) convolution2dLayer(5,numF,'Padding','same') batchNormalizationLayer reluLayer maxPooling2dLayer(3,'Stride',2,'Padding','same') convolution2dLayer(3,2*numF,'Padding','same') batchNormalizationLayer reluLayer maxPooling2dLayer(3,'Stride',2,'Padding','same') convolution2dLayer(3,4*numF,'Padding','same') batchNormalizationLayer reluLayer maxPooling2dLayer(3,'Stride',2,'Padding','same') convolution2dLayer(3,4*numF,'Padding','same') batchNormalizationLayer reluLayer convolution2dLayer(3,4*numF,'Padding','same') batchNormalizationLayer reluLayer maxPooling2dLayer(2) dropoutLayer(dropoutProb) fullyConnectedLayer(numClasses) softmaxLayer classificationLayer('Classes',categories(YTrain)); ];
Установите гиперпараметры, которые будут использоваться при обучении сети. Используйте мини-пакет размером 50 и скоростью обучения 1e-4. Задайте оптимизацию Адама. Поскольку объем данных в этом примере относительно невелик, установите окружение выполнения на 'cpu'
для воспроизводимости. Можно также обучить сеть на доступном графическом процессоре, установив окружение выполнения на 'gpu'
или 'auto'
. Для получения дополнительной информации смотрите trainingOptions
.
miniBatchSize = 50; options = trainingOptions('adam', ... 'InitialLearnRate',1e-4, ... 'MaxEpochs',30, ... 'MiniBatchSize',miniBatchSize, ... 'Shuffle','every-epoch', ... 'Plots','training-progress', ... 'Verbose',false, ... 'ExecutionEnvironment','cpu');
Обучите сеть.
trainedNet = trainNetwork(XTrain,YTrain,layers,options);
Используйте обученную сеть, чтобы предсказать метки цифр для тестового набора.
[Ypredicted,probs] = classify(trainedNet,XTest,'ExecutionEnvironment','CPU'); cnnAccuracy = sum(Ypredicted==YTest)/numel(YTest)*100
cnnAccuracy = 98.1667
Суммируйте эффективность обученной сети на тестовом наборе с графиком неточностей. Отображение точности и отзыва для каждого класса с помощью сводных данных по столбцам и строкам. Таблица в нижней части графика неточностей показывает значения точности. Таблица справа от графика неточностей показывает значения отзыва.
figure('Units','normalized','Position',[0.2 0.2 0.5 0.5]); ccDCNN = confusionchart(YTest,Ypredicted); ccDCNN.Title = 'Confusion Chart for DCNN'; ccDCNN.ColumnSummary = 'column-normalized'; ccDCNN.RowSummary = 'row-normalized';
DCNN, использующий mel-частотные спектрограммы в качестве входов, классифицирует разговорные цифры в тестовом наборе с частотой точности также приблизительно 98%.
В этом примере показано, как использовать различные методы машинного и глубокого обучения для классификации разговорных цифр в FSDD. Пример иллюстрировал вейвлет в паре как с SVM, так и с LSTM. Байесовские методы использовались для оптимизации гиперпараметров LSTM. Наконец, пример показывает, как использовать CNN с мел-частотными спектрограммами.
Цель примера состоит в том, чтобы продемонстрировать, как использовать инструменты MathWorks ® для решения задачи принципиально различными, но взаимодополняющими способами. Все рабочие процессы используют audioDatastore
управлять потоком данных с диска и обеспечивать правильную рандомизацию.
Все подходы, используемые в этом примере, одинаково хорошо выполняются на тестовом наборе. Этот пример не предназначен как прямое сравнение между различными подходами. Например, можно также использовать байесовскую оптимизацию для выбора гиперпараметра в CNN. Дополнительная стратегия, которая полезна в глубоком обучении с небольшими наборами обучающих данных, такими как эта версия FSDD, заключается в использовании увеличения данных. Как манипуляции влияют на класс, не всегда известно, поэтому увеличение данных не всегда допустимо. Однако для речи установленные стратегии увеличения данных доступны через audioDataAugmenter
.
В случае вейвлет времени существует также ряд модификаций, которые можно попробовать. Для примера можно изменить инвариантный масштаб преобразования, изменить количество вейвлета фильтров на банк фильтров и попробовать другие классификаторы.
function Labels = helpergenLabels(ads) % This function is only for use in Wavelet Toolbox examples. It may be % changed or removed in a future release. tmp = cell(numel(ads.Files),1); expression = "[0-9]+_"; for nf = 1:numel(ads.Files) idx = regexp(ads.Files{nf},expression); tmp{nf} = ads.Files{nf}(idx); end Labels = categorical(tmp); end
function x = helperReadSPData(x) % This function is only for use Wavelet Toolbox examples. It may change or % be removed in a future release. N = numel(x); if N > 8192 x = x(1:8192); elseif N < 8192 pad = 8192-N; prepad = floor(pad/2); postpad = ceil(pad/2); x = [zeros(prepad,1) ; x ; zeros(postpad,1)]; end x = x./max(abs(x)); end
function x = helperBayesOptLSTM(X_train, Y_train, X_val, Y_val) % This function is only for use in the % "Spoken Digit Recognition with Wavelet Scattering and Deep Learning" % example. It may change or be removed in a future release. x = @valErrorFun; function [valError,cons, fileName] = valErrorFun(optVars) %% LSTM Architecture [inputSize,~] = size(X_train{1}); numClasses = numel(unique(Y_train)); layers = [ ... sequenceInputLayer(inputSize) bilstmLayer(optVars.NumHiddenUnits,'OutputMode','last') % Using number of hidden layers value from optimizing variable fullyConnectedLayer(numClasses) softmaxLayer classificationLayer]; % Plots not displayed during training options = trainingOptions('adam', ... 'InitialLearnRate',optVars.InitialLearnRate, ... % Using initial learning rate value from optimizing variable 'MaxEpochs',300, ... 'MiniBatchSize',30, ... 'SequenceLength','shortest', ... 'Shuffle','never', ... 'Verbose', false); %% Train the network net = trainNetwork(X_train, Y_train, layers, options); %% Training accuracy X_val_P = net.classify(X_val); accuracy_training = sum(X_val_P == Y_val)./numel(Y_val); valError = 1 - accuracy_training; %% save results of network and options in a MAT file in the results folder along with the error value fileName = fullfile('results', num2str(valError) + ".mat"); save(fileName,'net','valError','options') cons = []; end % end for inner function end % end for outer function
function X = helperspeechSpectrograms(ads,segmentDuration,frameDuration,hopDuration,numBands) % This function is only for use in the % "Spoken Digit Recognition with Wavelet Scattering and Deep Learning" % example. It may change or be removed in a future release. % % helperspeechSpectrograms(ads,segmentDuration,frameDuration,hopDuration,numBands) % computes speech spectrograms for the files in the datastore ads. % segmentDuration is the total duration of the speech clips (in seconds), % frameDuration the duration of each spectrogram frame, hopDuration the % time shift between each spectrogram frame, and numBands the number of % frequency bands. disp("Computing speech spectrograms..."); numHops = ceil((segmentDuration - frameDuration)/hopDuration); numFiles = length(ads.Files); X = zeros([numBands,numHops,1,numFiles],'single'); for i = 1:numFiles [x,info] = read(ads); x = normalizeAndResize(x); fs = info.SampleRate; frameLength = round(frameDuration*fs); hopLength = round(hopDuration*fs); spec = melSpectrogram(x,fs, ... 'Window',hamming(frameLength,'periodic'), ... 'OverlapLength',frameLength - hopLength, ... 'FFTLength',2048, ... 'NumBands',numBands, ... 'FrequencyRange',[50,4000]); % If the spectrogram is less wide than numHops, then put spectrogram in % the middle of X. w = size(spec,2); left = floor((numHops-w)/2)+1; ind = left:left+w-1; X(:,ind,1,i) = spec; if mod(i,500) == 0 disp("Processed " + i + " files out of " + numFiles) end end disp("...done"); end %-------------------------------------------------------------------------- function x = normalizeAndResize(x) % This function is only for use in the % "Spoken Digit Recognition with Wavelet Scattering and Deep Learning" % example. It may change or be removed in a future release. N = numel(x); if N > 8192 x = x(1:8192); elseif N < 8192 pad = 8192-N; prepad = floor(pad/2); postpad = ceil(pad/2); x = [zeros(prepad,1) ; x ; zeros(postpad,1)]; end x = x./max(abs(x)); end
Копирайт 2018, The MathWorks, Inc.
trainingOptions
| trainNetwork