Обнаружение голосовой активности в шуме с помощью глубокого обучения

Этот пример показывает, как обнаружить области речи в среде низкий сигнал-шум с помощью глубокого обучения. Пример использует набор данных речевых команд, чтобы обучить двунаправленную сеть долгой краткосрочной памяти (BiLSTM) для обнаружения голосовой активности.

Введение

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

Этот пример использует сети долгой краткосрочной памяти (LSTM), которые являются типом рекуррентной нейронной сети (RNN), хорошо подходящим для изучения данных последовательности и timeseries. Сеть LSTM может изучать долгосрочные зависимости между временными шагами последовательности. Слой LSTM (lstmLayer (Deep Learning Toolbox)) может смотреть на временную последовательность в прямом направлении, в то время как двунаправленный слой LSTM (bilstmLayer (Deep Learning Toolbox)) может посмотреть на временную последовательность как в прямом, так и в обратном направлениях. Этот пример использует двунаправленный слой LSTM.

Этот пример обучает сеть LSTM обнаружения речевой активности с характерными последовательностями спектральных характеристик и метрикой гармонического отношения.

В сценариях с высоким ОСШ традиционные алгоритмы обнаружения речи работают адекватно. Чтение в аудио файла, которое состоит из слов, сказанных с паузами между. Повторно отобразите аудио на 16 кГц. Слушайте аудио.

fs = 16e3;
[speech,fileFs] = audioread('Counting-16-44p1-mono-15secs.wav');
speech = resample(speech,fs,fileFs);
speech = speech/max(abs(speech));
sound(speech,fs)

Используйте detectSpeech функция для определения местоположения областей речи. The detectSpeech функция правильно определяет все области речи.

win = hamming(50e-3 * fs,'periodic');
detectSpeech(speech,fs,'Window',win)

Повреждение аудиосигнала с шумом стиральной машины при ОСШ -20 дБ. Прослушайте поврежденный звук.

[noise,fileFs] = audioread('WashingMachine-16-8-mono-200secs.mp3');
noise = resample(noise,fs,fileFs);

SNR = -20;
noiseGain = 10^(-SNR/20) * norm(speech) / norm(noise);

noisySpeech = speech + noiseGain*noise(1:numel(speech));
noisySpeech = noisySpeech./max(abs(noisySpeech));

sound(noisySpeech,fs)

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

detectSpeech(noisySpeech,fs,'Window',win)

Загрузка и загрузка предварительно обученной сети и настроенного audioFeatureExtractor объект. Сеть была обучена обнаруживать речь в окружениях с низким ОСШ, заданных функциях, выводимых из audioFeatureExtractor объект.

url = 'http://ssd.mathworks.com/supportfiles/audio/VoiceActivityDetection.zip';
    downloadNetFolder = tempdir;
    netFolder = fullfile(downloadNetFolder,'VoiceActivityDetection');
    if ~exist(netFolder,'dir')
        disp('Downloading pretrained network (1 file - 8 MB) ...')
        unzip(url,downloadNetFolder)
    end
load(fullfile(netFolder,'voiceActivityDetectionExample.mat'));
speechDetectNet
speechDetectNet = 
  SeriesNetwork with properties:

         Layers: [6×1 nnet.cnn.layer.Layer]
     InputNames: {'sequenceinput'}
    OutputNames: {'classoutput'}

afe
afe = 
  audioFeatureExtractor with properties:

   Properties
                     Window: [256×1 double]
              OverlapLength: 128
                 SampleRate: 16000
                  FFTLength: []
    SpectralDescriptorInput: 'linearSpectrum'

   Enabled Features
     spectralCentroid, spectralCrest, spectralEntropy, spectralFlux, spectralKurtosis, spectralRolloffPoint
     spectralSkewness, spectralSlope, harmonicRatio

   Disabled Features
     linearSpectrum, melSpectrum, barkSpectrum, erbSpectrum, mfcc, mfccDelta
     mfccDeltaDelta, gtcc, gtccDelta, gtccDeltaDelta, spectralDecrease, spectralFlatness
     spectralSpread, pitch


   To extract a feature, set the corresponding property to true.
   For example, obj.mfcc = true, adds mfcc to the list of enabled features.

Извлеките функции из речевых данных и затем нормализуйте их. Ориентируйте функции так, чтобы время было между столбцами.

features = extract(afe,noisySpeech);
features = (features - mean(features,1)) ./ std(features,[],1);
features = features';

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

decisionsCategorical = classify(speechDetectNet,features);

Каждое решение соответствует окну анализа, проанализированному audioFeatureExtractor. Тиражируйте решения так, чтобы они находились в одном соответствии с аудио выборок. Постройте график речи, шумной речи и решений VAD.

decisionsWindow = 1.2*(double(decisionsCategorical)-1);
decisionsSample = [repelem(decisionsWindow(1),numel(afe.Window)), ...
                   repelem(decisionsWindow(2:end),numel(afe.Window)-afe.OverlapLength)];


t = (0:numel(decisionsSample)-1)/afe.SampleRate;
plot(t,noisySpeech(1:numel(t)), ...
     t,speech(1:numel(t)), ...
     t,decisionsSample);
xlabel('Time (s)')
ylabel('Amplitude')
legend('Noisy Speech','Speech','VAD','Location','southwest')

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

audiowrite('Speech.wav',speech,fs)
audiowrite('Noise.wav',noise,fs)

Чтобы применить сеть VAD к передаче потокового аудио, вы должны пойти на компромисс между задержкой и точностью. Задайте параметры для обнаружения потоковой голосовой активности в демонстрации шума. Можно задать длительность теста, длину последовательности, поданную в сеть, длину скачка последовательности и ОСШ для тестирования. Как правило, увеличение длины последовательности увеличивает точность, но также увеличивает задержку. Можно также выбрать выход сигнала к устройству в качестве исходного сигнала или сигнала с шумом.

testDuration = 20;

sequenceLength = 400;
sequenceHop = 20;
ОСШ = -20;
noiseGain = 10 ^ (-SNR/20) * norm (речь )/norm (шум);

signalToListenTo = "noisy";

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

helperStreamingDemo(speechDetectNet,afe, ...
                    'Speech.wav','Noise.wav', ...
                    testDuration,sequenceLength,sequenceHop,signalToListenTo,noiseGain);

Остальная часть примера проходит через обучение и оценку сети VAD.

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

Обучение:

  1. Создайте audioDatastore это указывает на файлы звуковой речи, используемые для обучения сети LSTM.

  2. Создайте обучающий сигнал, состоящий из речевых сегментов, разделенных сегментами молчания различной длительности.

  3. Повреждение сигнала речи плюс тишина с помощью шума стиральной машины (ОСШ = -10 дБ).

  4. Извлечение функции последовательностей, состоящих из спектральных характеристик и гармонического отношения, из сигнала с шумом.

  5. Обучите сеть LSTM с помощью функции последовательностей для идентификации областей голосовой активности.

Предсказание:

  1. Создайте audioDatastore речевых файлов, используемых для тестирования обученной сети, и создания тестового сигнала, состоящего из речи, разделенной сегментами молчания.

  2. Повреждение тестового сигнала с помощью шума стиральной машины (ОСШ = -10 дБ).

  3. Извлеките последовательности функций из шумного тестового сигнала.

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

  5. Сравните точность сети с базовой линией голосовой активности из тестового сигнала сигнал плюс молчание.

Вот эскиз тренировочного процесса.

Вот эскиз процесса предсказания. Вы используете обученную сеть, чтобы делать предсказания.

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

Загрузите и извлеките набор данных речевых команд Google [1].

url = 'https://ssd.mathworks.com/supportfiles/audio/google_speech.zip';

downloadFolder = tempdir;
datasetFolder = fullfile(downloadFolder,'google_speech');

if ~exist(datasetFolder,'dir')
    disp('Downloading Google speech commands data set (1.9 GB)...')
    unzip(url,downloadFolder)
end
Downloading Google speech commands data set (1.9 GB)...

Создайте audioDatastore это указывает на обучающие данные набор.

adsTrain = audioDatastore(fullfile(datasetFolder, 'train'), "Includesubfolders",true);

Создайте audioDatastore это указывает на набор данных валидации.

adsValidation = audioDatastore(fullfile(datasetFolder, 'validation'), "Includesubfolders",true);

Создайте обучающий сигнал Speech-Plus-Silence

Чтение содержимого аудио файла с помощью read. Получите частоту дискретизации от adsInfo struct.

[data,adsInfo] = read(adsTrain);
Fs = adsInfo.SampleRate;

Прослушайте аудиосигнал с помощью команды sound.

sound(data,Fs)

Постройте график аудиосигнала.

timeVector = (1/Fs) * (0:numel(data)-1);
plot(timeVector,data)
ylabel("Amplitude")
xlabel("Time (s)")
title("Sample Audio")
grid on

Сигнал имеет неречевые фрагменты (тишина, фоновый шум и т.д.), которые не содержат полезной речевой информации. Этот пример удаляет молчание с помощью detectSpeech функция.

Извлеките полезный фрагмент данных. Задайте 50 мс периодического окна Хэмминга для анализа. Функции detectSpeech без выходных аргументов для построения графика обнаруженных речевых областей. Функции detectSpeech снова для возврата индексов обнаруженной речи. Изолируйте обнаруженные области речи и используйте sound команда для прослушивания аудио.

win = hamming(50e-3 * Fs,'periodic');

detectSpeech(data,Fs,'Window',win);

speechIndices = detectSpeech(data,Fs,'Window',win);

sound(data(speechIndices(1,1):speechIndices(1,2)),Fs)

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

speechIndices(1,1) = max(speechIndices(1,1) - 5*numel(win),1);
speechIndices(1,2) = min(speechIndices(1,2) + 5*numel(win),numel(data));

sound(data(speechIndices(1,1):speechIndices(1,2)),Fs)

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

reset(adsTrain)
adsTrain = shuffle(adsTrain);
adsValidation = shuffle(adsValidation);

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

TM = [];
for index1 = 1:500
     data = read(adsTrain); 
     [~,T] = detectSpeech(data,Fs,'Window',win);
     TM = [TM;T];
end

T = mean(TM);

reset(adsTrain)

Создайте 1000-секундный сигнал обучения путем объединения нескольких речевых файлов из набора обучающих данных. Использование detectSpeech для удаления нежелательных фрагментов каждого файла. Вставьте случайный период молчания между сегментами речи.

Предварительно выделите сигнал обучения.

duration = 2000*Fs;
audioTraining = zeros(duration,1);

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

maskTraining = zeros(duration,1);

Задайте максимальную длительность сегмента тишины 2 секунды.

maxSilenceSegment = 2;

Создайте обучающий сигнал путем вызова read на datastore в цикле.

numSamples = 1;    

while numSamples < duration
    data = read(adsTrain);
    data = data ./ max(abs(data)); % Normalize amplitude

    % Determine regions of speech
    idx = detectSpeech(data,Fs,'Window',win,'Thresholds',T);

    % If a region of speech is detected
    if ~isempty(idx)
        
        % Extend the indices by five frames
        idx(1,1) = max(1,idx(1,1) - 5*numel(win));
        idx(1,2) = min(length(data),idx(1,2) + 5*numel(win));
        
        % Isolate the speech
        data = data(idx(1,1):idx(1,2));
        
        % Write speech segment to training signal
        audioTraining(numSamples:numSamples+numel(data)-1) = data;
        
        % Set VAD baseline
        maskTraining(numSamples:numSamples+numel(data)-1) = true;
        
        % Random silence period
        numSilenceSamples = randi(maxSilenceSegment*Fs,1,1);
        numSamples = numSamples + numel(data) + numSilenceSamples;
    end
end

Визуализируйте 10-секундный фрагмент обучающего сигнала. Постройте график базовой маски голосовой активности.

figure
range = 1:10*Fs;
plot((1/Fs)*(range-1),audioTraining(range));
hold on
plot((1/Fs)*(range-1),maskTraining(range));
grid on
lines = findall(gcf,"Type","Line");
lines(1).LineWidth = 2;
xlabel("Time (s)")
legend("Signal","Speech Region")
title("Training Signal (first 10 seconds)");

Прослушайте первые 10 секунд обучающего сигнала.

sound(audioTraining(range),Fs);

Добавьте шум к обучающему сигналу

Повреждите сигнал обучения с шумом стиральной машины, добавив шум стиральной машины к речевому сигналу таким образом, чтобы отношение сигнал/шум составляло -10 дБ.

Считайте шум 8 кГц и преобразуйте его в 16 кГц.

noise = audioread("WashingMachine-16-8-mono-1000secs.mp3");
noise = resample(noise,2,1);

Коррумпированный обучающий сигнал с шумом.

audioTraining = audioTraining(1:numel(noise));
SNR = -10;
noise = 10^(-SNR/20) * noise * norm(audioTraining) / norm(noise);
audioTrainingNoisy = audioTraining + noise; 
audioTrainingNoisy = audioTrainingNoisy / max(abs(audioTrainingNoisy));

Визуализируйте 10-секундный фрагмент шумного обучающего сигнала. Постройте график базовой маски голосовой активности.

figure
plot((1/Fs)*(range-1),audioTrainingNoisy(range));
hold on
plot((1/Fs)*(range-1),maskTraining(range));
grid on
lines = findall(gcf,"Type","Line");
lines(1).LineWidth = 2;
xlabel("Time (s)")
legend("Noisy Signal","Speech Area")
title("Training Signal (first 10 seconds)");

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

sound(audioTrainingNoisy(range),Fs)

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

speechIndices = detectSpeech(audioTrainingNoisy,Fs,'Window',win);

speechIndices(:,1) = max(1,speechIndices(:,1) - 5*numel(win));
speechIndices(:,2) = min(numel(audioTrainingNoisy),speechIndices(:,2) + 5*numel(win));

noisyMask = zeros(size(audioTrainingNoisy));
for ii = 1:size(speechIndices)
    noisyMask(speechIndices(ii,1):speechIndices(ii,2)) = 1;
end

Визуализируйте 10-секундный фрагмент шумного обучающего сигнала. Постройте график маски голосовой активности, полученной при анализе сигнала с шумом.

figure
plot((1/Fs)*(range-1),audioTrainingNoisy(range));
hold on
plot((1/Fs)*(range-1),noisyMask(range));
grid on
lines = findall(gcf,"Type","Line");
lines(1).LineWidth = 2;
xlabel("Time (s)")
legend("Noisy Signal","Mask from Noisy Signal")
title("Training Signal (first 10 seconds)");

Создайте сигнал валидации речи плюс молчание

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

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

duration = 200*Fs;
audioValidation = zeros(duration,1);
maskValidation = zeros(duration,1);

Создайте сигнал валидации путем вызова read на datastore в цикле.

numSamples = 1;    
while numSamples < duration
    data = read(adsValidation);
    data = data ./ max(abs(data)); % Normalize amplitude
    
    % Determine regions of speech
    idx = detectSpeech(data,Fs,'Window',win,'Thresholds',T);
    
    % If a region of speech is detected
    if ~isempty(idx)
        
        % Extend the indices by five frames
        idx(1,1) = max(1,idx(1,1) - 5*numel(win));
        idx(1,2) = min(length(data),idx(1,2) + 5*numel(win));

        % Isolate the speech
        data = data(idx(1,1):idx(1,2));
        
        % Write speech segment to training signal
        audioValidation(numSamples:numSamples+numel(data)-1) = data;
        
        % Set VAD Baseline
        maskValidation(numSamples:numSamples+numel(data)-1) = true;
        
        % Random silence period
        numSilenceSamples = randi(maxSilenceSegment*Fs,1,1);
        numSamples = numSamples + numel(data) + numSilenceSamples;
    end
end

Повреждите сигнал валидации с помощью шума стиральной машины, добавив шум стиральной машины к речевому сигналу таким образом, чтобы отношение сигнал/шум составляло -10 дБ. Используйте другой файл шума для сигнала валидации, чем для обучающего сигнала.

noise = audioread("WashingMachine-16-8-mono-200secs.mp3");
noise = resample(noise,2,1);
noise = noise(1:duration);
audioValidation = audioValidation(1:numel(noise));

noise = 10^(-SNR/20) * noise * norm(audioValidation) / norm(noise);
audioValidationNoisy = audioValidation + noise; 
audioValidationNoisy = audioValidationNoisy / max(abs(audioValidationNoisy));

Извлечение обучающих функций

Этот пример обучает сеть LSTM с помощью следующих функций:

Этот пример использует audioFeatureExtractor чтобы создать оптимальный трубопровод редукции данных для набора функций. Создайте audioFeatureExtractor объект для извлечения функции набора. Используйте 256-точечное окно Ханна с 50% перекрытием.

afe = audioFeatureExtractor('SampleRate',Fs, ...
    'Window',hann(256,"Periodic"), ...
    'OverlapLength',128, ...
    ...
    'spectralCentroid',true, ...
    'spectralCrest',true, ...
    'spectralEntropy',true, ...
    'spectralFlux',true, ...
    'spectralKurtosis',true, ...
    'spectralRolloffPoint',true, ...
    'spectralSkewness',true, ...
    'spectralSlope',true, ...
    'harmonicRatio',true);

featuresTraining = extract(afe,audioTrainingNoisy);

Отобразите размерности матрицы функций. Первая размерность соответствует количеству окон, в которые был разбит сигнал (зависит от длины окна и длины перекрытия). Второе измерение - это количество функций, используемых в этом примере.

[numWindows,numFeatures] = size(featuresTraining)
numWindows = 125009
numFeatures = 9

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

Вычислите среднее и стандартное отклонение для каждого коэффициента и используйте их для нормализации данных.

M = mean(featuresTraining,1);
S = std(featuresTraining,[],1);
featuresTraining = (featuresTraining - M) ./ S;

Извлеките функции из сигнала валидации, используя тот же процесс.

featuresValidation = extract(afe,audioValidationNoisy);
featuresValidation = (featuresValidation - mean(featuresValidation,1)) ./ std(featuresValidation,[],1);

Каждая функция соответствует 128 выборкам данных (длина скачка). Для каждого скачка установите ожидаемое голосовое/отсутствие голосового значения в режим значений маски базовой линии, соответствующих этим 128 выборкам. Преобразуйте голосовую маску/голосовую маску нет в категориальную.

windowLength = numel(afe.Window);
hopLength = windowLength - afe.OverlapLength;
range = (hopLength) * (1:size(featuresTraining,1)) + hopLength;
maskMode = zeros(size(range));
for index = 1:numel(range)
    maskMode(index) = mode(maskTraining( (index-1)*hopLength+1:(index-1)*hopLength+windowLength ));
end
maskTraining = maskMode.';

maskTrainingCat = categorical(maskTraining);

Сделайте то же самое для маски валидации.

range = (hopLength) * (1:size(featuresValidation,1)) + hopLength;
maskMode = zeros(size(range));
for index = 1:numel(range)
    maskMode(index) = mode(maskValidation( (index-1)*hopLength+1:(index-1)*hopLength+windowLength ));
end
maskValidation = maskMode.';

maskValidationCat = categorical(maskValidation);

Разделите функции и маску на последовательности длиной 800 с 75% перекрытием между последовательностями.

sequenceLength = 800;
sequenceOverlap = round(0.75*sequenceLength);

trainFeatureCell = helperFeatureVector2Sequence(featuresTraining',sequenceLength,sequenceOverlap);
trainLabelCell = helperFeatureVector2Sequence(maskTrainingCat',sequenceLength,sequenceOverlap);

Определите сетевую архитектуру LSTM

Сети LSTM могут изучать долгосрочные зависимости между временными шагами данных последовательности. Этот пример использует двунаправленный слой LSTM bilstmLayer (Deep Learning Toolbox), чтобы посмотреть на последовательность как в прямом, так и в обратном направлениях.

Задайте размер входа, чтобы быть последовательностями длины 9 (количество функций). Задайте скрытый двунаправленный слой LSTM с размером выходом 200 и выведите последовательность. Эта команда предписывает двунаправленному слою LSTM сопоставить входные временные ряды с 200 функциями, которые передаются следующему слою. Затем задайте двунаправленный слой LSTM с размером выходом 200 и выведите последний элемент последовательности. Эта команда предписывает двунаправленному слою LSTM сопоставить его вход с 200 функциями, а затем подготавливает выход для полностью подключенного уровня. Наконец, задайте два класса, включив полносвязный слой размера 2, далее слой softmax и слой классификации.

layers = [ ...    
    sequenceInputLayer( size(featuresValidation,2) )    
    bilstmLayer(200,"OutputMode","sequence")   
    bilstmLayer(200,"OutputMode","sequence")   
    fullyConnectedLayer(2)   
    softmaxLayer   
    classificationLayer      
    ];

Затем задайте опции обучения для классификатора. Задайте MaxEpochs на 20 так, что сеть пропускает 20 через обучающие данные. Задайте MiniBatchSize на 64 чтобы сеть рассматривала 64 обучающих сигнала за раз. Задайте Plots на "training-progress" чтобы сгенерировать графики, которые показывают процесс обучения с увеличениями количества итераций. Задайте Verbose на false чтобы отключить печать выхода таблицы, который соответствует данным, показанным на графике. Задайте Shuffle на "every-epoch" тасовать обучающую последовательность в начале каждой эпохи. Задайте LearnRateSchedule на "piecewise" уменьшить скорость обучения на заданный коэффициент (0,1) каждый раз, когда прошло определенное количество эпох (10). Задайте ValidationData к предикторам и целям валидации.

Этот пример использует решатель адаптивной оценки момента (ADAM). ADAM работает лучше с рекуррентными нейронными сетями (RNNs), такими как LSTMs, чем стохастический градиентный спуск по умолчанию с импульсом (SGDM) решателя.

maxEpochs = 20;
miniBatchSize = 64;
options = trainingOptions("adam", ...
    "MaxEpochs",maxEpochs, ...
    "MiniBatchSize",miniBatchSize, ...
    "Shuffle","every-epoch", ...
    "Verbose",0, ...
    "SequenceLength",sequenceLength, ...
    "ValidationFrequency",floor(numel(trainFeatureCell)/miniBatchSize), ...
    "ValidationData",{featuresValidation.',maskValidationCat.'}, ...
    "Plots","training-progress", ...
    "LearnRateSchedule","piecewise", ...
    "LearnRateDropFactor",0.1, ...
    "LearnRateDropPeriod",5);

Обучите сеть LSTM

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

doTraining = true;
if doTraining
   [speechDetectNet,netInfo] = trainNetwork(trainFeatureCell,trainLabelCell,layers,options);
    fprintf("Validation accuracy: %f percent.\n", netInfo.FinalValidationAccuracy);
else
    load speechDetectNet
end
Validation accuracy: 91.320312 percent.

Используйте обученную сеть для обнаружения речевой активности

Оцените голосовую активность в сигнале валидации с помощью обученной сети. Преобразуйте предполагаемую маску VAD из категориальной в двойную.

EstimatedVADMask = classify(speechDetectNet,featuresValidation.');
EstimatedVADMask = double(EstimatedVADMask);
EstimatedVADMask = EstimatedVADMask.' - 1;

Вычислите и постройте матрицу неточностей валидации из векторов фактических и оцененных меток.

figure
cm = confusionchart(maskValidation,EstimatedVADMask,"title","Validation Accuracy");
cm.ColumnSummary = "column-normalized";
cm.RowSummary = "row-normalized";

Если вы изменили параметры сети или трубопровода редукции данных, рассмотрите пересохранение файла MAT с новой сетью и audioFeatureExtractor объект.

resaveNetwork = false;
if resaveNetwork
     сохранить'Audio_VoiceActivityDetectionExample.mat','speechDetectNet','afe');
end

Вспомогательные функции

Преобразуйте векторы функций в последовательности

function [sequences,sequencePerFile] = helperFeatureVector2Sequence(features,featureVectorsPerSequence,featureVectorOverlap)
    if featureVectorsPerSequence <= featureVectorOverlap
        error('The number of overlapping feature vectors must be less than the number of feature vectors per sequence.')
    end

    if ~iscell(features)
        features = {features};
    end
    hopLength = featureVectorsPerSequence - featureVectorOverlap;
    idx1 = 1;
    sequences = {};
    sequencePerFile = cell(numel(features),1);
    for ii = 1:numel(features)
        sequencePerFile{ii} = floor((size(features{ii},2) - featureVectorsPerSequence)/hopLength) + 1;
        idx2 = 1;
        for j = 1:sequencePerFile{ii}
            sequences{idx1,1} = features{ii}(:,idx2:idx2 + featureVectorsPerSequence - 1); %#ok<AGROW>
            idx1 = idx1 + 1;
            idx2 = idx2 + hopLength;
        end
    end
end

Потоковая демонстрация

function helperStreamingDemo(speechDetectNet,afe,cleanSpeech,noise,testDuration,sequenceLength,sequenceHop,signalToListenTo,noiseGain)

Создание dsp.AudioFileReader объекты для чтения из файлов речи и шума система координат за кадром.

    speechReader = dsp.AudioFileReader(cleanSpeech,'PlayCount',inf);
    noiseReader = dsp.AudioFileReader(noise,'PlayCount',inf);
    fs = speechReader.SampleRate;

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

    movSTD = dsp.MovingStandardDeviation('Method','Exponential weighting','ForgettingFactor',1);
    movMean = dsp.MovingAverage('Method','Exponential weighting','ForgettingFactor',1);

Создайте три dsp.AsyncBuffer объекты. Один для буферизации вход аудио, один для буферизации извлечённых функций и один для буферизации выход буфера. Буфер выхода необходим только для визуализации решений в режиме реального времени.

    audioInBuffer = dsp.AsyncBuffer;
    featureBuffer = dsp.AsyncBuffer;
    audioOutBuffer = dsp.AsyncBuffer;

Для звуковых буферов вы будете буферизировать и исходный чистый речевой сигнал, и сигнал с шумом. Вы будете воспроизводить только указанный signalToListenTo. Преобразуйте signalToListenTo переменная для канала, который вы хотите прослушать.

    channelToListenTo = 1;
    if strcmp(signalToListenTo,"clean")
        channelToListenTo = 2;
    end

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

    scope = timescope('SampleRate',fs, ...
        'TimeSpanSource','property', ...
        'TimeSpan',3, ...
        'BufferLength',fs*3*3, ...
        'YLimits',[-1 1], ...
        'TimeSpanOverrunAction','Scroll', ...
        'ShowGrid',true, ...
        'NumInputPorts',3, ...
        'LayoutDimensions',[3,1], ...
        'Title','Noisy Speech');
    scope.ActiveDisplay = 2;
    scope.Title = 'Clean Speech (Original)';
    scope.YLimits = [-1 1];
    scope.ActiveDisplay = 3;
    scope.Title = 'Detected Speech';
    scope.YLimits = [-1 1];

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

    deviceWriter = audioDeviceWriter('SampleRate',fs);

Инициализируйте переменные, используемые в цикле.

    windowLength = numel(afe.Window);
    hopLength = windowLength - afe.OverlapLength;
    myMax = 0;
    audioBufferInitialized = false;
    featureBufferInitialized = false;

Запустите потоковую демонстрацию.

    tic
    while toc < testDuration
    
        % Read a frame of the speech signal and a frame of the noise signal
        speechIn = speechReader();
        noiseIn = noiseReader();
        
        % Mix the speech and noise at the specified SNR
        noisyAudio = speechIn + noiseGain*noiseIn;
        
        % Update a running max for normalization
        myMax = max(myMax,max(abs(noisyAudio)));
        
        % Write the noisy audio and speech to buffers
        write(audioInBuffer,[noisyAudio,speechIn]);
        
        % If enough samples are buffered,
        % mark the audio buffer as initialized and push the read pointer
        % for the audio buffer up a window length.
        if audioInBuffer.NumUnreadSamples >= windowLength && ~audioBufferInitialized
            audioBufferInitialized = true;
            read(audioInBuffer,windowLength);
        end
        
        % If enough samples are in the audio buffer to calculate a feature
        % vector, read the samples, normalize them, extract the feature vectors, and write
        % the latest feature vector to the features buffer.
        while (audioInBuffer.NumUnreadSamples >= hopLength) && audioBufferInitialized
            x = read(audioInBuffer,windowLength + hopLength,windowLength);
            write(audioOutBuffer,x(end-hopLength+1:end,:));
            noisyAudio = x(:,1);
            noisyAudio = noisyAudio/myMax;
            features = extract(afe,noisyAudio);
            write(featureBuffer,features(2,:));
        end
        
        % If enough feature vectors are buffered, mark the feature buffer
        % as initialized and push the read pointer for the feature buffer
        % and the audio output buffer (so that they are in sync).
        if featureBuffer.NumUnreadSamples >= (sequenceLength + sequenceHop) && ~featureBufferInitialized
            featureBufferInitialized = true;
            read(featureBuffer,sequenceLength - sequenceHop);
            read(audioOutBuffer,(sequenceLength - sequenceHop)*windowLength);
        end
        
       while featureBuffer.NumUnreadSamples >= sequenceHop && featureBufferInitialized
            features = read(featureBuffer,sequenceLength,sequenceLength - sequenceHop);
            features(isnan(features)) = 0;
            
            % Use only the new features to update the
            % standard deviation and mean. Normalize the features.
            localSTD = movSTD(features(end-sequenceHop+1:end,:));
            localMean = movMean(features(end-sequenceHop+1:end,:));
            features = (features - localMean(end,:)) ./ localSTD(end,:);
            
            decision = classify(speechDetectNet,features');
            decision = decision(end-sequenceHop+1:end);
            decision = double(decision)' - 1;
            decision = repelem(decision,hopLength);
            
            audioHop = read(audioOutBuffer,sequenceHop*hopLength);
            
            % Listen to the speech or speech+noise
            deviceWriter(audioHop(:,channelToListenTo));
            
            % Visualize the speech+noise, the original speech, and the
            % voice activity detection.
            scope(audioHop(:,1),audioHop(:,2),audioHop(:,1).*decision)
       end
    end
    release(deviceWriter)
    release(audioInBuffer)
    release(audioOutBuffer)
    release(featureBuffer)
    release(movSTD)
    release(movMean)
    release(scope)
end

Ссылки

[1] Warden P. «Speech Commands: A public dataset for single-word speech recognition», 2017. Доступно из https://storage.googleapis.com/download.tensorflow.org/data/speech_commands_v0.01.tar.gz. Копирайт Google 2017. Набор данных речевых команд лицензирован по лицензии Creative Commons Attribution 4.0