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

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

Введение

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

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

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

Чтобы запустить пример, необходимо сначала загрузить набор данных. Если вы не хотите загружать набор данных или обучать сеть, то можно загрузить предварительно обученную сеть путем открытия этого примера в MATLAB® и ввода load("speechDetectNet.mat") в командной строке.

Сводные данные в качестве примера

Пример проходит следующие шаги:

Обучение:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

datafolder = PathToDatabase;
ads        = audioDatastore(datafolder,"Includesubfolders",true);

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

indices = cellfun(@(c)~contains(c,"_background_noise_"),ads.Files);
ads     = subset(ads,indices);

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

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

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

Переставьте порядок файлов в хранилищах данных.

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

Создайте сигнал обучения речи плюс тишина

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

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

Слушайте звуковой сигнал с помощью звуковой команды.

sound(data,Fs)

Постройте звуковой сигнал.

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

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

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

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

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

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

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

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

maskTraining = zeros(duration,1);

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

maxSilenceSegment = 2;

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

numSamples = 1;
while numSamples < duration
    data = read(adsTrain);
    % Remove non-voice areas from the segment
    data = HelperGetSpeechSegments(data,Fs);
    data = data{1};
    % 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

Визуализируйте 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 Area")
title("Training Signal (first 10 seconds)");

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

sound(audioTraining(range),Fs);

Добавьте шум в учебный сигнал

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

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

noise = audioread("WashingMachine-16-8-mono-1000secs.wav");
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)

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

[~,noisyMask] = HelperGetSpeechSegments(audioTrainingNoisy,Fs);

Визуализируйте 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);
    % Remove non-voice areas from the segment
    data = HelperGetSpeechSegments(data,Fs);
    data = data{1};
    % Write speech segment to training signal
    audioValidation(numSamples:numSamples+numel(data)-1) = data;
    maskValidation(numSamples:numSamples+numel(data)-1)  = true;
    % Random silence period
    numSilenceSamples = randi(maxSilenceSegment*Fs,1,1);
    numSamples        = numSamples + numel(data) + numSilenceSamples;
end

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

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

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

Извлеките учебные функции

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

Задайте системные параметры.

WindowLength         = 256;
params.WindowLength  = WindowLength;
params.Window        = hann(WindowLength,"Periodic");
params.OverlapLength = 128;
params.FFTLength     = 256;
params.Range         = [0, Fs/2];
params.SpectrumType  = "Power";

Вычислите спектр мощности учебного сигнала.

[~,frequencyVector,~,S] = spectrogram(audioTrainingNoisy,hann(WindowLength,"Periodic"),128,WindowLength,Fs,"power" ,"onesided");

Извлеките функции и затем конкатенируйте их.

SCentroid     = spectralCentroid(S,frequencyVector);
SCrest        = spectralCrest(S,frequencyVector);
Sentropy      = spectralEntropy(S,frequencyVector);
SFlux         = spectralFlux(S,frequencyVector);
SKurtosis     = spectralKurtosis(S,frequencyVector);
SRolloffPoint = spectralRolloffPoint(S,frequencyVector);
SSkewness     = spectralSkewness(S,frequencyVector);
SSlope        = spectralSlope(S,frequencyVector);
hr            = harmonicRatio(audioTrainingNoisy,Fs,"Window",params.Window,"OverlapLength",params.OverlapLength);

featuresTraining = [SCentroid SCrest Sentropy SFlux SKurtosis SRolloffPoint SSkewness SSlope hr];

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

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

      124999


numFeatures =

     9

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

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

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

Извлеките функции от сигнала валидации использование того же процесса. Функция помощника getSpeechActivityFeatures инкапсулирует шаги нормализации и выделение признаков.

featuresValidation = getSpeechActivityFeatures(audioValidationNoisy,Fs,params);

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

hopLength = (WindowLength - params.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;
trainFeatureCell = {};
trainLabelCell   = {};
index = 1;
while index < size(featuresTraining,1) - sequenceLength
   trainFeatureCell{end+1} = featuresTraining(index:index+sequenceLength-1,:).'; %#ok
   trainLabelCell{end+1}   =  maskTrainingCat(index:index+sequenceLength-1).';   %#ok
   index = index + round(sequenceLength/4);
end

Задайте сетевую архитектуру LSTM

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

Задайте входной размер, чтобы быть последовательностями длины 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",10);

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

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

doTraining = true;
if doTraining
   [speechDetectNet,info] = trainNetwork(trainFeatureCell,trainLabelCell,layers,options);
    fprintf("Validation accuracy: %f percent.\n" , info.ValidationAccuracy(end));
else
    load speechDetectNet
end
Validation accuracy: 90.734375 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";

Каждое значение маски соответствует 128 выборкам (длина транзитного участка). Повторите значения маски, чтобы сделать маску из той же длины как звуковой сигнал валидации. Сделайте то же самое для базовой маски.

EstimatedVADMask = repmat(EstimatedVADMask,1,hopLength);
EstimatedVADMask = reshape(EstimatedVADMask.',numel(EstimatedVADMask),1);

ActualVADMask = repmat(maskValidation,1,hopLength);
ActualVADMask = reshape(ActualVADMask.',numel(ActualVADMask),1);

Изолируйте области предполагаемого речевого действия путем установки областей без речи на NaN.

audioEst = audioValidationNoisy;
audioEst(EstimatedVADMask == 0) = NaN;

audioAct = audioValidation;
audioAct(ActualVADMask == 0) = NaN;

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

figure
range = 50*Fs:100*Fs;
subplot(2,1,1)
plot(range/Fs,[audioValidationNoisy(range),audioEst(range)])
title("Estimated Speech Region")
legend("Signal","Speech Detected")
grid on
subplot(2,1,2)
plot(range/Fs,[audioValidation(range),audioAct(range)])
title("Baseline")
legend("Signal","Speech Detected")
grid on
xlabel("Time (s)")