Этот пример показывает, как обучить простую модель глубокого обучения, которая обнаруживает присутствие речевых команд в аудио. Пример использует Речевой Набор данных Команд [1], чтобы обучить сверточную нейронную сеть, чтобы распознать данный набор команд.
Чтобы запустить пример, необходимо сначала загрузить набор данных. Если вы не хотите загружать набор данных или обучать сеть, то можно загрузить предварительно обученную сеть путем открытия этого примера в MATLAB® и ввода load('commandNet.mat')
в командной строке. После загрузки сети перейдите непосредственно к последнему разделу этого примера, Обнаружьте Команды Используя Передачу потокового аудио от Микрофона.
Загрузите набор данных с http://download.tensorflow.org/data/speech_commands_v0.01.tar.gz и извлеките загруженный файл. Установите datafolder
на местоположение данных. Используйте audioDatastore
, чтобы создать datastore, который содержит имена файлов и соответствующие метки. Используйте имена папок в качестве источника метки. Задайте метод чтения, чтобы считать целый звуковой файл. Создайте копию datastore для дальнейшего использования.
datafolder = fullfile(tempdir,'speech_commands_v0.01'); ads = audioDatastore(datafolder, ... 'IncludeSubfolders',true, ... 'FileExtensions','.wav', ... 'LabelSource','foldernames') ads0 = copy(ads);
ads = audioDatastore with properties: Files: { ' ...\Temp\speech_commands_v0.01\_background_noise_\doing_the_dishes.wav'; ' ...\Local\Temp\speech_commands_v0.01\_background_noise_\dude_miaowing.wav'; ' ...\Local\Temp\speech_commands_v0.01\_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 8250 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
использует melSpectrogram
для логарифмических-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 24982 Processed 2000 files out of 24982 Processed 3000 files out of 24982 Processed 4000 files out of 24982 Processed 5000 files out of 24982 Processed 6000 files out of 24982 Processed 7000 files out of 24982 Processed 8000 files out of 24982 Processed 9000 files out of 24982 Processed 10000 files out of 24982 Processed 11000 files out of 24982 Processed 12000 files out of 24982 Processed 13000 files out of 24982 Processed 14000 files out of 24982 Processed 15000 files out of 24982 Processed 16000 files out of 24982 Processed 17000 files out of 24982 Processed 18000 files out of 24982 Processed 19000 files out of 24982 Processed 20000 files out of 24982 Processed 21000 files out of 24982 Processed 22000 files out of 24982 Processed 23000 files out of 24982 Processed 24000 files out of 24982 ...done Computing speech spectrograms... Processed 1000 files out of 3477 Processed 2000 files out of 3477 Processed 3000 files out of 3477 ...done Computing speech spectrograms... Processed 1000 files out of 3473 Processed 2000 files out of 3473 Processed 3000 files out of 3473 ...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
роликов фона, взятых из звуковых файлов в datastore adsBkg
, используйте функцию поддержки 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: 1.7103% Validation error: 4.5912%
Постройте матрицу беспорядка. Отобразите точность и отзыв для каждого класса при помощи сводных данных строки и столбца. Сортировка классов матрицы беспорядка. Самый большой беспорядок между неизвестными словами и командами, и прочь, вниз и не, и пойдите и нет.
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: 295.9141 kB Single-image prediction time on CPU: 3.1274 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]); 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 = melSpectrogram(waveBuffer,fs, ... 'WindowLength',frameLength, ... 'OverlapLength',frameLength - hopLength, ... 'FFTLength',512, ... 'NumBands',numBands, ... 'FrequencyRange',[50,7000]); 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