Этот пример показывает, что типичный рабочий процесс для выбора признаков применился к задаче разговорного распознавания цифры.
В последовательном выборе признаков вы обучаете сеть на данном наборе функций и затем инкрементно добавляете или удаляете функции, пока самая высокая точность не достигнута [1]. В этом примере вы применяете последовательный прямой выбор к задаче разговорного распознавания цифры с помощью Свободного Разговорного Набора данных Цифры [2].
Чтобы мотивировать пример, начните путем загрузки предварительно обученной сети, audioFeatureExtractor
Объект (Audio Toolbox) раньше обучал сеть и коэффициенты нормализации для функций.
load('network_Audio_SequentialFeatureSelection.mat','bestNet','afe','normalizers');
Создайте audioDeviceReader
(Audio Toolbox), чтобы считать аудио из микрофона. Создайте три dsp.AsyncBuffer
(DSP System Toolbox) объекты: один, чтобы буферизовать аудио читал из вашего микрофона, один, чтобы буферизовать краткосрочную энергию входного аудио для речевого обнаружения, и один, чтобы буферизовать предсказания.
fs = afe.SampleRate; deviceReader = audioDeviceReader('SampleRate',fs,'SamplesPerFrame',256); audioBuffer = dsp.AsyncBuffer(fs*3); steBuffer = dsp.AsyncBuffer(1000); predictionBuffer = dsp.AsyncBuffer(5);
Создайте график отобразить передачу потокового аудио, вероятность сетевые выходные параметры во время вывода и предсказание.
fig = figure; streamAxes = subplot(3,1,1); streamPlot = plot(zeros(fs,1)); ylabel('Amplitude') xlabel('Time (s)') title('Audio Stream') streamAxes.XTick = [0,fs]; streamAxes.XTickLabel = [0,1]; streamAxes.YLim = [-1,1]; analyzedAxes = subplot(3,1,2); analyzedPlot = plot(zeros(fs/2,1)); title('Analyzed Segment') ylabel('Amplitude') xlabel('Time (s)') set(gca,'XTickLabel',[]) analyzedAxes.XTick = [0,fs/2]; analyzedAxes.XTickLabel = [0,0.5]; analyzedAxes.YLim = [-1,1]; probabilityAxes = subplot(3,1,3); probabilityPlot = bar(0:9,0.1*ones(1,10)); axis([-1,10,0,1]) ylabel('Probability') xlabel('Class')
Выполните распознавание цифры потоковой передачи (цифры 0 до 9) в течение 20 секунд. В то время как цикл запускается, говорите одну из цифр и протестируйте его точность.
Во-первых, задайте краткосрочный энергетический порог, под которым можно принять, что сигнал не содержит речи.
steThreshold = 0.015; idxVec = 1:fs; tic while toc < 20 % Read in a frame of audio from your device. audioIn = deviceReader(); % Write the audio into a the buffer. write(audioBuffer,audioIn); % While 200 ms of data is unused, continue this loop. while audioBuffer.NumUnreadSamples > 0.2*fs % Read 1 second from the audio buffer. Of that 1 second, 800 ms % is rereading old data and 200 ms is new data. audioToAnalyze = read(audioBuffer,fs,0.8*fs); % Update the figure to plot the current audio data. streamPlot.YData = audioToAnalyze; ste = mean(abs(audioToAnalyze)); write(steBuffer,ste); if steBuffer.NumUnreadSamples > 5 abc = sort(peek(steBuffer)); steThreshold = abc(round(0.4*numel(abc))); end if ste > steThreshold % Use the detectSpeeech function to determine if a region of speech % is present. idx = detectSpeech(audioToAnalyze,fs); % If a region of speech is present, perform the following. if ~isempty(idx) % Zero out all parts of the signal except the speech % region, and trim to 0.5 seconds. audioToAnalyze = HelperTrimOrPad(audioToAnalyze(idx(1,1):idx(1,2)),fs/2); % Normalize the audio. audioToAnalyze = audioToAnalyze/max(abs(audioToAnalyze)); % Update the analyzed segment plot analyzedPlot.YData = audioToAnalyze; % Extract the features and transpose them so that time is % across columns. features = (extract(afe,audioToAnalyze))'; % Normalize the features. features = (features - normalizers.Mean) ./ normalizers.StandardDeviation; % Call classify to determine the probabilities and the % winning label. features(isnan(features)) = 0; [label,probs] = classify(bestNet,features); % Update the plot with the probabilities and the winning % label. probabilityPlot.YData = probs; write(predictionBuffer,probs); if predictionBuffer.NumUnreadSamples == predictionBuffer.Capacity lastTen = peek(predictionBuffer); [~,decision] = max(mean(lastTen.*hann(size(lastTen,1)),1)); probabilityAxes.Title.String = num2str(decision-1); end end else % If the signal energy is below the threshold, assume no speech % detected. probabilityAxes.Title.String = ''; probabilityPlot.YData = 0.1*ones(10,1); analyzedPlot.YData = zeros(fs/2,1); reset(predictionBuffer) end drawnow limitrate end end
Остаток от примера иллюстрирует, как сеть, используемая в обнаружении потоковой передачи, была обучена и как признаки, поданные в сеть, были выбраны.
Загрузите Свободный разговорный набор данных цифры (FSDD) [2]. FSDD состоит из коротких звуковых файлов с разговорными цифрами (0-9).
url = "https://zenodo.org/record/1342401/files/Jakobovski/free-spoken-digit-dataset-v1.0.8.zip"; downloadFolder = tempdir; datasetFolder = fullfile(downloadFolder,'FSDD'); if ~exist(datasetFolder,'dir') fprintf('Downloading Free Spoken Digit Dataset ...\n') unzip(url,datasetFolder) end
Создайте audioDatastore
(Audio Toolbox), чтобы указать на записи. Получите частоту дискретизации набора данных.
ads = audioDatastore(datasetFolder,'IncludeSubfolders',true);
[~,adsInfo] = read(ads);
fs = adsInfo.SampleRate;
Первым элементом имен файлов является цифра, на которой говорят в файле. Получите первый элемент имен файлов, преобразуйте их в категориальный, и затем установите Labels
свойство audioDatastore
.
[~,filenames] = cellfun(@(x)fileparts(x),ads.Files,'UniformOutput',false);
ads.Labels = categorical(string(cellfun(@(x)x(1),filenames)));
Чтобы разделить datastore в набор разработки и набор валидации, используйте splitEachLabel
(Audio Toolbox). Выделите 80% данных для разработки и остающиеся 20% для валидации.
[adsTrain,adsValidation] = splitEachLabel(ads,0.8);
Создайте audioFeatureExtractor
Объект (Audio Toolbox) извлечь функции аудио более чем 30 MS Windows с частотой обновления 10 мс. Установите все функции, которые требуется протестировать в этом примере к true
.
win = hamming(round(0.03*fs),"periodic"); overlapLength = round(0.02*fs); afe = audioFeatureExtractor( ... 'Window', win, ... 'OverlapLength',overlapLength, ... 'SampleRate', fs, ... ... 'linearSpectrum', false, ... 'melSpectrum', false, ... 'barkSpectrum', false, ... 'erbSpectrum', false, ... ... 'mfcc', true, ... 'mfccDelta', true, ... 'mfccDeltaDelta', true, ... 'gtcc', true, ... 'gtccDelta', true, ... 'gtccDeltaDelta', true, ... ... 'spectralCentroid', true, ... 'spectralCrest', true, ... 'spectralDecrease', true, ... 'spectralEntropy', true, ... 'spectralFlatness', true, ... 'spectralFlux', true, ... 'spectralKurtosis', true, ... 'spectralRolloffPoint',true, ... 'spectralSkewness', true, ... 'spectralSlope', true, ... 'spectralSpread', true, ... ... 'pitch', false, ... 'harmonicRatio', false);
Задайте Список слоев глубокого обучения и trainingOptions
используемый в этом примере. Первый слой, sequenceInputLayer
, только заполнитель. В зависимости от которых функций вы тестируете во время последовательного выбора признаков, первый слой заменяется sequenceInputLayer
из соответствующего размера.
numUnits = 100; слои = [ ... sequenceInputLayer (1) bilstmLayer (numUnits,"OutputMode","last") fullyConnectedLayer (numel (категории (adsTrain.Labels))) softmaxLayer classificationLayer]; опции = trainingOptions ("adam", ... "LearnRateSchedule","piecewise", ... "Shuffle","every-epoch", ... "Verbose"ложь, ... "MaxEpochs",20);
В канонической форме последовательного выбора признаков вы обучаете сеть на данном наборе функций и затем инкрементно добавляете или удаляете функции, пока точность больше не улучшается [1].
Рассмотрите простой случай прямого выбора на наборе четырех функций. В первом прямом цикле выбора каждая из четырех функций тестируется независимо путем обучения сети и сравнения их точности валидации. Функция, которая привела к самой высокой точности валидации, отмечена. Во втором прямом цикле выбора лучшая функция от первого цикла сочетается с каждой из остающихся функций. Теперь каждая пара функций используется для обучения. Если точность во втором цикле не улучшилась по точности в первом цикле, концах процесса выбора. В противном случае новый лучший набор функций выбран. Прямой цикл выбора продолжается, пока точность больше не улучшается.
В обратном выборе признаков вы начинаете по образованию на наборе функций, который состоит из всех функций, и протестируйте, улучшается ли точность, когда вы удаляете функции.
Помощник функционирует (HelperSFS, HelperTrainAndValidateNetwork и HelperTrimOrPad) реализация прямой или обратный последовательный выбор признаков. Задайте учебный datastore, datastore валидации, экстрактор функции аудио, слоя сети, сетевые опции и направление. Как правило выберите вперед, если вы ожидаете маленький набор функций или назад если вы ожидаете большой набор функций.
direction = 'forward';
[вахтенный журнал, bestFeatures, bestNet, нормализаторы] = HelperSFS (adsTrain, adsValidation, afe, слои, опции, направление);
Starting parallel pool (parpool) using the 'local' profile ... Connected to the parallel pool (number of workers: 6).
logbook
выведите от HelperFeatureExtractor
таблица, содержащая все протестированные настройки функции и соответствующая точность валидации.
logbook
logbook=48×2 table
Features Accuracy
__________________________________ ________
"mfcc, gtcc" 97.333
"mfcc, mfccDelta, gtcc" 97
"mfcc, gtcc, spectralEntropy" 97
"mfcc, gtcc, spectralFlatness" 97
"mfcc, gtcc, spectralFlux" 97
"mfcc, gtcc, spectralSpread" 97
"gtcc" 96.667
"gtcc, spectralCentroid" 96.667
"gtcc, spectralFlux" 96.667
"mfcc, gtcc, spectralRolloffPoint" 96.667
"mfcc, gtcc, spectralSkewness" 96.667
"gtcc, spectralEntropy" 96.333
"mfcc, gtcc, gtccDeltaDelta" 96.333
"mfcc, gtcc, spectralKurtosis" 96.333
"mfccDelta, gtcc" 96
"gtcc, gtccDelta" 96
⋮
bestFeatures
выведите от HelperSFS
содержит struct с оптимальным набором функций к true
.
bestFeatures
bestFeatures = struct with fields:
mfcc: 1
mfccDelta: 0
mfccDeltaDelta: 0
gtcc: 1
gtccDelta: 0
gtccDeltaDelta: 0
spectralCentroid: 0
spectralCrest: 0
spectralDecrease: 0
spectralEntropy: 0
spectralFlatness: 0
spectralFlux: 0
spectralKurtosis: 0
spectralRolloffPoint: 0
spectralSkewness: 0
spectralSlope: 0
spectralSpread: 0
Можно установить audioFeatureExtractor
использование struct.
set(afe,bestFeatures) afe
afe = audioFeatureExtractor with properties: Properties Window: [240×1 double] OverlapLength: 160 SampleRate: 8000 FFTLength: [] SpectralDescriptorInput: 'linearSpectrum' Enabled Features mfcc, gtcc Disabled Features linearSpectrum, melSpectrum, barkSpectrum, erbSpectrum, mfccDelta, mfccDeltaDelta gtccDelta, gtccDeltaDelta, spectralCentroid, spectralCrest, spectralDecrease, spectralEntropy spectralFlatness, spectralFlux, spectralKurtosis, spectralRolloffPoint, spectralSkewness, spectralSlope spectralSpread, pitch, harmonicRatio To extract a feature, set the corresponding property to true. For example, obj.mfcc = true, adds mfcc to the list of enabled features.
HelperSFS
также выводит лучшую сеть выполнения и коэффициенты нормализации, которые соответствуют выбранным признакам. Чтобы сохранить сеть, сконфигурировал audioFeatureExtractor
, и коэффициенты нормализации, некомментарий эта линия:
% save('network_Audio_SequentialFeatureSelection.mat','bestNet','afe','normalizers')
Этот пример иллюстрирует рабочий процесс для последовательного выбора признаков для Рекуррентной нейронной сети (LSTM или BiLSTM). Это могло легко быть адаптировано к рабочим процессам RNN-CNN и CNN.
function [trueLabels,predictedLabels,net,normalizers] = HelperTrainAndValidateNetwork(adsTrain,adsValidation,afe,layers,options) % Train and validate a network. % % INPUTS: % adsTrain - audioDatastore object that points to training set % adsValidation - audioDatastore object that points to validation set % afe - audioFeatureExtractor object. % layers - Layers of LSTM or BiLSTM network % options - trainingOptions object % % OUTPUTS: % trueLabels - true labels of validation set % predictedLabels - predicted labels of validation set % net - trained network % normalizers - normalization factors for features under test % Copyright 2019 The MathWorks, Inc. % Convert the data to tall arrays. tallTrain = tall(adsTrain); tallValidation = tall(adsValidation); % Extract features from the training set. Reorient the features so that % time is along rows to be compatible with sequenceInputLayer. fs = afe.SampleRate; tallTrain = cellfun(@(x)HelperTrimOrPad(x,fs/2),tallTrain,"UniformOutput",false); tallTrain = cellfun(@(x)x/max(abs(x),[],'all'),tallTrain,"UniformOutput",false); tallFeaturesTrain = cellfun(@(x)extract(afe,x),tallTrain,"UniformOutput",false); tallFeaturesTrain = cellfun(@(x)x',tallFeaturesTrain,"UniformOutput",false); %#ok<NASGU> [~,featuresTrain] = evalc('gather(tallFeaturesTrain)'); % Use evalc to suppress command-line output. tallValidation = cellfun(@(x)HelperTrimOrPad(x,fs/2),tallValidation,"UniformOutput",false); tallValidation = cellfun(@(x)x/max(abs(x),[],'all'),tallValidation,"UniformOutput",false); tallFeaturesValidation = cellfun(@(x)extract(afe,x),tallValidation,"UniformOutput",false); tallFeaturesValidation = cellfun(@(x)x',tallFeaturesValidation,"UniformOutput",false); %#ok<NASGU> [~,featuresValidation] = evalc('gather(tallFeaturesValidation)'); % Use evalc to suppress command-line output. % Use the training set to determine the mean and standard deviation of each % feature. Normalize the training and validation sets. allFeatures = cat(2,featuresTrain{:}); M = mean(allFeatures,2,'omitnan'); S = std(allFeatures,0,2,'omitnan'); featuresTrain = cellfun(@(x)(x-M)./S,featuresTrain,'UniformOutput',false); for ii = 1:numel(featuresTrain) idx = find(isnan(featuresTrain{ii})); if ~isempty(idx) featuresTrain{ii}(idx) = 0; end end featuresValidation = cellfun(@(x)(x-M)./S,featuresValidation,'UniformOutput',false); for ii = 1:numel(featuresValidation) idx = find(isnan(featuresValidation{ii})); if ~isempty(idx) featuresValidation{ii}(idx) = 0; end end % Replicate the labels of the train and validation sets so that they are in % one-to-one correspondence with the sequences. labelsTrain = adsTrain.Labels; % Update input layer for the number of features under test. layers(1) = sequenceInputLayer(size(featuresTrain{1},1)); % Train the network. net = trainNetwork(featuresTrain,labelsTrain,layers,options); % Evaluate the network. Call classify to get the predicted labels for each % sequence. predictedLabels = classify(net,featuresValidation); trueLabels = adsValidation.Labels; % Save the normalization factors as a struct. normalizers.Mean = M; normalizers.StandardDeviation = S; end
function [logbook,bestFeatures,bestNet,bestNormalizers] = HelperSFS(adsTrain,adsValidate,afeThis,layers,options,direction) % % INPUTS: % adsTrain - audioDatastore object that points to training set % adsValidate - audioDatastore object that points to validation set % afe - audioFeatureExtractor object. Set all features to test to true % layers - Layers of LSTM or BiLSTM network % options - trainingOptions object % direction - SFS direction, specify as 'forward' or 'backward' % % OUTPUTS: % logbook - table containing feature configurations tested and corresponding validation accuracies % bestFeatures - struct containg best feature configuration % bestNet - Trained network with highest validation accuracy % bestNormalizers - Feature normalization factors for best features % Copyright 2019 The MathWorks, Inc. afe = copy(afeThis); featuresToTest = fieldnames(info(afe)); N = numel(featuresToTest); bestValidationAccuracy = 0; % Set the initial feature configuration: all on for backward selection % or all off for forward selection. featureConfig = info(afe); for i = 1:N if strcmpi(direction,"backward") featureConfig.(featuresToTest{i}) = true; else featureConfig.(featuresToTest{i}) = false; end end % Initialize logbook to track feature configuration and accuracy. logbook = table(featureConfig,0,'VariableNames',["Feature Configuration","Accuracy"]); % Perform sequential feature evaluation. wrapperIdx = 1; bestAccuracy = 0; while wrapperIdx <= N % Create a cell array containing all feature configurations to test % in the current loop. featureConfigsToTest = cell(numel(featuresToTest),1); for ii = 1:numel(featuresToTest) if strcmpi(direction,"backward") featureConfig.(featuresToTest{ii}) = false; else featureConfig.(featuresToTest{ii}) = true; end featureConfigsToTest{ii} = featureConfig; if strcmpi(direction,"backward") featureConfig.(featuresToTest{ii}) = true; else featureConfig.(featuresToTest{ii}) = false; end end % Loop over every feature set. for ii = 1:numel(featureConfigsToTest) % Determine the current feature configuration to test. Update % the feature afe. currentConfig = featureConfigsToTest{ii}; set(afe,currentConfig) % Train and get k-fold cross-validation accuracy for current % feature configuration. [trueLabels,predictedLabels,net,normalizers] = HelperTrainAndValidateNetwork(adsTrain,adsValidate,afe,layers,options); valAccuracy = mean(trueLabels==predictedLabels)*100; if valAccuracy > bestValidationAccuracy bestValidationAccuracy = valAccuracy; bestNet = net; bestNormalizers = normalizers; end % Update Logbook result = table(currentConfig,valAccuracy,'VariableNames',["Feature Configuration","Accuracy"]); logbook = [logbook;result]; %#ok<AGROW> end % Determine and print the setting with the best accuracy. If accuracy % did not improve, end the run. [a,b] = max(logbook{:,'Accuracy'}); if a <= bestAccuracy wrapperIdx = inf; else wrapperIdx = wrapperIdx + 1; end bestAccuracy = a; % Update the features-to-test based on the most recent winner. winner = logbook{b,'Feature Configuration'}; fn = fieldnames(winner); tf = structfun(@(x)(x),winner); if strcmpi(direction,"backward") featuresToRemove = fn(~tf); else featuresToRemove = fn(tf); end for ii = 1:numel(featuresToRemove) loc = strcmp(featuresToTest,featuresToRemove{ii}); featuresToTest(loc) = []; if strcmpi(direction,"backward") featureConfig.(featuresToRemove{ii}) = false; else featureConfig.(featuresToRemove{ii}) = true; end end end % Sort the logbook and make it more readable. logbook(1,:) = []; % Delete placeholder first row. logbook = sortrows(logbook,{'Accuracy'},{'descend'}); bestFeatures = logbook{1,'Feature Configuration'}; m = logbook{:,'Feature Configuration'}; fn = fieldnames(m); myString = strings(numel(m),1); for wrapperIdx = 1:numel(m) tf = structfun(@(x)(x),logbook{wrapperIdx,'Feature Configuration'}); myString(wrapperIdx) = strjoin(fn(tf),", "); end logbook = table(myString,logbook{:,'Accuracy'},'VariableNames',["Features","Accuracy"]); end
function y = HelperTrimOrPad(x,n) % y = HelperTrimOrPad(x,n) trims or pads the input x to n samples. If x is % trimmed, it is trimmed equally on the front and back. If x is padded, it is % padded equally on the front and back with zeros. For odd-length trimming or % padding, the extra sample is trimmed or padded from the back. % Copyright 2019 The MathWorks, Inc. a = size(x,1); if a < n frontPad = floor((n-a)/2); backPad = n - a - frontPad; y = [zeros(frontPad,1);x;zeros(backPad,1)]; elseif a > n frontTrim = floor((a-n)/2)+1; backTrim = a - n - frontTrim; y = x(frontTrim:end-backTrim); else y = x; end end
[1] Джайн, А., и Д. Зонгкер. "Выбор признаков: Оценка, Приложение и Эффективность Небольшой выборки". Транзакции IEEE согласно Анализу Шаблона и Искусственному интеллекту. Издание 19, Выпуск 2, 1997, стр 153-158.
[2] Якобовский. “Jakobovski/Free-Spoken-Digit-Dataset”. GitHub, 30 мая 2019. https://github.com/Jakobovski/free-spoken-digit-dataset.