딥러닝을 사용해 음성 명령 인식하기

이 예제에서는 오디오에서 음성 명령의 존재 여부를 감지하는 딥러닝 모델을 훈련시키는 방법을 보여줍니다. 이 예제에서는 Speech Commands Dataset을 사용하여 컨벌루션 신경망이 주어진 명령 세트를 인식하도록 훈련시킵니다[1].

신경망을 처음부터 훈련시키려면 먼저 데이터 세트를 다운로드해야 합니다. 데이터 세트 다운로드나 신경망 훈련을 원치 않는 경우에는 이 예제와 함께 제공된 사전 훈련된 신경망을 불러온 후 이 예제의 다음 두 섹션 사전 훈련된 신경망을 사용하여 명령 인식하기마이크의 스트리밍 오디오를 사용하여 명령 감지하기를 실행할 수 있습니다.

사전 훈련된 신경망을 사용하여 명령 인식하기

훈련 과정을 자세히 살펴보기 전에 사전 훈련된 음성 인식 신경망을 사용하여 음성 명령을 식별해 보겠습니다.

사전 훈련된 신경망을 불러옵니다.

load('commandNet.mat')

이 신경망은 다음 음성 명령을 인식하도록 훈련되어 있습니다.

  • "yes"

  • "no"

  • "up"

  • "down"

  • "left"

  • "right"

  • "on"

  • "off"

  • "stop"

  • "go"

한 사람이 "stop"이라고 말하는 짧은 음성 신호를 불러옵니다.

 [x,fs] = audioread('stop_command.flac');

명령을 들어봅니다.

 sound(x,fs)

이 사전 훈련된 신경망은 청각 기반 스펙트로그램을 입력값으로 받습니다. 먼저, 음성 파형을 청각 기반 스펙트로그램으로 변환해 보겠습니다.

함수 extractAuditoryFeature를 사용하여 청각 스펙트로그램을 계산합니다. 특징 추출에 대해서는 이 예제의 뒷부분에서 자세히 살펴봅니다.

auditorySpect = helperExtractAuditoryFeatures(x,fs);

청각 스펙트로그램을 기반으로 명령을 분류합니다.

command = classify(trainedNet,auditorySpect)
command = 

  categorical

     stop 

이 신경망은 이 세트에 속하지 않는 단어를 "unknown"으로 분류하도록 훈련되어 있습니다.

이제, 식별할 명령 목록에 포함되지 않은 단어("play")를 분류해 보겠습니다.

음성 신호를 불러와서 들어봅니다.

x = audioread('play_command.flac');
sound(x,fs)

청각 스펙트로그램을 계산합니다.

auditorySpect = helperExtractAuditoryFeatures(x,fs);

신호를 분류합니다.

command = classify(trainedNet,auditorySpect)
command = 

  categorical

     unknown 

이 신경망은 배경 잡음을 "background"로 분류하도록 훈련되어 있습니다.

랜덤 잡음으로 구성된 1초 길이의 신호를 만듭니다.

x = 0.01 * randn(16e3,1);

청각 스펙트로그램을 계산합니다.

auditorySpect = helperExtractAuditoryFeatures(x,fs);

배경 잡음을 분류합니다.

command = classify(trainedNet,auditorySpect)
command = 

  categorical

     background 

마이크의 스트리밍 오디오를 사용하여 명령 감지하기

사전 훈련된 명령 감지 신경망을 마이크의 스트리밍 오디오에 대해 테스트합니다. yes, no, stop과 같은 명령을 말해 봅니다. 그런 다음 Marvin, Sheila, bed, house, cat, bird 또는 0과 9 사이의 임의의 숫자와 같은 알려지지 않은 단어를 말해 봅니다.

분류 속도를 Hz 단위로 지정하고 마이크의 오디오를 읽을 수 있는 오디오 장치 리더를 만듭니다.

classificationRate = 20;
adr = audioDeviceReader('SampleRate',fs,'SamplesPerFrame',floor(fs/classificationRate));

오디오를 위한 버퍼를 초기화합니다. 신경망의 분류 레이블을 추출합니다. 스트리밍 오디오의 레이블 및 분류 확률을 위해 0.5초 분량의 버퍼를 초기화합니다. 초기화한 버퍼를 사용하여 보다 긴 시간에 걸쳐 분류 결과를 비교하고, 이를 사용하여 명령이 감지된 때를 대상으로 '일치하는' 부분을 구축합니다. 결정 논리에 대한 임계값을 지정합니다.

audioBuffer = dsp.AsyncBuffer(fs);

labels = trainedNet.Layers(end).Classes;
YBuffer(1:classificationRate/2) = categorical("background");

probBuffer = zeros([numel(labels),classificationRate/2]);

countThreshold = ceil(classificationRate*0.2);
probThreshold = 0.7;

Figure를 만들고, 이 Figure가 존재하는 한 계속해서 명령을 감지합니다. 루프를 무한정으로 실행하려면 timeLimitInf로 설정하십시오. 라이브 감지를 중단하려면 Figure를 닫으십시오.

h = figure('Units','normalized','Position',[0.2 0.1 0.6 0.8]);

timeLimit = 20;

tic;
while ishandle(h) && toc < timeLimit

    % Extract audio samples from the audio device and add the samples to
    % the buffer.
    x = adr();
    write(audioBuffer,x);
    y = read(audioBuffer,fs,fs-adr.SamplesPerFrame);

    spec = helperExtractAuditoryFeatures(y,fs);

    % 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 = [YBuffer(2:end),YPredicted];
    probBuffer = [probBuffer(:,2:end),probs(:)];

    % Plot the current waveform and spectrogram.
    subplot(2,1,1)
    plot(y)
    axis tight
    ylim([-1,1])

    subplot(2,1,2)
    pcolor(spec')
    caxis([-4 2.6445])
    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 probability of the predicted label is at
    % least probThreshold. Otherwise, do not declare a detection.
    [YMode,count] = mode(YBuffer);

    maxProb = max(probBuffer(labels == YMode,:));
    subplot(2,1,1)
    if YMode == "background" || count < countThreshold || maxProb < probThreshold
        title(" ")
    else
        title(string(YMode),'FontSize',20)
    end

    drawnow
end

음성 명령 데이터 세트 불러오기

데이터 세트 [1]를 다운로드하여 추출합니다.

url = 'https://storage.googleapis.com/download.tensorflow.org/data/speech_commands_v0.01.tar.gz';

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

if ~exist(datasetFolder,'dir')
    disp('Downloading speech commands data set (1.5 GB)...')
    untar(url,datasetFolder)
end

이 데이터 세트를 가리키는 audioDatastore (Audio Toolbox)를 만듭니다.

ads = audioDatastore(datasetFolder, ...
    'IncludeSubfolders',true, ...
    'FileExtensions','.wav', ...
    'LabelSource','foldernames')
ads = 

  audioDatastore with properties:

                       Files: {
                              ' ...\Local\Temp\google_speech\_background_noise_\doing_the_dishes.wav';
                              ' ...\AppData\Local\Temp\google_speech\_background_noise_\dude_miaowing.wav';
                              ' ...\AppData\Local\Temp\google_speech\_background_noise_\exercise_bike.wav'
                               ... and 64724 more
                              }
                     Folders: {
                              'C:\Users\bhemmat\AppData\Local\Temp\google_speech'
                              }
                      Labels: [_background_noise_; _background_noise_; _background_noise_ ... and 64724 more categorical]
    AlternateFileSystemRoots: {}
              OutputDataType: 'double'
      SupportedOutputFormats: ["wav"    "flac"    "ogg"    "mp4"    "m4a"]
         DefaultOutputFormat: "wav"

인식할 단어 선택하기

모델이 명령으로 인식해야 할 단어를 사용자가 지정해 줍니다. 명령이 아닌 단어는 모두 unknown으로 지정합니다. 명령이 아닌 단어를 unknown으로 지정하면 명령을 제외한 모든 단어의 분포에 근접한 단어 그룹이 만들어집니다. 신경망은 이 그룹을 사용하여 명령과 명령이 아닌 단어의 차이를 학습합니다.

알려진 단어와 알려지지 않은 단어 사이의 클래스 불균형을 줄이고 처리 속도를 높이려면 알려지지 않은 단어들의 일부만 훈련 세트에 포함하십시오. 배경 잡음이 있는 긴 파일은 훈련 세트에 포함하지 마십시오. 배경 잡음은 나중에 별도의 단계에서 추가하게 됩니다.

subset (Audio Toolbox)을 사용하여 명령과 알려지지 않은 단어의 서브셋만 포함하는 데이터저장소를 만듭니다. 각 범주에 속하는 표본의 개수를 셉니다.

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");

adsSubset = subset(ads,isCommand|isUnknown);
countEachLabel(adsSubset)
ans =

  11×2 table

     Label     Count
    _______    _____

    down       2359 
    go         2372 
    left       2353 
    no         2375 
    off        2357 
    on         2367 
    right      2367 
    stop       2380 
    unknown    8186 
    up         2375 
    yes        2377 

데이터를 훈련 세트, 검증 세트와 테스트 세트로 분할하기

데이터 세트 폴더에는 검증 세트와 테스트 세트로 사용할 오디오 파일이 나열된 텍스트 파일이 포함되어 있습니다. 이러한 미리 정의된 검증 세트와 테스트 세트에는 동일한 사람이 발화한 동일한 단어가 포함되어 있지 않으므로, 전체 데이터 세트에서 서브셋을 임의로 선택하는 것보다 이러한 미리 정의된 세트를 사용하는 것이 좋습니다.

이 예제에서는 단일 신경망을 훈련시키기 때문에 테스트 세트가 아닌 검증 세트만 사용하여 훈련된 모델을 평가합니다. 여러 신경망을 훈련시킨 후 검증 정확도가 가장 높은 신경망을 최종 신경망으로 선택하는 경우 테스트 세트를 사용하여 최종 신경망을 평가할 수 있습니다.

검증 파일의 목록을 읽어 들입니다.

c = importdata(fullfile(datasetFolder,'validation_list.txt'));
filesValidation = string(c);

테스트 파일의 목록을 읽어 들입니다.

c = importdata(fullfile(datasetFolder,'testing_list.txt'));
filesTest = string(c);

데이터저장소에서 어느 파일을 검증 세트로 보내고 어느 파일을 테스트 세트로 보낼지 결정합니다.

files = adsSubset.Files;
sf = split(files,filesep);
isValidation = ismember(sf(:,end-1) + "/" + sf(:,end),filesValidation);
isTest = ismember(sf(:,end-1) + "/" + sf(:,end),filesTest);

adsValidation = subset(adsSubset,isValidation);
adsTrain = subset(adsSubset,~isValidation & ~isTest);

전체 데이터셋으로 신경망을 훈련시키고 가능한 가장 높은 정확도를 얻으려면 reduceDatasetfalse로 설정하십시오. 이 예제를 빠르게 실행하려면 reduceDatasettrue로 설정하십시오.

reduceDataset = false;
if reduceDataset
    numUniqueLabels = numel(unique(adsTrain.Labels));
    % Reduce the dataset by a factor of 20
    adsTrain = splitEachLabel(adsTrain,round(numel(adsTrain.Files) / numUniqueLabels / 20));
    adsValidation = splitEachLabel(adsValidation,round(numel(adsValidation.Files) / numUniqueLabels / 20));
end

청각 스펙트로그램 계산하기

컨벌루션 신경망의 효율적인 훈련을 위해 데이터를 준비하려면 음성 파형을 청각 기반 스펙트로그램으로 변환하십시오.

특징 추출의 파라미터를 정의합니다. segmentDuration은 각 음성 클립의 지속 시간(단위: 초)입니다. frameDuration은 스펙트럼 계산을 위한 각 프레임의 지속 시간입니다. hopDuration은 각 스펙트럼 사이의 시간 스텝입니다. numBands는 청각 스펙트로그램의 필터 개수입니다.

특징 추출을 수행할 audioFeatureExtractor (Audio Toolbox) 객체를 만듭니다.

fs = 16e3; % Known sample rate of the data set.

segmentDuration = 1;
frameDuration = 0.025;
hopDuration = 0.010;

segmentSamples = round(segmentDuration*fs);
frameSamples = round(frameDuration*fs);
hopSamples = round(hopDuration*fs);
overlapSamples = frameSamples - hopSamples;

FFTLength = 512;
numBands = 50;

afe = audioFeatureExtractor( ...
    'SampleRate',fs, ...
    'FFTLength',FFTLength, ...
    'Window',hann(frameSamples,'periodic'), ...
    'OverlapLength',overlapSamples, ...
    'barkSpectrum',true);
setExtractorParams(afe,'barkSpectrum','NumBands',numBands);

데이터셋에서 파일을 읽어 들입니다. 컨벌루션 신경망을 훈련시키려면 입력값의 크기가 일정해야 합니다. 이 데이터 세트의 일부 파일은 길이가 1초보다 짧습니다. 길이가 segmentSamples가 되도록 오디오 신호의 앞뒤에 0 채우기를 적용합니다.

x = read(adsTrain);

numSamples = size(x,1);

numToPadFront = floor( (segmentSamples - numSamples)/2 );
numToPadBack = ceil( (segmentSamples - numSamples)/2 );

xPadded = [zeros(numToPadFront,1,'like',x);x;zeros(numToPadBack,1,'like',x)];

오디오 특징을 추출하려면 extract를 호출하십시오. 출력값은 각 행을 따라 시간값이 있는 바크 스펙트럼입니다.

features = extract(afe,xPadded);
[numHops,numFeatures] = size(features)
numHops =

    98


numFeatures =

    50

audioFeatureExtractor는 측정값이 윈도우 유형과 윈도우 적용 길이에 독립적이 되도록 청각 스펙트로그램을 윈도우의 거듭제곱으로 정규화합니다. 이 예제에서는 로그를 적용하여 청각 스펙트로그램을 사후 처리합니다. 작은 수의 로그를 구하면 반올림 오차가 발생할 수 있습니다. 반올림 오차를 방지하기 위해 윈도우 정규화를 역으로 적용합니다.

적용할 역정규화 인자를 결정합니다.

unNorm = 2/(sum(afe.Window)^2);

처리 속도를 높이려면 parfor를 사용하여 여러 워커 간에 특징 추출을 분산할 수 있습니다.

먼저, 데이터셋의 파티션 개수를 결정합니다. Parallel Computing Toolbox™가 설치되어 있지 않으면 단일 파티션을 사용하십시오.

if ~isempty(ver('parallel')) && ~reduceDataset
    pool = gcp;
    numPar = numpartitions(adsTrain,pool);
else
    numPar = 1;
end

각 파티션에 대해, 데이터저장소에서 데이터를 읽어 들이고 신호를 0으로 채운 후 특징을 추출합니다.

parfor ii = 1:numPar
    subds = partition(adsTrain,numPar,ii);
    XTrain = zeros(numHops,numBands,1,numel(subds.Files));
    for idx = 1:numel(subds.Files)
        x = read(subds);
        xPadded = [zeros(floor((segmentSamples-size(x,1))/2),1);x;zeros(ceil((segmentSamples-size(x,1))/2),1)];
        XTrain(:,:,:,idx) = extract(afe,xPadded);
    end
    XTrainC{ii} = XTrain;
end

출력값을 4번째 차원을 따라 청각 스펙트로그램이 있는 4차원 배열로 변환합니다.

XTrain = cat(4,XTrainC{:});

[numHops,numBands,numChannels,numSpec] = size(XTrain)
numHops =

    98


numBands =

    50


numChannels =

     1


numSpec =

       25041

윈도우의 거듭제곱으로 특징을 스케일링한 후 로그를 구합니다. 보다 매끄러운 분포를 갖는 데이터를 얻으려면 작은 오프셋을 사용하여 스펙트로그램의 로그를 구하십시오.

XTrain = XTrain/unNorm;
epsil = 1e-6;
XTrain = log10(XTrain + epsil);

검증 세트에 대해 위에서 설명한 특징 추출 단계를 수행합니다.

if ~isempty(ver('parallel'))
    pool = gcp;
    numPar = numpartitions(adsValidation,pool);
else
    numPar = 1;
end
parfor ii = 1:numPar
    subds = partition(adsValidation,numPar,ii);
    XValidation = zeros(numHops,numBands,1,numel(subds.Files));
    for idx = 1:numel(subds.Files)
        x = read(subds);
        xPadded = [zeros(floor((segmentSamples-size(x,1))/2),1);x;zeros(ceil((segmentSamples-size(x,1))/2),1)];
        XValidation(:,:,:,idx) = extract(afe,xPadded);
    end
    XValidationC{ii} = XValidation;
end
XValidation = cat(4,XValidationC{:});
XValidation = XValidation/unNorm;
XValidation = log10(XValidation + epsil);

훈련 레이블과 검증 레이블을 분리합니다. 비어 있는 범주를 제거합니다.

YTrain = removecats(adsTrain.Labels);
YValidation = removecats(adsValidation.Labels);

데이터 시각화하기

몇몇 훈련 샘플의 파형과 청각 스펙트로그램을 플로팅합니다. 대응하는 오디오 클립을 재생합니다.

specMin = min(XTrain,[],'all');
specMax = max(XTrain,[],'all');
idx = randperm(numel(adsTrain.Files),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 specMax])
    shading flat

    sound(x,fs)
    pause(2)
end

배경 잡음 데이터 추가하기

신경망은 발화된 다양한 단어를 인식할 수 있어야 할 뿐 아니라 입력값에 무음이 포함되어 있는지 배경 잡음이 포함되어 있는지 감지할 수도 있어야 합니다.

_background_noise_ 폴더에 있는 오디오 파일을 사용하여 1초 길이의 배경 잡음 샘플 클립을 만듭니다. 각 배경 잡음 파일로부터 동일한 개수의 배경 클립을 만듭니다. 배경 잡음을 직접 녹음한 다음 _background_noise_ 폴더에 추가해도 됩니다. 이 함수는 스펙트로그램을 계산하기 전에 먼저 volumeRange로 지정된 범위에 있는 로그 균등 분포에서 샘플링한 인자로 각 오디오 클립을 다시 스케일링합니다.

adsBkg = subset(ads,ads.Labels=="_background_noise_");
 numBkgClips = 4000;
if reduceDataset
    numBkgClips = numBkgClips/20;
end
volumeRange = log10([1e-4,1]);

numBkgFiles = numel(adsBkg.Files);
numClipsPerFile = histcounts(1:numBkgClips,linspace(1,numBkgClips,numBkgFiles+1));
Xbkg = zeros(size(XTrain,1),size(XTrain,2),1,numBkgClips,'single');
bkgAll = readall(adsBkg);
ind = 1;

for count = 1:numBkgFiles
    bkg = bkgAll{count};
    idxStart = randi(numel(bkg)-fs,numClipsPerFile(count),1);
    idxEnd = idxStart+fs-1;
    gain = 10.^((volumeRange(2)-volumeRange(1))*rand(numClipsPerFile(count),1) + volumeRange(1));
    for j = 1:numClipsPerFile(count)

        x = bkg(idxStart(j):idxEnd(j))*gain(j);

        x = max(min(x,1),-1);

        Xbkg(:,:,:,ind) = extract(afe,x);

        if mod(ind,1000)==0
            disp("Processed " + string(ind) + " background clips out of " + string(numBkgClips))
        end
        ind = ind + 1;
    end
end
Xbkg = Xbkg/unNorm;
Xbkg = log10(Xbkg + epsil);
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

배경 잡음의 스펙트로그램을 훈련 세트, 검증 세트, 테스트 세트로 분할합니다. _background_noise_ 폴더에는 약 5분 30초 분량의 배경 잡음만 포함되어 있으므로 서로 다른 데이터 세트의 배경 샘플은 밀접한 상관관계를 갖습니다. 배경 잡음의 변형을 늘리기 위해 직접 배경 파일을 만들어 폴더에 추가할 수 있습니다. 잡음에 대한 신경망의 견고성을 높이기 위해 음성 파일에 배경 잡음을 섞어볼 수도 있습니다.

numTrainBkg = floor(0.85*numBkgClips);
numValidationBkg = floor(0.15*numBkgClips);

XTrain(:,:,:,end+1:end+numTrainBkg) = Xbkg(:,:,:,1:numTrainBkg);
YTrain(end+1:end+numTrainBkg) = "background";

XValidation(:,:,:,end+1:end+numValidationBkg) = Xbkg(:,:,:,numTrainBkg+1:end);
YValidation(end+1:end+numValidationBkg) = "background";

훈련 세트와 검증 세트에서 다양한 클래스 레이블의 분포를 플로팅합니다.

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")

신경망 아키텍처 정의하기

간단한 신경망 아키텍처를 계층 배열로 만듭니다. 컨벌루션 계층과 배치 정규화 계층을 사용하고, 최댓값 풀링 계층을 사용하여 특징 맵을 "공간적으로"(즉, 시간과 주파수에서) 다운샘플링합니다. 시간의 흐름에 따라 입력 특징 맵을 전역적으로 풀링하는 마지막 최댓값 풀링 계층을 추가합니다. 이렇게 하면 입력 스펙트로그램에서 (근사적인) 시간 이동 불변성이 적용되어 음성의 정확한 시간적 위치에 상관없이 신경망이 동일한 분류를 수행할 수 있게 됩니다. 전역적 풀링은 마지막 완전 연결 계층에서 파라미터의 개수를 대폭 줄여 주기도 합니다. 신경망이 훈련 데이터의 구체적인 특징을 기억할 가능성을 줄이려면 입력값의 마지막 완전 연결 계층에 소량의 드롭아웃을 추가하십시오.

신경망은 몇 개의 필터만 있는 컨벌루션 계층 5개만 포함하므로 크기가 작습니다. numF는 컨벌루션 계층의 필터 개수를 제어합니다. 신경망의 정확도를 높이려면 컨벌루션 계층, 배치 정규화 계층, ReLU 계층으로 구성된 동일한 블록들을 추가하여 신경망의 심도를 높여보십시오. numF를 높여 컨벌루션 필터의 개수를 늘려볼 수도 있습니다.

가중 교차 엔트로피 분류 손실을 사용합니다. weightedClassificationLayer(classWeights)classWeights에 의해 가중치가 적용된 관측값으로 교차 엔트로피 손실을 계산하는 사용자 지정 분류 계층을 만듭니다. categories(YTrain)에 나타나는 클래스 순서대로 클래스 가중치를 지정합니다. 각 클래스에 손실의 총 가중치를 동일하게 주려면 각 클래스의 훈련 표본의 개수에 반비례하는 클래스 가중치를 사용하십시오. Adam 최적화 함수를 사용하여 신경망을 훈련시키는 경우에는 훈련 알고리즘이 클래스 가중치의 전체 정규화에 대해 독립적입니다.

classWeights = 1./countcats(YTrain);
classWeights = classWeights'/mean(classWeights);
numClasses = numel(categories(YTrain));

timePoolSize = ceil(numHops/8);

dropoutProb = 0.2;
numF = 12;
layers = [
    imageInputLayer([numHops numBands])

    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([timePoolSize,1])

    dropoutLayer(dropoutProb)
    fullyConnectedLayer(numClasses)
    softmaxLayer
    weightedClassificationLayer(classWeights)];

신경망 훈련시키기

훈련 옵션을 지정합니다. 미니 배치 크기가 128인 Adam 최적화 함수를 사용합니다. Epoch 25회에 대해 훈련시키고, Epoch 20회가 지나면 학습률을 10배만큼 줄입니다.

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);

신경망을 훈련시킵니다. GPU가 없으면, 신경망 훈련에 시간이 걸릴 수 있습니다.

trainedNet = trainNetwork(XTrain,YTrain,layers,options);

훈련된 신경망 평가하기

(데이터 증대가 적용되지 않은) 훈련 세트와 검증 세트에 대해 신경망의 최종 정확도를 계산합니다. 신경망은 이 데이터 세트에서 매우 정확한 결과를 나타냅니다. 그러나 훈련 데이터, 검증 데이터, 테스트 데이터는 모두 실제 환경을 그대로 반영한다고 보기 어려운 비슷한 분포를 갖고 있습니다. 이러한 한계는 특히 적은 개수의 발화된 단어를 포함하는 unknown 범주에서 두드러지게 나타납니다.

if reduceDataset
    load('commandNet.mat','trainedNet');
end
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.526%
Validation error: 5.1539%

정오분류표를 플로팅합니다. 열 및 행 요약을 사용하여 각 클래스의 정밀도를 표시하고 다시 호출합니다. 정오분류표의 클래스를 정렬합니다. 알려지지 않은 단어와 명령인 upoff, downno, gono 사이에서 가장 큰 오분류가 발생합니다.

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"])

모바일 응용 프로그램과 같이 하드웨어 리소스 제약이 있는 응용 프로그램에서 작업할 때는 가용 메모리와 연산 리소스에 대한 제한을 고려해야 합니다. 신경망의 총 크기를 kB 단위로 계산하고, CPU를 사용할 때의 예측 속도를 테스트합니다. 예측 시간은 단일 입력 영상을 분류하는 시간입니다. 신경망에 여러 개의 영상을 입력하는 경우, 복수의 영상이 동시에 분류될 수 있고, 그 결과 영상당 예측 시간이 단축됩니다. 그러나 스트리밍 오디오를 분류할 때는 단일 영상 예측 시간이 가장 중요합니다.

info = whos('trainedNet');
disp("Network size: " + info.bytes/1024 + " kB")

for i = 1:100
    x = randn([numHops,numBands]);
    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: 286.7314 kB
Single-image prediction time on CPU: 3.1647 ms

참고 문헌

[1] Warden P. "Speech Commands: A public dataset for single-word speech recognition", 2017. Available from https://storage.googleapis.com/download.tensorflow.org/data/speech_commands_v0.01.tar.gz. Copyright Google 2017. The Speech Commands Dataset is licensed under the Creative Commons Attribution 4.0 license, available here: https://creativecommons.org/licenses/by/4.0/legalcode.

참고 문헌

[1] Warden P. "Speech Commands: A public dataset for single-word speech recognition", 2017. Available from http://download.tensorflow.org/data/speech_commands_v0.01.tar.gz. Copyright Google 2017. The Speech Commands Dataset is licensed under the Creative Commons Attribution 4.0 license, available here: https://creativecommons.org/licenses/by/4.0/legalcode.

참고 항목

| |

관련 항목