В этом примере показано, как обучить простую модель глубокого обучения, которая обнаруживает присутствие речевых команд в аудио. Пример использует Речевой Набор данных Команд [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'
Задайте слова, которые вы хотите, чтобы ваша модель распознала командами. Пометьте все слова, которые не являются командами как 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