Main Content

이 번역 페이지는 최신 내용을 담고 있지 않습니다. 최신 내용을 영문으로 보려면 여기를 클릭하십시오.

딥러닝 신경망을 사용하여 음성 잡음 제거하기

이 예제에서는 딥러닝 신경망을 사용하여 음성 신호의 잡음을 제거하는 방법을 다룹니다. 이 예제에서는 동일한 작업에 적용된 두 가지 유형의 신경망, 즉 완전 연결 신경망과 컨벌루션 신경망을 비교합니다.

소개

음성 잡음 제거의 목표는 음성 신호에서 잡음을 제거하는 한편 음성의 품질과 명료함을 향상하는 것입니다. 이 예제에서는 딥러닝 신경망을 사용하여 음성 신호에서 세탁기 잡음을 제거하는 방법을 보여줍니다. 이 예제에서는 동일한 작업에 적용된 두 가지 유형의 신경망, 즉 완전 연결 신경망과 컨벌루션 신경망을 비교합니다.

문제 개요

8kHz로 샘플링된 다음과 같은 음성 신호가 있다고 가정하겠습니다.

[cleanAudio,fs] = audioread("SpeechDFT-16-8-mono-5secs.wav");
sound(cleanAudio,fs)

음성 신호에 세탁기 잡음을 추가합니다. 신호 대 잡음비(SNR)가 0dB이 되도록 잡음 전력을 설정합니다.

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

% Extract a noise segment from a random location in the noise file
ind = randi(numel(noise) - numel(cleanAudio) + 1, 1, 1);
noiseSegment = noise(ind:ind + numel(cleanAudio) - 1);

speechPower = sum(cleanAudio.^2);
noisePower = sum(noiseSegment.^2);
noisyAudio = cleanAudio + sqrt(speechPower/noisePower) * noiseSegment;

잡음이 있는 음성 신호를 들어봅니다.

sound(noisyAudio,fs)

원래 신호와 잡음이 있는 신호를 시각화합니다.

t = (1/fs) * (0:numel(cleanAudio)-1);

subplot(2,1,1)
plot(t,cleanAudio)
title("Clean Audio")
grid on

subplot(2,1,2)
plot(t,noisyAudio)
title("Noisy Audio")
xlabel("Time (s)")
grid on

음성 잡음 제거의 목표는 음성 신호에서 세탁기 소음을 제거하는 한편 출력 신호에서 원치 않는 아티팩트를 최소화하는 것입니다.

데이터셋 검토하기

이 예제에서는 Mozilla Common Voice 데이터셋 [1]의 서브셋을 사용하여 딥러닝 신경망을 훈련시키고 테스트합니다. 데이터 세트에는 연구대상자가 말하는 짧은 문장을 48kHz로 녹음한 내용이 담겨 있습니다. 데이터 세트를 다운로드하고 다운로드한 파일의 압축을 풉니다.

url = 'http://ssd.mathworks.com/supportfiles/audio/commonvoice.zip';
downloadFolder = tempdir;
dataFolder = fullfile(downloadFolder,'commonvoice');

if ~exist(dataFolder,'dir')
    disp('Downloading data set (956 MB) ...')
    unzip(url,downloadFolder)
end

audioDatastore를 사용하여 훈련 세트를 위한 데이터저장소를 만듭니다. 성능을 희생하여 예제 런타임의 속도를 높이려면 reduceDatasettrue로 설정하십시오.

adsTrain = audioDatastore(fullfile(dataFolder,'train'),'IncludeSubfolders',true);

reduceDataset = true;
if reduceDataset
    adsTrain = shuffle(adsTrain);
    adsTrain = subset(adsTrain,1:1000);
end

read를 사용하여 데이터저장소의 첫 번째 파일의 내용을 가져옵니다.

[audio,adsTrainInfo] = read(adsTrain);

음성 신호를 들어봅니다.

sound(audio,adsTrainInfo.SampleRate)

음성 신호를 플로팅합니다.

figure
t = (1/adsTrainInfo.SampleRate) * (0:numel(audio)-1);
plot(t,audio)
title("Example Speech Signal")
xlabel("Time (s)")
grid on

딥러닝 시스템 개요

아래에는 기본적인 딥러닝 훈련 도식이 나와 있습니다. 음성은 일반적으로 4kHz보다 낮으므로 먼저 깨끗한 오디오 신호와 잡음이 있는 오디오 신호를 8kHz로 다운샘플링하여 신경망의 계산 부하를 줄일 것입니다. 예측 변수 및 목표 신경망 신호는 각각 잡음이 있는 오디오 신호와 깨끗한 오디오 신호의 크기 스펙트럼입니다. 신경망 출력값은 잡음이 제거된 신호의 크기 스펙트럼입니다. 회귀 신경망은 예측 변수 입력값을 사용하여 출력 목표와 입력 목표 사이의 평균 제곱 오차를 최소화합니다. 잡음이 제거된 오디오는 출력 크기 스펙트럼과 잡음이 있는 신호의 위상을 사용하여 시간 영역으로 다시 변환됩니다 [2].

윈도우 길이 256개 샘플, 중첩 75% 및 해밍 윈도우를 갖는 단시간 푸리에 변환(STFT)을 사용하여 오디오를 주파수 영역으로 변환합니다. 음수 주파수에 해당하는 주파수 샘플을 제외하여 스펙트럼 벡터의 크기를 129로 줄입니다(시간 영역 음성 신호는 실수이므로 이는 정보 손실로 이어지지 않음). 예측 변수 입력값은 8개의 잡음이 있는 연속된 STFT 벡터로 구성되어 있어 각 STFT 출력 추정값은 잡음이 있는 현재 STFT 1개와 잡음이 있는 이전 STFT 벡터 7개를 바탕으로 계산됩니다.

STFT 목표값과 예측 변수

이 섹션에서는 하나의 훈련 파일에서 목표 신호와 예측 변수 신호를 생성하는 방법을 보여줍니다.

먼저 시스템 파라미터를 정의합니다.

windowLength = 256;
win = hamming(windowLength,"periodic");
overlap = round(0.75 * windowLength);
ffTLength = windowLength;
inputFs = 48e3;
fs = 8e3;
numFeatures = ffTLength/2 + 1;
numSegments = 8;

dsp.SampleRateConverter (DSP System Toolbox) 객체를 만들어 48kHz 오디오를 8kHz로 변환합니다.

src = dsp.SampleRateConverter("InputSampleRate",inputFs, ...
                              "OutputSampleRate",fs, ...
                              "Bandwidth",7920);

read를 사용하여 데이터저장소에서 오디오 파일의 내용을 가져옵니다.

audio = read(adsTrain);

오디오 길이가 샘플 레이트 변환기 데시메이션 인자의 배수여야 합니다.

decimationFactor = inputFs/fs;
L = floor(numel(audio)/decimationFactor);
audio = audio(1:decimationFactor*L);

오디오 신호를 8kHz로 변환합니다.

audio = src(audio);
reset(src)

세탁기 잡음 벡터에서 무작위 잡음 세그먼트를 만듭니다.

randind = randi(numel(noise) - numel(audio),[1 1]);
noiseSegment = noise(randind : randind + numel(audio) - 1);

SNR이 0dB이 되도록 음성 신호에 잡음을 추가합니다.

noisePower = sum(noiseSegment.^2);
cleanPower = sum(audio.^2);
noiseSegment = noiseSegment .* sqrt(cleanPower/noisePower);
noisyAudio = audio + noiseSegment;

stft를 사용하여 원래 오디오 신호와 잡음이 있는 오디오 신호에서 크기 STFT 벡터를 생성합니다.

cleanSTFT = stft(audio,'Window',win,'OverlapLength',overlap,'FFTLength',ffTLength);
cleanSTFT = abs(cleanSTFT(numFeatures-1:end,:));
noisySTFT = stft(noisyAudio,'Window',win,'OverlapLength',overlap,'FFTLength',ffTLength);
noisySTFT = abs(noisySTFT(numFeatures-1:end,:));

잡음이 있는 STFT에서 8개 세그먼트 크기의 훈련 예측 변수 신호를 생성합니다. 연속된 예측 변수 간의 중첩은 7개 세그먼트입니다.

noisySTFT = [noisySTFT(:,1:numSegments - 1), noisySTFT];
stftSegments = zeros(numFeatures, numSegments , size(noisySTFT,2) - numSegments + 1);
for index = 1:size(noisySTFT,2) - numSegments + 1
    stftSegments(:,:,index) = (noisySTFT(:,index:index + numSegments - 1)); 
end

목표값과 예측 변수를 설정합니다. 두 변수의 마지막 차원은 오디오 파일에 의해 생성된 서로 다른 예측 변수/목표값 쌍의 개수에 해당합니다. 각 예측 변수는 129×8이고 각 목표값은 129×1입니다.

targets = cleanSTFT;
size(targets)
ans = 1×2

   129   544

predictors = stftSegments;
size(predictors)
ans = 1×3

   129     8   544

tall형 배열을 사용하여 특징 추출하기

처리 속도를 높이려면 tall형 배열을 사용하여 데이터저장소에 있는 모든 오디오 파일의 음성 세그먼트에서 특징 시퀀스를 추출하십시오. 메모리 내 배열과 달리 tall형 배열은 일반적으로 gather 함수를 호출하기 전까지 계산되지 않은 상태로 유지됩니다. 이처럼 계산을 보류하여 대규모 데이터 세트에 대해 빠르게 작업할 수 있습니다. 이후 gather를 사용하여 출력값을 요청하면 MATLAB은 가능한 경우 대기 중인 계산을 결합하여 데이터 통과 횟수를 최소화합니다. Parallel Computing Toolbox™가 있으면 로컬 MATLAB 세션에서 또는 로컬 병렬 풀에서 tall형 배열을 사용할 수 있습니다. MATLAB® Parallel Server™가 설치되어 있으면 클러스터에서도 tall형 배열 계산을 실행할 수 있습니다.

먼저 데이터저장소를 tall형 배열로 변환합니다.

reset(adsTrain)
T = tall(adsTrain)
Starting parallel pool (parpool) using the 'local' profile ...
Connected to the parallel pool (number of workers: 6).
T =

  M×1 tall cell array

    {234480×1 double}
    {210288×1 double}
    {282864×1 double}
    {292080×1 double}
    {410736×1 double}
    {303600×1 double}
    {326640×1 double}
    {233328×1 double}
        :        :
        :        :

위 화면을 보면 (데이터저장소에 있는 파일의 개수에 해당하는) 행 개수 값 M을 아직 알 수 없음을 알 수 있습니다. M은 계산이 완료되기 전까지 자리 표시자로 기능합니다.

tall형 테이블에서 목표값 및 예측 변수 크기 STFT를 추출합니다. 이 작업은 후속 계산에서 사용할 새로운 tall형 배열 변수를 만듭니다. 함수 HelperGenerateSpeechDenoisingFeaturesSTFT 목표값과 예측 변수 섹션에서 이미 살펴본 단계를 수행합니다. cellfun 명령은 데이터저장소에 있는 각 오디오 파일의 내용에 HelperGenerateSpeechDenoisingFeatures를 적용합니다.

[targets,predictors] = cellfun(@(x)HelperGenerateSpeechDenoisingFeatures(x,noise,src),T,"UniformOutput",false);

gather를 사용하여 목표값과 예측 변수를 계산합니다.

[targets,predictors] = gather(targets,predictors);
Evaluating tall expression using the Parallel Pool 'local':
- Pass 1 of 1: Completed in 42 sec
Evaluation completed in 1 min 36 sec

모든 특징을 평균 0, 표준편차 1로 정규화하는 것이 좋습니다.

예측 변수와 목표값의 평균과 표준편차를 각각 계산하고 이를 사용하여 데이터를 정규화합니다.

predictors = cat(3,predictors{:});
noisyMean = mean(predictors(:));
noisyStd = std(predictors(:));
predictors(:) = (predictors(:) - noisyMean)/noisyStd;

targets = cat(2,targets{:});
cleanMean = mean(targets(:));
cleanStd = std(targets(:));
targets(:) = (targets(:) - cleanMean)/cleanStd;

딥러닝 신경망에 필요한 차원으로 예측 변수와 목표값의 형태를 변경합니다.

predictors = reshape(predictors,size(predictors,1),size(predictors,2),1,size(predictors,3));
targets = reshape(targets,1,1,size(targets,1),size(targets,2));

훈련 중에 데이터의 1%를 검증용으로 사용할 것입니다. 검증은 신경망이 훈련 데이터에 과적합되는 경우를 발견하는 데 유용합니다.

데이터를 훈련 세트와 검증 세트로 무작위로 분할합니다.

inds = randperm(size(predictors,4));
L = round(0.99 * size(predictors,4));

trainPredictors = predictors(:,:,:,inds(1:L));
trainTargets = targets(:,:,:,inds(1:L));

validatePredictors = predictors(:,:,:,inds(L+1:end));
validateTargets = targets(:,:,:,inds(L+1:end));

완전 연결 계층을 사용한 음성 잡음 제거

먼저 완전 연결 계층으로 구성된 잡음 제거 신경망을 살펴보겠습니다. 완전 연결 계층의 각 뉴런은 이전 계층의 모든 활성화에 연결됩니다. 완전 연결 계층은 입력값에 가중치 행렬을 곱한 다음 편향 벡터를 더합니다. 가중치 행렬과 편향 벡터의 차원은 계층의 뉴런 개수와 이전 계층의 활성화 개수로 결정됩니다.

신경망의 계층을 정의합니다. 입력 크기를 NumFeatures×NumSegments(이 예제에서는 129×8)인 영상이 되도록 지정합니다. 각각 1024개의 뉴런을 갖는 2개의 은닉 완전 연결 계층을 정의합니다. 순수 선형 시스템이므로 각 은닉 완전 연결 계층 뒤에 ReLU(Rectified Linear Unit) 계층이 오도록 합니다. 배치 정규화 계층은 출력값의 평균과 표준편차를 정규화합니다. 129개의 뉴런을 갖는 완전 연결 계층을 추가하고 그 뒤에 회귀 계층을 추가합니다.

layers = [
    imageInputLayer([numFeatures,numSegments])
    fullyConnectedLayer(1024)
    batchNormalizationLayer
    reluLayer
    fullyConnectedLayer(1024)
    batchNormalizationLayer
    reluLayer
    fullyConnectedLayer(numFeatures)
    regressionLayer
    ];

다음으로, 신경망의 훈련 옵션을 지정합니다. 신경망이 훈련 데이터를 3번 통과하도록 MaxEpochs3으로 설정합니다. 신경망이 한 번에 128개의 훈련 신호를 살펴보도록 MiniBatchSize128로 설정합니다. Plots"training-progress"로 지정하여 반복 횟수가 늘어남에 따라 훈련 진행 상황을 표시하는 플롯을 생성합니다. Verbosefalse로 설정하여 플롯에 표시되는 데이터에 대응되는 표의 형태로 출력값이 명령줄 창에 출력되지 않도록 합니다. Shuffle"every-epoch"로 지정하여 각 Epoch의 시작 시점에 훈련 시퀀스를 섞습니다. LearnRateSchedule"piecewise"로 지정하여 특정 횟수의 Epoch(1)를 통과할 때마다 지정된 인수(0.9)만큼 학습률을 줄입니다. ValidationData를 검증 예측 변수와 목표값으로 설정합니다. Epoch 1회마다 한 번씩 검증 평균 제곱 오차가 계산되도록 ValidationFrequency를 설정합니다. 이 예제에서는 ADAM(적응적 모멘트 추정) 솔버를 사용합니다.

miniBatchSize = 128;
options = trainingOptions("adam", ...
    "MaxEpochs",3, ...
    "InitialLearnRate",1e-5,...
    "MiniBatchSize",miniBatchSize, ...
    "Shuffle","every-epoch", ...
    "Plots","training-progress", ...
    "Verbose",false, ...
    "ValidationFrequency",floor(size(trainPredictors,4)/miniBatchSize), ...
    "LearnRateSchedule","piecewise", ...
    "LearnRateDropFactor",0.9, ...
    "LearnRateDropPeriod",1, ...
    "ValidationData",{validatePredictors,validateTargets});

trainNetwork를 사용하여 지정된 훈련 옵션과 계층 아키텍처로 신경망을 훈련시킵니다. 훈련 세트가 크기 때문에 훈련 과정에 몇 분 정도 걸릴 수 있습니다. 신경망을 처음부터 훈련시키는 대신 사전 훈련된 신경망을 다운로드하고 불러오려면 doTrainingfalse로 설정하십시오.

doTraining = true;
if doTraining
    denoiseNetFullyConnected = trainNetwork(trainPredictors,trainTargets,layers,options);
else
    url = 'http://ssd.mathworks.com/supportfiles/audio/SpeechDenoising.zip';
    downloadNetFolder = tempdir;
    netFolder = fullfile(downloadNetFolder,'SpeechDenoising');
    if ~exist(netFolder,'dir')
        disp('Downloading pretrained network (1 file - 8 MB) ...')
        unzip(url,downloadNetFolder)
    end
    s = load(fullfile(netFolder,"denoisenet.mat"));
    denoiseNetFullyConnected = s.denoiseNetFullyConnected;
    cleanMean = s.cleanMean;
    cleanStd = s.cleanStd;
    noisyMean = s.noisyMean;
    noisyStd = s.noisyStd;
end

신경망의 완전 연결 계층의 가중치 개수를 계산합니다.

numWeights = 0;
for index = 1:numel(denoiseNetFullyConnected.Layers)
    if isa(denoiseNetFullyConnected.Layers(index),"nnet.cnn.layer.FullyConnectedLayer")
        numWeights = numWeights + numel(denoiseNetFullyConnected.Layers(index).Weights);
    end
end
fprintf("The number of weights is %d.\n",numWeights);
The number of weights is 2237440.

컨벌루션 계층을 사용한 음성 잡음 제거

완전 연결 계층 대신 컨벌루션 계층을 사용하는 신경망을 살펴보겠습니다 [3]. 2차원 컨벌루션 계층은 입력값에 슬라이딩 필터를 적용합니다. 이 계층은 입력값의 세로와 가로 방향을 따라 필터를 이동하면서 가중치와 입력값의 내적을 계산한 다음 편향 항을 추가하여 입력값을 컨벌루션합니다. 일반적으로 컨벌루션 계층은 완전 연결 계층보다 파라미터 개수가 적습니다.

[3]에서 설명된 16개의 컨벌루션 계층으로 구성된 완전 컨벌루션 신경망의 계층을 정의합니다. 처음 15개의 컨벌루션 계층은 필터 너비가 각각 9, 5, 9이고 필터 개수가 각각 18, 30, 8인 3개 계층으로 구성된 그룹이 5번 반복된 것입니다. 마지막 컨벌루션 계층에는 필터 너비가 129인 필터가 1개 있습니다. 이 신경망에서 컨벌루션은 (주파수 차원을 따라) 한 방향으로만 수행되고, 시간 차원의 필터 너비는 첫 번째 계층을 제외한 모든 계층에 대해 1로 설정됩니다. 완전 연결 신경망과 마찬가지로, 컨벌루션 계층 뒤에는 ReLu 계층과 배치 정규화 계층이 옵니다.

layers = [imageInputLayer([numFeatures,numSegments])
          convolution2dLayer([9 8],18,"Stride",[1 100],"Padding","same")
          batchNormalizationLayer
          reluLayer
          
          repmat( ...
          [convolution2dLayer([5 1],30,"Stride",[1 100],"Padding","same")
          batchNormalizationLayer
          reluLayer
          convolution2dLayer([9 1],8,"Stride",[1 100],"Padding","same")
          batchNormalizationLayer
          reluLayer
          convolution2dLayer([9 1],18,"Stride",[1 100],"Padding","same")
          batchNormalizationLayer
          reluLayer],4,1)
          
          convolution2dLayer([5 1],30,"Stride",[1 100],"Padding","same")
          batchNormalizationLayer
          reluLayer
          convolution2dLayer([9 1],8,"Stride",[1 100],"Padding","same")
          batchNormalizationLayer
          reluLayer
          
          convolution2dLayer([129 1],1,"Stride",[1 100],"Padding","same")
          
          regressionLayer
          ];

훈련 옵션은 검증 목표 신호의 차원이 회귀 계층에 필요한 차원과 같아지도록 치환된다는 점을 제외하면 완전 연결 신경망의 옵션과 동일합니다.

options = trainingOptions("adam", ...
    "MaxEpochs",3, ...
    "InitialLearnRate",1e-5, ...
    "MiniBatchSize",miniBatchSize, ...
    "Shuffle","every-epoch", ...
    "Plots","training-progress", ...
    "Verbose",false, ...
    "ValidationFrequency",floor(size(trainPredictors,4)/miniBatchSize), ...
    "LearnRateSchedule","piecewise", ...
    "LearnRateDropFactor",0.9, ...
    "LearnRateDropPeriod",1, ...
    "ValidationData",{validatePredictors,permute(validateTargets,[3 1 2 4])});

trainNetwork를 사용하여 지정된 훈련 옵션과 계층 아키텍처로 신경망을 훈련시킵니다. 훈련 세트가 크기 때문에 훈련 과정에 몇 분 정도 걸릴 수 있습니다. 신경망을 처음부터 훈련시키는 대신 사전 훈련된 신경망을 다운로드하고 불러오려면 doTrainingfalse로 설정하십시오.

doTraining = true;
if doTraining
    denoiseNetFullyConvolutional = trainNetwork(trainPredictors,permute(trainTargets,[3 1 2 4]),layers,options);
else
    url = 'http://ssd.mathworks.com/supportfiles/audio/SpeechDenoising.zip';
    downloadNetFolder = tempdir;
    netFolder = fullfile(downloadNetFolder,'SpeechDenoising');
    if ~exist(netFolder,'dir')
        disp('Downloading pretrained network (1 file - 8 MB) ...')
        unzip(url,downloadNetFolder)
    end
    s = load(fullfile(netFolder,"denoisenet.mat"));
    denoiseNetFullyConvolutional = s.denoiseNetFullyConvolutional;
    cleanMean = s.cleanMean;
    cleanStd = s.cleanStd;
    noisyMean = s.noisyMean;
    noisyStd = s.noisyStd;
end

신경망의 완전 연결 계층의 가중치 개수를 계산합니다.

numWeights = 0;
for index = 1:numel(denoiseNetFullyConvolutional.Layers)
    if isa(denoiseNetFullyConvolutional.Layers(index),"nnet.cnn.layer.Convolution2DLayer")
        numWeights = numWeights + numel(denoiseNetFullyConvolutional.Layers(index).Weights);
    end
end
fprintf("The number of weights in convolutional layers is %d\n",numWeights);
The number of weights in convolutional layers is 31812

잡음 제거 신경망 테스트하기

테스트 데이터 세트를 읽어 들입니다.

adsTest = audioDatastore(fullfile(dataFolder,'test'),'IncludeSubfolders',true);

데이터저장소에서 파일의 내용을 읽어 들입니다.

[cleanAudio,adsTestInfo] = read(adsTest);

오디오 길이가 샘플 레이트 변환기 데시메이션 인자의 배수여야 합니다.

L = floor(numel(cleanAudio)/decimationFactor);
cleanAudio = cleanAudio(1:decimationFactor*L);

오디오 신호를 8kHz로 변환합니다.

cleanAudio = src(cleanAudio);
reset(src)

이 테스트 단계에서는 훈련 단계에서 사용되지 않는 세탁기 잡음으로 음성을 손상시킵니다.

noise = audioread("WashingMachine-16-8-mono-200secs.mp3");

세탁기 잡음 벡터에서 무작위 잡음 세그먼트를 만듭니다.

randind = randi(numel(noise) - numel(cleanAudio), [1 1]);
noiseSegment = noise(randind : randind + numel(cleanAudio) - 1);

SNR이 0dB이 되도록 음성 신호에 잡음을 추가합니다.

noisePower = sum(noiseSegment.^2);
cleanPower = sum(cleanAudio.^2);
noiseSegment = noiseSegment .* sqrt(cleanPower/noisePower);
noisyAudio = cleanAudio + noiseSegment;

stft를 사용하여 잡음이 있는 오디오 신호에서 크기 STFT 벡터를 생성합니다.

noisySTFT = stft(noisyAudio,'Window',win,'OverlapLength',overlap,'FFTLength',ffTLength);
noisyPhase = angle(noisySTFT(numFeatures-1:end,:));
noisySTFT = abs(noisySTFT(numFeatures-1:end,:));

잡음이 있는 STFT에서 8개 세그먼트 크기의 훈련 예측 변수 신호를 생성합니다. 연속된 예측 변수 간의 중첩은 7개 세그먼트입니다.

noisySTFT = [noisySTFT(:,1:numSegments-1) noisySTFT];
predictors = zeros( numFeatures, numSegments , size(noisySTFT,2) - numSegments + 1);
for index = 1:(size(noisySTFT,2) - numSegments + 1)
    predictors(:,:,index) = noisySTFT(:,index:index + numSegments - 1); 
end

훈련 단계에서 계산한 평균과 표준편차를 사용하여 예측 변수를 정규화합니다.

predictors(:) = (predictors(:) - noisyMean) / noisyStd;

2개의 훈련된 신경망으로 predict를 사용하여 잡음이 제거된 크기 STFT를 계산합니다.

predictors = reshape(predictors, [numFeatures,numSegments,1,size(predictors,3)]);
STFTFullyConnected = predict(denoiseNetFullyConnected, predictors);
STFTFullyConvolutional = predict(denoiseNetFullyConvolutional, predictors);

훈련 단계에서 사용한 평균과 표준편차로 출력값을 스케일링합니다.

STFTFullyConnected(:) = cleanStd * STFTFullyConnected(:) + cleanMean;
STFTFullyConvolutional(:) = cleanStd * STFTFullyConvolutional(:) + cleanMean;

단측 STFT를 중심이 맞춰진 STFT로 변환합니다.

STFTFullyConnected = STFTFullyConnected.' .* exp(1j*noisyPhase);
STFTFullyConnected = [conj(STFTFullyConnected(end-1:-1:2,:)); STFTFullyConnected];
STFTFullyConvolutional = squeeze(STFTFullyConvolutional) .* exp(1j*noisyPhase);
STFTFullyConvolutional = [conj(STFTFullyConvolutional(end-1:-1:2,:)) ; STFTFullyConvolutional];

잡음이 제거된 음성 신호를 계산합니다. istft가 역 STFT를 수행합니다. 잡음이 있는 STFT 벡터의 위상을 사용하여 시간 영역 신호를 재생성합니다.

denoisedAudioFullyConnected = istft(STFTFullyConnected,  ...
                                    'Window',win,'OverlapLength',overlap, ...
                                    'FFTLength',ffTLength,'ConjugateSymmetric',true);
                                
denoisedAudioFullyConvolutional = istft(STFTFullyConvolutional,  ...
                                        'Window',win,'OverlapLength',overlap, ...
                                        'FFTLength',ffTLength,'ConjugateSymmetric',true);

깨끗한 오디오 신호, 잡음이 있는 오디오 신호 및 잡음이 제거된 오디오 신호를 플로팅합니다.

t = (1/fs) * (0:numel(denoisedAudioFullyConnected)-1);

figure

subplot(4,1,1)
plot(t,cleanAudio(1:numel(denoisedAudioFullyConnected)))
title("Clean Speech")
grid on

subplot(4,1,2)
plot(t,noisyAudio(1:numel(denoisedAudioFullyConnected)))
title("Noisy Speech")
grid on

subplot(4,1,3)
plot(t,denoisedAudioFullyConnected)
title("Denoised Speech (Fully Connected Layers)")
grid on

subplot(4,1,4)
plot(t,denoisedAudioFullyConvolutional)
title("Denoised Speech (Convolutional Layers)")
grid on
xlabel("Time (s)")

깨끗한 스펙트로그램, 잡음이 있는 스펙트로그램 및 잡음이 제거된 스펙트로그램을 플로팅합니다.

h = figure;

subplot(4,1,1)
spectrogram(cleanAudio,win,overlap,ffTLength,fs);
title("Clean Speech")
grid on

subplot(4,1,2)
spectrogram(noisyAudio,win,overlap,ffTLength,fs);
title("Noisy Speech")
grid on

subplot(4,1,3)
spectrogram(denoisedAudioFullyConnected,win,overlap,ffTLength,fs);
title("Denoised Speech (Fully Connected Layers)")
grid on

subplot(4,1,4)
spectrogram(denoisedAudioFullyConvolutional,win,overlap,ffTLength,fs);
title("Denoised Speech (Convolutional Layers)")
grid on

p = get(h,'Position');
set(h,'Position',[p(1) 65 p(3) 800]);

잡음이 있는 음성을 들어봅니다.

sound(noisyAudio,fs)

완전 연결 계층을 갖는 신경망에서 잡음이 제거된 음성을 들어봅니다.

sound(denoisedAudioFullyConnected,fs)

컨벌루션 계층을 갖는 신경망에서 잡음이 제거된 음성을 들어봅니다.

sound(denoisedAudioFullyConvolutional,fs)

깨끗한 음성을 들어봅니다.

sound(cleanAudio,fs)

testDenoisingNets를 호출하여 데이터저장소의 더 많은 파일을 테스트할 수 있습니다. 이 함수는 위에서 살펴본 시간 영역 플롯과 주파수 영역 플롯을 생성하고, 깨끗한 오디오 신호, 잡음이 있는 오디오 신호 및 잡음이 제거된 오디오 신호도 반환합니다.

[cleanAudio,noisyAudio,denoisedAudioFullyConnected,denoisedAudioFullyConvolutional] = testDenoisingNets(adsTest,denoiseNetFullyConnected,denoiseNetFullyConvolutional,noisyMean,noisyStd,cleanMean,cleanStd);

실시간 활용 사례

이전 섹션의 절차에서는 잡음이 있는 신호의 전체 스펙트럼을 predict로 전달했습니다. 이는 낮은 대기 시간이 요구되는 실시간 활용 사례에는 적합하지 않습니다.

잡음 제거 신경망의 실시간 스트리밍 버전을 시뮬레이션하는 방법을 보여주는 예제는 speechDenoisingRealtimeApp을 실행하십시오. 이 앱은 완전 연결 계층이 있는 신경망을 사용합니다. 오디오 프레임 길이는 STFT의 건너 뛰는 크기, 즉 0.25 * 256 = 64개 샘플과 동일합니다.

speechDenoisingRealtimeApp은 시뮬레이션과 상호 작용하도록 설계된 사용자 인터페이스(UI)를 실행합니다. 이 UI를 사용하여 파라미터를 조정할 수 있으며, 결과는 즉시 시뮬레이션에 반영됩니다. 잡음이 제거된 출력값에 작용하는 잡음 게이트를 활성화/비활성화하여 잡음을 더 줄이고 잡음 게이트의 어택 시간, 해제 시간 및 임계값을 조정할 수도 있습니다. UI에서 깨끗한 오디오, 잡음이 없는 오디오 또는 잡음이 제거된 오디오를 들어볼 수 있습니다.

스코프는 깨끗한 신호, 잡음이 있는 신호 및 잡음이 제거된 신호와 잡음 게이트의 이득을 플로팅합니다.

참고 문헌

[1] https://voice.mozilla.org/en

[2] "Experiments on Deep Learning for Speech Denoising", Ding Liu, Paris Smaragdis, Minje Kim, INTERSPEECH, 2014.

[3] "A Fully Convolutional Neural Network for Speech Enhancement", Se Rim Park, Jin Won Lee, INTERSPEECH, 2017.