Распознание речевых команд с использованием глубокого обучения

В этом примере показано, как обучить простую модель глубокого обучения, которая обнаруживает присутствие речевых команд в аудио. Пример использует Речевой Набор данных Команд [1], чтобы обучить сверточную нейронную сеть распознавать данный набор команд.

Чтобы запустить пример, необходимо сначала загрузить набор данных. Если вы не хотите загружать набор данных или обучать сеть, то можно загрузить предварительно обученную сеть путем открытия этого примера в MATLAB® и ввода load('commandNet.mat') в командной строке. После загрузки сети перейдите непосредственно к последнему разделу этого примера, Обнаружьте Команды Используя Передачу потокового аудио от Микрофона.

Загрузите речевой набор данных команд

Загрузите набор данных с https://storage.googleapis.com/download.tensorflow.org/data/speech_commands_v0.01.tar.gz и извлеките загруженный файл. Установите datafolder к местоположению данных. Используйте audioDatastore создать datastore, который содержит имена файлов и соответствующие метки. Используйте имена папок в качестве источника метки. Задайте метод чтения, чтобы считать целый звуковой файл. Создайте копию datastore для дальнейшего использования.

datafolder = PathToDatabase;
ads = audioDatastore(datafolder, ...
    'IncludeSubfolders',true, ...
    'FileExtensions','.wav', ...
    'LabelSource','foldernames')
ads0 = copy(ads);
ads = 

  audioDatastore with properties:

                       Files: {
                              ' ...\datasets\google_speech\_background_noise_\doing_the_dishes.wav';
                              ' ...\datasets\google_speech\_background_noise_\dude_miaowing.wav';
                              ' ...\datasets\google_speech\_background_noise_\exercise_bike.wav'
                               ... and 64724 more
                              }
                      Labels: [_background_noise_; _background_noise_; _background_noise_ ... and 64724 more categorical]
    AlternateFileSystemRoots: {}
              OutputDataType: 'double'

Выберите Words to Recognize

Задайте слова, которые вы хотите, чтобы ваша модель распознала командами. Пометьте все слова, которые не являются командами как unknown. Маркировка слов, которые не являются командами как unknown создает группу слов, которая аппроксимирует распределение всех слов кроме команд. Сеть использует эту группу, чтобы изучить различие между командами и всеми другими словами.

Чтобы уменьшать неустойчивость класса между известными и неизвестными словами и ускорить обработку, только включайте дробный includeFraction из неизвестных слов в наборе обучающих данных. Еще не включайте более длинные файлы с фоновым шумом в наборе обучающих данных. Фоновый шум будет добавлен на отдельном шаге позже.

Используйте subset(ads,indices) создать datastore, который содержит только файлы и помечает индексированным indices. Уменьшайте datastore ads так, чтобы это содержало только команды и подмножество неизвестных слов. Считайте количество примеров, принадлежащих каждому классу.

commands = categorical(["yes","no","up","down","left","right","on","off","stop","go"]);

isCommand = ismember(ads.Labels,commands);
isUnknown = ~ismember(ads.Labels,[commands,"_background_noise_"]);

includeFraction = 0.2;
mask = rand(numel(ads.Labels),1) < includeFraction;
isUnknown = isUnknown & mask;
ads.Labels(isUnknown) = categorical("unknown");

ads = subset(ads,isCommand|isUnknown);
countEachLabel(ads)
ans =

  11×2 table

     Label     Count
    _______    _____

    down       2359 
    go         2372 
    left       2353 
    no         2375 
    off        2357 
    on         2367 
    right      2367 
    stop       2380 
    unknown    8294 
    up         2375 
    yes        2377 

Разделение данных в обучение, валидацию и наборы тестов

Папка набора данных содержит текстовые файлы, которые перечисляют звуковые файлы, которые будут использоваться в качестве валидации и наборов тестов. Они предопределили валидацию, и наборы тестов не содержат произнесение того же слова тем же человеком, таким образом, лучше использовать эти предопределенные наборы, чем выбрать случайное подмножество целого набора данных. Используйте функцию поддержки splitData разделять datastore в обучение, валидацию и наборы тестов на основе списка валидации и тестовых файлов, расположенных в папке набора данных.

Поскольку этот пример обучает одну сеть, он только использует набор валидации а не набор тестов, чтобы оценить обученную модель. Если вы обучаете много сетей и выбираете сеть с самой высокой точностью валидации как ваша итоговая сеть, то можно использовать набор тестов, чтобы оценить итоговую сеть.

[adsTrain,adsValidation,adsTest] = splitData(ads,datafolder);

Вычислите речевые спектрограммы

Чтобы подготовить данные к эффективному обучению сверточной нейронной сети, преобразуйте речевые формы волны, чтобы регистрировать-mel спектрограммы.

Задайте параметры вычисления спектрограммы. segmentDuration длительность каждого речевого клипа (в секундах). frameDuration длительность каждой системы координат для вычисления спектрограммы. hopDuration временной шаг между каждым столбцом спектрограммы. numBands количество журнала-mel, фильтрует и равняется высоте каждой спектрограммы.

segmentDuration = 1;
frameDuration = 0.025;
hopDuration = 0.010;
numBands = 40;

Вычислите спектрограммы для обучения, валидации и наборов тестов при помощи функции поддержки speechSpectrograms. speechSpectrograms функционируйте использует designAuditoryFilterBank для логарифмических-mel вычислений спектрограммы. Чтобы получить данные с более сглаженным распределением, возьмите логарифм спектрограмм с помощью маленького смещения epsil.

epsil = 1e-6;

XTrain = speechSpectrograms(adsTrain,segmentDuration,frameDuration,hopDuration,numBands);
XTrain = log10(XTrain + epsil);

XValidation = speechSpectrograms(adsValidation,segmentDuration,frameDuration,hopDuration,numBands);
XValidation = log10(XValidation + epsil);

XTest = speechSpectrograms(adsTest,segmentDuration,frameDuration,hopDuration,numBands);
XTest = log10(XTest + epsil);

YTrain = adsTrain.Labels;
YValidation = adsValidation.Labels;
YTest = adsTest.Labels;
Computing speech spectrograms...
Processed 1000 files out of 25128
Processed 2000 files out of 25128
Processed 3000 files out of 25128
Processed 4000 files out of 25128
Processed 5000 files out of 25128
Processed 6000 files out of 25128
Processed 7000 files out of 25128
Processed 8000 files out of 25128
Processed 9000 files out of 25128
Processed 10000 files out of 25128
Processed 11000 files out of 25128
Processed 12000 files out of 25128
Processed 13000 files out of 25128
Processed 14000 files out of 25128
Processed 15000 files out of 25128
Processed 16000 files out of 25128
Processed 17000 files out of 25128
Processed 18000 files out of 25128
Processed 19000 files out of 25128
Processed 20000 files out of 25128
Processed 21000 files out of 25128
Processed 22000 files out of 25128
Processed 23000 files out of 25128
Processed 24000 files out of 25128
Processed 25000 files out of 25128
...done
Computing speech spectrograms...
Processed 1000 files out of 3391
Processed 2000 files out of 3391
Processed 3000 files out of 3391
...done
Computing speech spectrograms...
Processed 1000 files out of 3457
Processed 2000 files out of 3457
Processed 3000 files out of 3457
...done

Визуализируйте данные

Постройте формы волны и спектрограммы нескольких учебных примеров. Проигрывайте соответствующие аудиоклипы.

specMin = min(XTrain(:));
specMax = max(XTrain(:));
idx = randperm(size(XTrain,4),3);
figure('Units','normalized','Position',[0.2 0.2 0.6 0.6]);
for i = 1:3
    [x,fs] = audioread(adsTrain.Files{idx(i)});
    subplot(2,3,i)
    plot(x)
    axis tight
    title(string(adsTrain.Labels(idx(i))))

    subplot(2,3,i+3)
    spect = XTrain(:,:,1,idx(i));
    pcolor(spect)
    caxis([specMin+2 specMax])
    shading flat

    sound(x,fs)
    pause(2)
end

Обучение нейронных сетей является самым легким, когда входные параметры к сети имеют довольно сглаженное распределение и нормированы. Чтобы проверять, что распределение данных является гладким, постройте гистограмму пиксельных значений обучающих данных.

figure
histogram(XTrain,'EdgeColor','none','Normalization','pdf')
axis tight
ax = gca;
ax.YScale = 'log';
xlabel("Input Pixel Value")
ylabel("Probability Density")

Добавьте данные о фоновом шуме

Сеть должна смочь не только распознать различные произносимые слова, но также и обнаружить, если вход содержит тишину или фоновый шум.

Используйте звуковые файлы в _background_noise_ папка, чтобы создать выборки вторых клипов фонового шума. Создайте равное количество роликов фона из каждого файла фонового шума. Можно также создать собственные записи фонового шума и добавить их в _background_noise_ папка. Вычислить numBkgClips спектрограммы роликов фона, взятых из звуковых файлов в adsBkg datastore, используйте функцию поддержки backgroundSpectrograms. Прежде, чем вычислить спектрограммы, функция повторно масштабирует каждый аудиоклип с фактором, произведенным от логарифмического равномерного распределения в области значений, данной volumeRange.

Создайте 4 000 роликов фона и повторно масштабируйте каждого номером между 1e-4 и 1. XBkg содержит спектрограммы фонового шума с объемами в пределах от практически тихого к громкому.

adsBkg = subset(ads0,ads0.Labels=="_background_noise_");
numBkgClips = 4000;
volumeRange = [1e-4,1];

XBkg = backgroundSpectrograms(adsBkg,numBkgClips,volumeRange,segmentDuration,frameDuration,hopDuration,numBands);
XBkg = log10(XBkg + epsil);
Computing background spectrograms...
Processed 1000 background clips out of 4000
Processed 2000 background clips out of 4000
Processed 3000 background clips out of 4000
Processed 4000 background clips out of 4000
...done

Разделите спектрограммы фонового шума между обучением, валидацией и наборами тестов. Поскольку _background_noise_ папка содержит только приблизительно пять с половиной минут фонового шума, фоновые выборки в различных наборах данных высоко коррелируются. Чтобы увеличить изменение в фоновом режиме шум, можно создать собственные фоновые файлы и добавить их в папку. Чтобы увеличить робастность сети к шуму, можно также попытаться смешать фоновый шум в речевые файлы.

numTrainBkg = floor(0.8*numBkgClips);
numValidationBkg = floor(0.1*numBkgClips);
numTestBkg = floor(0.1*numBkgClips);

XTrain(:,:,:,end+1:end+numTrainBkg) = XBkg(:,:,:,1:numTrainBkg);
XBkg(:,:,:,1:numTrainBkg) = [];
YTrain(end+1:end+numTrainBkg) = "background";

XValidation(:,:,:,end+1:end+numValidationBkg) = XBkg(:,:,:,1:numValidationBkg);
XBkg(:,:,:,1:numValidationBkg) = [];
YValidation(end+1:end+numValidationBkg) = "background";

XTest(:,:,:,end+1:end+numTestBkg) = XBkg(:,:,:,1: numTestBkg);
clear XBkg;
YTest(end+1:end+numTestBkg) = "background";

YTrain = removecats(YTrain);
YValidation = removecats(YValidation);
YTest = removecats(YTest);

Постройте распределение различных меток класса в наборах обучения и валидации. Набор тестов имеет очень похожее распределение к набору валидации.

figure('Units','normalized','Position',[0.2 0.2 0.5 0.5]);
subplot(2,1,1)
histogram(YTrain)
title("Training Label Distribution")
subplot(2,1,2)
histogram(YValidation)
title("Validation Label Distribution")

Добавьте увеличение данных

Создайте увеличенный datastore изображений для автоматического увеличения и изменения размеров спектрограмм. Переведите спектрограмму случайным образом до 10 систем координат (100 мс) вперед или назад вовремя и масштабируйте спектрограммы вдоль оси времени или вниз на 20 процентов. Увеличение данных может увеличить эффективный размер обучающих данных, и справка препятствуют тому, чтобы сеть сверхсоответствовала. Увеличенный datastore изображений создает увеличенные изображения в режиме реального времени во время обучения и вводит их к сети. Никакие увеличенные спектрограммы не сохранены в памяти.

sz = size(XTrain);
specSize = sz(1:2);
imageSize = [specSize 1];
augmenter = imageDataAugmenter( ...
    'RandXTranslation',[-10 10], ...
    'RandXScale',[0.8 1.2], ...
    'FillValue',log10(epsil));
augimdsTrain = augmentedImageDatastore(imageSize,XTrain,YTrain, ...
    'DataAugmentation',augmenter);

Задайте архитектуру нейронной сети

Создайте простую сетевую архитектуру как массив слоев. Используйте сверточные и пакетные слои нормализации и проредите карты функции "пространственно" (то есть, вовремя и частота) использование макс. слоев объединения. Добавьте финал макс. объединение слоя, который объединяет входную карту функции глобально в зависимости от времени. Это осуществляет (аппроксимируют) инвариантность перевода времени во входных спектрограммах, позволяя сети выполнить ту же классификацию, независимую от точного положения речи вовремя. Глобальное объединение также значительно сокращает количество параметров в итоговом полносвязном слое. Чтобы уменьшать возможность сети, запоминая определенные функции обучающих данных, добавьте небольшое количество уволенного к входу к последнему полносвязному слою.

Сеть мала, когда она имеет только пять сверточных слоев с немногими фильтрами. numF управляет количеством, просачивается сверточные слои. Чтобы увеличить точность сети, попытайтесь увеличить сетевую глубину путем добавления идентичных блоков сверточной, пакетной нормализации и слоев ReLU. Можно также попытаться увеличить число сверточных фильтров путем увеличения numF.

Используйте взвешенную перекрестную энтропийную потерю классификации. weightedClassificationLayer(classWeights) создает пользовательский слой классификации, который вычисляет перекрестную энтропийную потерю с наблюдениями, взвешенными classWeights. Задайте веса класса в том же порядке, как классы появляются в categories(YTrain). Чтобы дать каждому классу равную общую массу в потере, используйте веса класса, которые обратно пропорциональны количеству учебных примеров в каждом классе. При использовании оптимизатора Адама, чтобы обучить сеть, учебный алгоритм независим от полной нормализации весов класса.

classWeights = 1./countcats(YTrain);
classWeights = classWeights'/mean(classWeights);
numClasses = numel(categories(YTrain));

timePoolSize = ceil(imageSize(2)/8);
dropoutProb = 0.2;
numF = 12;
layers = [
    imageInputLayer(imageSize)

    convolution2dLayer(3,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([1 timePoolSize])

    dropoutLayer(dropoutProb)
    fullyConnectedLayer(numClasses)
    softmaxLayer
    weightedClassificationLayer(classWeights)];

Обучение сети

Задайте опции обучения. Используйте оптимизатор Адама с мини-пакетным размером 128. Обучайтесь в течение 25 эпох и уменьшайте темп обучения фактором 10 после 20 эпох.

miniBatchSize = 128;
validationFrequency = floor(numel(YTrain)/miniBatchSize);
options = trainingOptions('adam', ...
    'InitialLearnRate',3e-4, ...
    'MaxEpochs',25, ...
    'MiniBatchSize',miniBatchSize, ...
    'Shuffle','every-epoch', ...
    'Plots','training-progress', ...
    'Verbose',false, ...
    'ValidationData',{XValidation,YValidation}, ...
    'ValidationFrequency',validationFrequency, ...
    'LearnRateSchedule','piecewise', ...
    'LearnRateDropFactor',0.1, ...
    'LearnRateDropPeriod',20);

Обучите сеть. Если у вас нет графического процессора, то обучение сети может занять время. Чтобы загрузить предварительно обученную сеть вместо того, чтобы обучить сеть с нуля, установите doTraining к false.

doTraining = true;
if doTraining
    trainedNet = trainNetwork(augimdsTrain,layers,options);
else
    load('commandNet.mat','trainedNet');
end

Оцените обученную сеть

Вычислите итоговую точность сети на наборе обучающих данных (без увеличения данных) и набор валидации. Сеть очень точна на этом наборе данных. Однако обучение, валидация и тестовые данные, у всех есть подобные распределения, которые не обязательно отражают реальные среды. Это ограничение особенно применяется к unknown категория, которая содержит произнесение только небольшого количества слов.

YValPred = classify(trainedNet,XValidation);
validationError = mean(YValPred ~= YValidation);
YTrainPred = classify(trainedNet,XTrain);
trainError = mean(YTrainPred ~= YTrain);
disp("Training error: " + trainError*100 + "%")
disp("Validation error: " + validationError*100 + "%")
Training error: 4.0419%
Validation error: 6.4099%

Постройте матрицу беспорядка. Отобразите точность и отзыв для каждого класса при помощи сводных данных строки и столбца. Сортировка классов матрицы беспорядка. Самый большой беспорядок между неизвестными словами и командами, и прочь, вниз и не, и пойдите и нет.

figure('Units','normalized','Position',[0.2 0.2 0.5 0.5]);
cm = confusionchart(YValidation,YValPred);
cm.Title = 'Confusion Matrix for Validation Data';
cm.ColumnSummary = 'column-normalized';
cm.RowSummary = 'row-normalized';
sortClasses(cm, [commands,"unknown","background"])

При работе над приложениями с ограниченными аппаратными ресурсами, такими как мобильные приложения, рассмотрите ограничения на доступную память и вычислительные ресурсы. Вычислите общий размер сети в килобайтах и протестируйте ее скорость прогноза при использовании центрального процессора. Время прогноза является временем для классификации одного входного изображения. Если вы вводите повторные изображения к сети, они могут быть классифицированы одновременно, ведя к более коротким временам прогноза на изображение. При классификации передачи потокового аудио, однако, время прогноза одно изображения является самым релевантным.

info = whos('trainedNet');
disp("Network size: " + info.bytes/1024 + " kB")

for i=1:100
    x = randn(imageSize);
    tic
    [YPredicted,probs] = classify(trainedNet,x,"ExecutionEnvironment",'cpu');
    time(i) = toc;
end
disp("Single-image prediction time on CPU: " + mean(time(11:end))*1000 + " ms")
Network size: 285.2109 kB
Single-image prediction time on CPU: 1.9703 ms

Обнаружьте команды Используя передачу потокового аудио от микрофона

Протестируйте свою недавно обученную сеть обнаружения команды на передаче потокового аудио от вашего микрофона. Если вы не обучили сеть, то введите load('commandNet.mat') в командной строке, чтобы загрузить предварительно обученную сеть и параметры, требуемые классифицировать живой, передача потокового аудио. Попытайтесь говорить одну из команд, например, да, нет, или остановку. Затем попытайтесь говорить одно из неизвестных слов, таких как Марвин, Шейла, кровать, дом, кошка, птица или любой номер от нуля до девять.

Задайте уровень выборки аудио и уровень классификации в Гц и создайте читателя аудио устройства, который может считать аудио из вашего микрофона.

fs = 16e3;
classificationRate = 20;
audioIn = audioDeviceReader('SampleRate',fs, ...
    'SamplesPerFrame',floor(fs/classificationRate));

Задайте параметры для расчетов спектрограммы потоковой передачи и инициализируйте буфер для аудио. Извлеките метки классификации сети. Инициализируйте буферы половины секунды для меток и вероятностей классификации передачи потокового аудио. Используйте эти буферы, чтобы сравнить результаты классификации за более длительный промежуток времени и той сборкой 'соглашение', когда команда будет обнаружена.

frameLength = floor(frameDuration*fs);
hopLength = floor(hopDuration*fs);
waveBuffer = zeros([fs,1]);

labels = trainedNet.Layers(end).Classes;
YBuffer(1:classificationRate/2) = categorical("background");
probBuffer = zeros([numel(labels),classificationRate/2]);

Создайте фигуру и обнаружьте команды, пока созданная фигура существует. Чтобы остановить живое обнаружение, просто закройте фигуру.

h = figure('Units','normalized','Position',[0.2 0.1 0.6 0.8]);

filterBank = designAuditoryFilterBank(fs,'FrequencyScale','bark',...
    'FFTLength',512,...
    'NumBands',numBands,...
    'FrequencyRange',[50,7000]);

while ishandle(h)

    % Extract audio samples from the audio device and add the samples to
    % the buffer.
    x = audioIn();
    waveBuffer(1:end-numel(x)) = waveBuffer(numel(x)+1:end);
    waveBuffer(end-numel(x)+1:end) = x;

    % Compute the spectrogram of the latest audio samples.
    [~,~,~,spec] =  spectrogram(waveBuffer,hann(frameLength,'periodic'),frameLength - hopLength,512,'onesided');
    spec = filterBank * spec;
    spec = log10(spec + epsil);

    % Classify the current spectrogram, save the label to the label buffer,
    % and save the predicted probabilities to the probability buffer.
    [YPredicted,probs] = classify(trainedNet,spec,'ExecutionEnvironment','cpu');
    YBuffer(1:end-1)= YBuffer(2:end);
    YBuffer(end) = YPredicted;
    probBuffer(:,1:end-1) = probBuffer(:,2:end);
    probBuffer(:,end) = probs';

    % Plot the current waveform and spectrogram.
    subplot(2,1,1);
    plot(waveBuffer)
    axis tight
    ylim([-0.2,0.2])

    subplot(2,1,2)
    pcolor(spec)
    caxis([specMin+2 specMax])
    shading flat

    % Now do the actual command detection by performing a very simple
    % thresholding operation. Declare a detection and display it in the
    % figure title if all of the following hold:
    % 1) The most common label is not |background|.
    % 2) At least |countThreshold| of the latest frame labels agree.
    % 3) The maximum predicted probability of the predicted label is at
    % least |probThreshold|. Otherwise, do not declare a detection.
    [YMode,count] = mode(YBuffer);
    countThreshold = ceil(classificationRate*0.2);
    maxProb = max(probBuffer(labels == YMode,:));
    probThreshold = 0.7;
    subplot(2,1,1);
    if YMode == "background" || count<countThreshold || maxProb < probThreshold
        title(" ")
    else
        title(string(YMode),'FontSize',20)
    end

    drawnow

end