В этом примере показано, как классифицировать произносимые цифры с помощью машинных и глубоких методов обучения. В этом примере классификация выполняется с использованием вейвлет-временного рассеяния с помощью вспомогательной векторной машины (SVM) и сети с длительной кратковременной памятью (LSTM). Также применяется байесовская оптимизация для определения подходящих гиперпараметров для повышения точности сети LSTM. Кроме того, пример иллюстрирует подход с использованием глубокой сверточной нейронной сети (CNN) и мел-частотных спектрограмм.
Клонируйте или загрузите набор данных Free Spoken Digit Dataset (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 (Vavelet 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-кратную перекрестную проверку, чтобы предсказать точность обобщения модели на основе обучающих данных. Разбейте учебный набор на пять групп.
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 с более высокой точностью.
В качестве другого подхода к задаче распознавания разговорных цифр для классификации набора данных FSDD используйте глубокую сверточную нейронную сеть (DCNN) на основе мел-частотных спектрограмм. Используйте ту же процедуру усечения/заполнения сигнала, что и при преобразовании рассеяния. Аналогично нормализуйте каждую запись путем деления каждой выборки сигнала на максимальное абсолютное значение. Для обеспечения согласованности используйте те же обучающие и тестовые наборы, что и для преобразования рассеяния.
Задайте параметры для спектрограмм мел-частоты. Используйте ту же длительность окна или кадра, что и в преобразовании рассеяния, 0,22 секунды. Установите для перехода между окнами значение 10 ms Use 40 frequency bands.
segmentDuration = 8192*(1/8000); frameDuration = 0.22; hopDuration = 0.01; numBands = 40;
Сбросьте хранилища данных обучения и тестирования.
reset(adsTrain); reset(adsTest);
Вспомогательная функция helperspeechSpectrograms, определенный в конце этого примера, использует melSpectrogram получение спектрограммы мел-частоты после стандартизации длины записи и нормализации амплитуды. В качестве входов в DCNN используйте логарифм спектрограмм mel-frequency. Чтобы не брать нулевой логарифм, добавьте небольшой эпсилон к каждому элементу.
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, использующий в качестве входных данных спектрограммы мел-частоты, также классифицирует произносимые цифры в тестовом наборе с точностью приблизительно 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
Copyright 2018, The MathWorks, Inc.
trainingOptions | trainNetwork