ドキュメンテーション

最新のリリースでは、このページがまだ翻訳されていません。 このページの最新版は英語でご覧になれます。

深層学習を使用した音声コマンド認識

この例では、オーディオに存在する音声コマンドを検出するシンプルな深層学習モデルに学習させる方法を説明します。この例では、音声コマンド データセット [1] を使用して、与えられた一連のコマンドを認識する畳み込みニューラル ネットワークに学習させます。

この例を実行するには、まず、データセットをダウンロードしなければなりません。データ セットをダウンロードしない、またはネットワークに学習させない場合、この例を MATLAB® で開いて、コマンド ラインで load('commandNet.mat') と入力し、事前学習済みのネットワークを読み込むことができます。ネットワークを読み込んだ後、この例の最後の節「マイクからのストリーミング オーディオを使用したコマンドの検出」に直接進みます。

音声コマンド データセットの読み込み

データセットを http://download.tensorflow.org/data/speech_commands_v0.01.tar.gz からダウンロードし、ダウンロードしたファイルを解凍します。datafolder をデータの場所に設定します。audioDatastore を使用して、ファイル名と対応するラベルを含むデータストアを作成します。フォルダー名をラベル ソースとして使用します。オーディオ ファイル全体を読み取るための読み取り方法を指定します。後で使用するためにデータストアのコピーを作成します。

datafolder = fullfile(tempdir,'speech_commands_v0.01');
ads = audioDatastore(datafolder, ...
    'IncludeSubfolders',true, ...
    'FileExtensions','.wav', ...
    'LabelSource','foldernames')
ads0 = copy(ads);
ads = 

  audioDatastore with properties:

                       Files: {
                              ' ...\Temp\speech_commands_v0.01\_background_noise_\doing_the_dishes.wav';
                              ' ...\Local\Temp\speech_commands_v0.01\_background_noise_\dude_miaowing.wav';
                              ' ...\Local\Temp\speech_commands_v0.01\_background_noise_\exercise_bike.wav'
                               ... and 64724 more
                              }
                      Labels: [_background_noise_; _background_noise_; _background_noise_ ... and 64724 more categorical]
    AlternateFileSystemRoots: {}
              OutputDataType: 'double'

認識する単語の選択

モデルにコマンドとして認識させる単語を指定します。コマンドではないすべての単語に unknown とラベル付けします。コマンドではない単語に unknown とラベル付けすることで、コマンド以外のすべての単語の分布に近い単語のグループが作成されます。ネットワークは、このグループを使用して、コマンドと他のすべての単語の違いを学習します。

既知の単語と未知の単語の間でクラスの不均衡を減らし、処理を高速化するために、未知の単語の一部 (includeFraction) のみを学習セットに含めます。バックグラウンド ノイズがある長いファイルは、まだ学習セットに含めないでください。バックグラウンド ノイズは以降の別の手順で追加します。

subset(ads,indices) を使用して、indices によってインデックス付けされたファイルとラベルのみを含むデータストアを作成します。コマンドと未知の単語のサブセットのみが含まれるようにデータストア ads を縮小します。各クラスに属している例の数をカウントします。

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

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

  11×2 table

     Label     Count
    _______    _____

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

学習セット、検証セット、およびテスト セットへのデータの分割

データセット フォルダーには、検証セットおよびテスト セットとして使用するオーディオ ファイルをリストしたテキスト ファイルが含まれています。これらの事前定義された検証セットとテスト セットには、同じ人物による同じ単語の発話が含まれていないため、データセット全体のランダムなサブセットを選択するよりも、これらの事前定義されたセットを使用する方が得策です。サポート関数 splitData を使用して、データセット フォルダーにある検証ファイルとテスト ファイルのリストに基づき、データストアを学習セット、検証セット、およびテスト セットに分割します。

この例では 1 つのネットワークに学習させるため、テスト セットは使用せず検証セットのみを使用して、学習済みのモデルを評価します。多くのネットワークに学習させて検証精度が最も高いネットワークを最終的なネットワークとして選択すると、テスト セットを使用して最終的なネットワークを評価できます。

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

音声スペクトログラムの計算

畳み込みニューラル ネットワークの学習を効果的に行うためのデータを準備するために、音声波形を対数メル スペクトログラムに変換します。

スペクトログラムの計算に使用するパラメーターを定義します。segmentDuration は各音声クリップの長さ (秒単位) です。frameDuration はスペクトログラムの計算用の各フレームの長さです。hopDuration はスペクトログラムの各列間のタイム ステップです。numBands は対数メル フィルターの数で、各スペクトログラムの高さに一致します。

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

サポート関数 speechSpectrograms を使用して、学習セット、検証セット、およびテスト セットのスペクトログラムを計算します。関数 speechSpectrograms は対数メル スペクトログラムの計算に melSpectrogram を使用します。滑らかな分布のデータを得るために、小さいオフセット epsil を使用してスペクトログラムの対数を取ります。

epsil = 1e-6;

XTrain = speechSpectrograms(adsTrain,segmentDuration,frameDuration,hopDuration,numBands);
XTrain = log10(XTrain + epsil);

XValidation = speechSpectrograms(adsValidation,segmentDuration,frameDuration,hopDuration,numBands);
XValidation = log10(XValidation + epsil);

XTest = speechSpectrograms(adsTest,segmentDuration,frameDuration,hopDuration,numBands);
XTest = log10(XTest + epsil);

YTrain = adsTrain.Labels;
YValidation = adsValidation.Labels;
YTest = adsTest.Labels;
Computing speech spectrograms...
Processed 1000 files out of 24982
Processed 2000 files out of 24982
Processed 3000 files out of 24982
Processed 4000 files out of 24982
Processed 5000 files out of 24982
Processed 6000 files out of 24982
Processed 7000 files out of 24982
Processed 8000 files out of 24982
Processed 9000 files out of 24982
Processed 10000 files out of 24982
Processed 11000 files out of 24982
Processed 12000 files out of 24982
Processed 13000 files out of 24982
Processed 14000 files out of 24982
Processed 15000 files out of 24982
Processed 16000 files out of 24982
Processed 17000 files out of 24982
Processed 18000 files out of 24982
Processed 19000 files out of 24982
Processed 20000 files out of 24982
Processed 21000 files out of 24982
Processed 22000 files out of 24982
Processed 23000 files out of 24982
Processed 24000 files out of 24982
...done
Computing speech spectrograms...
Processed 1000 files out of 3477
Processed 2000 files out of 3477
Processed 3000 files out of 3477
...done
Computing speech spectrograms...
Processed 1000 files out of 3473
Processed 2000 files out of 3473
Processed 3000 files out of 3473
...done

データの可視化

いくつかの学習例について波形とスペクトログラムをプロットします。対応するオーディオ クリップを再生します。

specMin = min(XTrain(:));
specMax = max(XTrain(:));
idx = randperm(size(XTrain,4),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+2 specMax])
    shading flat

    sound(x,fs)
    pause(2)
end

ネットワークへの入力が適度に滑らかに分布していて正規化されている場合、ニューラル ネットワークの学習は最も簡単になります。データ分布が滑らかであることを確認するために、学習データのピクセル値のヒストグラムをプロットします。

figure
histogram(XTrain,'EdgeColor','none','Normalization','pdf')
axis tight
ax = gca;
ax.YScale = 'log';
xlabel("Input Pixel Value")
ylabel("Probability Density")

バックグラウンド ノイズ データの追加

このネットワークは、発声されたさまざまな単語を認識できるだけでなく、入力に無音部分またはバックグラウンド ノイズが含まれているかどうかを検出できなければなりません。

_background_noise_ フォルダーのオーディオ ファイルを使用して、バックグラウンド ノイズの 1 秒間のクリップのサンプルを作成します。各バックグラウンド ノイズ ファイルから同じ数のバックグラウンド クリップを作成します。また、バックグラウンド ノイズの録音を独自に作成して、_background_noise_ フォルダーに追加することもできます。adsBkg データストアのオーディオ ファイルから得られたバックグラウンド クリップの numBkgClips スペクトログラムを計算するには、サポート関数 backgroundSpectrograms を使用します。スペクトログラムを計算する前に、この関数は、対数一様分布からサンプリングされた係数を使用して、volumeRange で与えられた範囲に各オーディオ クリップを再スケーリングします。

4,000 個のバックグラウンド クリップを作成し、1e-41 の間の数で各クリップを再スケーリングします。XBkg には、ほぼ無音から大音量までの音量のバックグラウンド ノイズのスペクトログラムが含まれています。

adsBkg = subset(ads0,ads0.Labels=="_background_noise_");
numBkgClips = 4000;
volumeRange = [1e-4,1];

XBkg = backgroundSpectrograms(adsBkg,numBkgClips,volumeRange,segmentDuration,frameDuration,hopDuration,numBands);
XBkg = log10(XBkg + epsil);
Computing background spectrograms...
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
...done

バックグラウンド ノイズのスペクトログラムを学習セット、検証セット、およびテスト セットに分割します。_background_noise_ フォルダーには約 5 分半のバックグラウンド ノイズのみが含まれているため、異なるデータセットのバックグラウンド サンプルには高い相関があります。バックグラウンド ノイズのバリエーションを増やすために、独自のバックグラウンド ファイルを作成して、このフォルダーに追加できます。ノイズに対するネットワークのロバスト性を向上させるために、バックグラウンド ノイズを音声ファイルにミキシングしてみることもできます。

numTrainBkg = floor(0.8*numBkgClips);
numValidationBkg = floor(0.1*numBkgClips);
numTestBkg = floor(0.1*numBkgClips);

XTrain(:,:,:,end+1:end+numTrainBkg) = XBkg(:,:,:,1:numTrainBkg);
XBkg(:,:,:,1:numTrainBkg) = [];
YTrain(end+1:end+numTrainBkg) = "background";

XValidation(:,:,:,end+1:end+numValidationBkg) = XBkg(:,:,:,1:numValidationBkg);
XBkg(:,:,:,1:numValidationBkg) = [];
YValidation(end+1:end+numValidationBkg) = "background";

XTest(:,:,:,end+1:end+numTestBkg) = XBkg(:,:,:,1: numTestBkg);
clear XBkg;
YTest(end+1:end+numTestBkg) = "background";

YTrain = removecats(YTrain);
YValidation = removecats(YValidation);
YTest = removecats(YTest);

学習セットと検証セット内のさまざまなクラス ラベルの分布をプロットします。テスト セットの分布は検証セットの分布と非常によく似ています。

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

データ拡張の追加

スペクトログラムの自動拡張とサイズ変更用の拡張イメージ データストアを作成します。スペクトログラムを時間について最大 10 フレーム (100 ms) 前後にランダムに移動し、さらにこのスペクトログラムを時間軸に沿って 20% スケールアップまたはスケールダウンします。データを拡張すると、学習データの有効サイズが増加し、ネットワークの過適合を防止するのに役立ちます。拡張したイメージ データストアは、学習中にリアルタイムで拡張イメージを作成し、それらのイメージをネットワークに入力します。拡張されたスペクトログラムはメモリに保存されません。

sz = size(XTrain);
specSize = sz(1:2);
imageSize = [specSize 1];
augmenter = imageDataAugmenter( ...
    'RandXTranslation',[-10 10], ...
    'RandXScale',[0.8 1.2], ...
    'FillValue',log10(epsil));
augimdsTrain = augmentedImageDatastore(imageSize,XTrain,YTrain, ...
    'DataAugmentation',augmenter);

ニューラル ネットワーク アーキテクチャの定義

シンプルなネットワーク アーキテクチャを層の配列として作成します。畳み込み層とバッチ正規化層を使用します。最大プーリング層を使って特徴マップを "空間的に" (つまり、時間と周波数に関して) ダウンサンプリングします。入力の特徴マップを時間の経過と共にグローバルにプーリングする最後の最大プーリング層を追加します。これによって (近似的な) 時間並進不変性が入力スペクトログラムに課されるため、ネットワークは、音声の正確な時間的位置とは無関係に同じ分類を実行できます。また、グローバル プーリングによって、最後の全結合層のパラメーター数が大幅に減少します。ネットワークが学習データの特定の特徴を記憶する可能性を減らすために、最後の全結合層への入力に少量のドロップアウトを追加します。

このネットワークは、フィルターがほとんどない 5 つの畳み込み層しかないため小規模です。numF によって畳み込み層のフィルターの数を制御します。ネットワークの精度を高めるために、畳み込み層、バッチ正規化層、および ReLU 層の同等なブロックを追加して、ネットワーク深さを大きくすることを試してください。numF を増やして、畳み込みフィルターの数を増やしてみることもできます。

重み付き交差エントロピー分類損失を使用します。weightedClassificationLayer(classWeights) は、classWeights によって重み付けされた観測値を使用して交差エントロピー損失を計算するカスタム分類層を作成します。categories(YTrain) に現れるクラスと同じ順序でクラスの重みを指定します。各クラスの損失の合計重みを等しくするために、各クラスの学習例の数に反比例するクラスの重みを使用します。ネットワークの学習に Adam オプティマイザーを使用する場合、学習アルゴリズムはクラスの重み全体の正規化に依存しません。

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

timePoolSize = ceil(imageSize(2)/8);
dropoutProb = 0.2;
numF = 12;
layers = [
    imageInputLayer(imageSize)

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

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

ネットワークの学習

学習オプションを指定します。ミニバッチ サイズを 128 として Adam オプティマイザーを使用します。学習は 25 エポック行い、20 エポック後に学習率を 10 分の 1 に下げます。

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 がない場合、ネットワークの学習に時間がかかる場合があります。ネットワークにゼロから学習させる代わりに、事前学習済みのネットワークを読み込む場合は、doTrainingfalse に設定します。

doTraining = true;
if doTraining
    trainedNet = trainNetwork(augimdsTrain,layers,options);
else
    load('commandNet.mat','trainedNet');
end

学習済みネットワークの評価

学習セット (データ拡張なし) と検証セットに対するネットワークの最終精度を計算します。このデータセットではネットワークは非常に正確になります。ただし、学習データ、検証データ、およびテスト データの分布はどれも似ていて、必ずしも実際の環境を反映していません。この制限は特に unknown カテゴリに当てはまります。このカテゴリには、少数の単語の発話しか含まれていません。

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.7103%
Validation error: 4.5912%

混同行列をプロットします。列と行の要約を使用して、各クラスの適合率と再現率を表示します。混同行列のクラスを並べ替えます。大きな混同は、未知の単語間、コマンド upoffdownno、および 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"])

モバイル用途など、ハードウェア リソースに制約がある用途で使用する場合は、利用可能なメモリおよび計算リソースの制限を考慮します。CPU を使用する場合は、ネットワークの合計サイズを KB 単位で計算し、その予測速度をテストします。予測時間は 1 つの入力イメージの分類にかかる時間です。複数のイメージをネットワークに入力する場合、これらを同時に分類して、イメージあたりの予測時間を短くすることができます。ストリーミング オーディオを分類する場合、1 つのイメージの予測時間が最も重要です。

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

for i=1:100
    x = randn(imageSize);
    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: 295.9141 kB
Single-image prediction time on CPU: 3.1274 ms

マイクからのストリーミング オーディオを使用したコマンドの検出

新しく学習させたコマンド検出ネットワークをマイクからのストリーミング オーディオでテストします。ネットワークの学習を行っていない場合は、コマンド ラインで load('commandNet.mat') と入力して、ライブ ストリーミング オーディオの分類に必要な、事前学習済みのネットワークとパラメーターを読み込みます。yesnostop など、いずれかのコマンドを発声してみてください。さらに、MarvinSheilabedhousecatbird などの未知の単語や、0 から 9 までの数のいずれかを発声してみてください。

オーディオのサンプリング レートと分類レートを Hz 単位で指定し、マイクからオーディオを読み取ることができるオーディオ デバイス リーダーを作成します。

fs = 16e3;
classificationRate = 20;
audioIn = audioDeviceReader('SampleRate',fs, ...
    'SamplesPerFrame',floor(fs/classificationRate));

ストリーミング スペクトログラムの計算用のパラメーターを指定し、オーディオのバッファーを初期化します。ネットワークの分類ラベルを抽出します。ストリーミング オーディオのラベルと分類確率用の 0.5 秒のバッファーを初期化します。これらのバッファーを使用して長い時間にわたって分類結果を比較し、それによって、コマンドが検出されたタイミングとの '一致' を構築します。

frameLength = floor(frameDuration*fs);
hopLength = floor(hopDuration*fs);
waveBuffer = zeros([fs,1]);

labels = trainedNet.Layers(end).Classes;
YBuffer(1:classificationRate/2) = categorical("background");
probBuffer = zeros([numel(labels),classificationRate/2]);

Figure を作成し、作成した Figure が存在する限りコマンドを検出します。ライブ検出を停止するには、単に Figure を閉じます。

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

while ishandle(h)

    % Extract audio samples from the audio device and add the samples to
    % the buffer.
    x = audioIn();
    waveBuffer(1:end-numel(x)) = waveBuffer(numel(x)+1:end);
    waveBuffer(end-numel(x)+1:end) = x;

    % Compute the spectrogram of the latest audio samples.
    spec = melSpectrogram(waveBuffer,fs, ...
        'WindowLength',frameLength, ...
        'OverlapLength',frameLength - hopLength, ...
        'FFTLength',512, ...
        'NumBands',numBands, ...
        'FrequencyRange',[50,7000]);
    spec = log10(spec + epsil);

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

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

    subplot(2,1,2)
    pcolor(spec)
    caxis([specMin+2 specMax])
    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 predicted probability of the predicted label is at
    % least |probThreshold|. Otherwise, do not declare a detection.
    [YMode,count] = mode(YBuffer);
    countThreshold = ceil(classificationRate*0.2);
    maxProb = max(probBuffer(labels == YMode,:));
    probThreshold = 0.7;
    subplot(2,1,1);
    if YMode == "background" || count<countThreshold || maxProb < probThreshold
        title(" ")
    else
        title(string(YMode),'FontSize',20)
    end

    drawnow

end

参照

[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.

参考

| |

関連するトピック