Main Content

このページの内容は最新ではありません。最新版の英語を参照するには、ここをクリックします。

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

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

事前学習済みの音声コマンド認識システムを使用する方法については、Speech Command Recognition Using Deep Learning (Audio Toolbox)を参照してください。

この例を短時間で実行するには、speedupExampletrue に設定します。公開されている例をすべて実行するには、speedupExamplefalse に設定します。

speedupExample = false;

正確に再現できるように、乱数シードを設定します。

rng default

データの読み込み

この例では、Google Speech Commands Datase [1] を使用します。データ セットをダウンロードして解凍します。

downloadFolder = matlab.internal.examples.downloadSupportFile("audio","google_speech.zip");
dataFolder = tempdir;
unzip(downloadFolder,dataFolder)
dataset = fullfile(dataFolder,"google_speech");

データの拡張

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

サポート関数 augmentDataset は、Google Speech Commands データセットの background フォルダーにある長時間のオーディオ ファイルを使用して、バックグラウンド ノイズから成る 1 秒間のセグメントを作成します。この関数は、各バックグラウンド ノイズ ファイルから同じ数のバックグラウンド セグメントを作成し、それらのセグメントを学習フォルダーと検証フォルダーに分割して格納します。

augmentDataset(dataset)

学習データストアの作成

学習データ セットを指すaudioDatastore (Audio Toolbox)を作成します。

ads = audioDatastore(fullfile(dataset,"train"), ...
    IncludeSubfolders=true, ...
    FileExtensions=".wav", ...
    LabelSource="foldernames");

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

既知の単語と未知の単語の間でクラスの不均衡を減らし、処理を高速化するために、未知の単語の一部のみを学習セットに含めます。

subset (Audio Toolbox)を使用して、コマンド、バックグラウンド ノイズ、および不明な単語のサブセットのみが含まれるデータストアを作成します。各カテゴリに属している例の数をカウントします。

commands = categorical(["yes","no","up","down","left","right","on","off","stop","go"]);
background = categorical("background");

isCommand = ismember(ads.Labels,commands);
isBackground = ismember(ads.Labels,background);
isUnknown = ~(isCommand|isBackground);

includeFraction = 0.2; % Fraction of unknowns to include.
idx = find(isUnknown);
idx = idx(randperm(numel(idx),round((1-includeFraction)*sum(isUnknown))));
isUnknown(idx) = false;

ads.Labels(isUnknown) = categorical("unknown");

adsTrain = subset(ads,isCommand|isUnknown|isBackground);
adsTrain.Labels = removecats(adsTrain.Labels);

検証データストアの作成

検証データ セットを指すaudioDatastore (Audio Toolbox)を作成します。学習データストアの作成に用いたのと同じ手順に従います。

ads = audioDatastore(fullfile(dataset,"validation"), ...
    IncludeSubfolders=true, ...
    FileExtensions=".wav", ...
    LabelSource="foldernames");

isCommand = ismember(ads.Labels,commands);
isBackground = ismember(ads.Labels,background);
isUnknown = ~(isCommand|isBackground);

includeFraction = 0.2; % Fraction of unknowns to include.
idx = find(isUnknown);
idx = idx(randperm(numel(idx),round((1-includeFraction)*sum(isUnknown))));
isUnknown(idx) = false;

ads.Labels(isUnknown) = categorical("unknown");

adsValidation = subset(ads,isCommand|isUnknown|isBackground);
adsValidation.Labels = removecats(adsValidation.Labels);

学習ラベルと検証ラベルの分布を表示します。

figure(Units="normalized",Position=[0.2,0.2,0.5,0.5])

tiledlayout(2,1)

nexttile
histogram(adsTrain.Labels)
title("Training Label Distribution")
ylabel("Number of Observations")
grid on

nexttile
histogram(adsValidation.Labels)
title("Validation Label Distribution")
ylabel("Number of Observations")
grid on

必要であれば、データ セットを小さくして、この例の実行を高速化します。

if speedupExample
    numUniqueLabels = numel(unique(adsTrain.Labels)); %#ok<UNRCH> 
    % 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

学習用データの準備

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

処理を高速化するには、複数のワーカーに特徴量抽出を分散します。Parallel Computing Toolbox™ を利用できる場合は、並列プールを起動します。

if canUseParallelPool && ~speedupExample
    useParallel = true;
    gcp;
else
    useParallel = false;
end
Starting parallel pool (parpool) using the 'local' profile ...
Connected to parallel pool with 6 workers.

特徴の抽出

オーディオ入力から聴覚スペクトログラムを抽出するパラメーターを定義します。segmentDuration は各音声クリップの長さ (秒) です。frameDuration はスペクトル計算の各フレームの長さです。hopDuration は各スペクトル間のタイム ステップです。numBands は聴覚スペクトログラムのフィルター数です。

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

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

FFTLength = 512;
numBands = 50;

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

audioFeatureExtractor (Audio Toolbox)オブジェクトを作成して特徴量抽出を実行します。

afe = audioFeatureExtractor( ...
    SampleRate=fs, ...
    FFTLength=FFTLength, ...
    Window=hann(frameSamples,"periodic"), ...
    OverlapLength=overlapSamples, ...
    barkSpectrum=true);
setExtractorParameters(afe,"barkSpectrum",NumBands=numBands,WindowNormalization=false);

長さが等しくなるようにオーディオをパディングするため一連のtransform (Audio Toolbox)audioDatastore (Audio Toolbox)で定義し、特徴量を抽出してから、対数を適用します。

transform1 = transform(adsTrain,@(x)[zeros(floor((segmentSamples-size(x,1))/2),1);x;zeros(ceil((segmentSamples-size(x,1))/2),1)]);
transform2 = transform(transform1,@(x)extract(afe,x));
transform3 = transform(transform2,@(x){log10(x+1e-6)});

関数readall (Audio Toolbox)を使用して、データストアからすべてのデータを読み取ります。ファイルが読み取られるたびに、変換が実行されてからデータが返されます。

XTrain = readall(transform3,UseParallel=useParallel);

出力は numFiles 行 1 列の cell 配列です。cell 配列の各要素は、ファイルから抽出された聴覚スペクトログラムに対応します。

numFiles = numel(XTrain)
numFiles = 28463
[numHops,numBands,numChannels] = size(XTrain{1})
numHops = 98
numBands = 50
numChannels = 1

cell 配列を変換し、4 番目の次元に聴覚スペクトログラムをもつ 4 次元配列にします。

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

[numHops,numBands,numChannels,numFiles] = size(XTrain)
numHops = 98
numBands = 50
numChannels = 1
numFiles = 28463

検証セットに対して、上記で説明した特徴量抽出の手順を実行します。

transform1 = transform(adsValidation,@(x)[zeros(floor((segmentSamples-size(x,1))/2),1);x;zeros(ceil((segmentSamples-size(x,1))/2),1)]);
transform2 = transform(transform1,@(x)extract(afe,x));
transform3 = transform(transform2,@(x){log10(x+1e-6)});
XValidation = readall(transform3,UseParallel=useParallel);
XValidation = cat(4,XValidation{:});

便宜上、学習ターゲット ラベルと検証ターゲット ラベルを分離します。

TTrain = adsTrain.Labels;
TValidation = 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]);

tlh = tiledlayout(2,3);
for ii = 1:3
    [x,fs] = audioread(adsTrain.Files{idx(ii)});

    nexttile(tlh,ii)
    plot(x)
    axis tight
    title(string(adsTrain.Labels(idx(ii))))
    
    nexttile(tlh,ii+3)
    spect = XTrain(:,:,1,idx(ii))';
    pcolor(spect)
    clim([specMin specMax])
    shading flat
    
    sound(x,fs)
    pause(2)
end

ネットワーク アーキテクチャの定義

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

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

各クラスの損失の合計重みを等しくするために、各クラスの学習例の数に反比例するクラスの重みを使用します。ネットワークの学習に Adam オプティマイザーを使用する場合、学習アルゴリズムはクラスの重み全体の正規化に依存しません。

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

timePoolSize = ceil(numHops/8);

dropoutProb = 0.2;
numF = 12;
layers = [
    imageInputLayer([numHops,afe.FeatureVectorLength])
    
    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
    classificationLayer(Classes=classes,ClassWeights=classWeights)];

学習オプションの指定

学習パラメーターを定義するには、trainingOptionsを使用します。ミニバッチ サイズを 128 として Adam オプティマイザーを使用します。

miniBatchSize = 128;
validationFrequency = floor(numel(TTrain)/miniBatchSize);
options = trainingOptions("adam", ...
    InitialLearnRate=3e-4, ...
    MaxEpochs=15, ...
    MiniBatchSize=miniBatchSize, ...
    Shuffle="every-epoch", ...
    Plots="training-progress", ...
    Verbose=false, ...
    ValidationData={XValidation,TValidation}, ...
    ValidationFrequency=validationFrequency);

ネットワークの学習

ネットワークに学習させるには、trainNetworkを使用します。GPU がない場合、ネットワークの学習に時間がかかる場合があります。

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

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

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

YValidation = classify(trainedNet,XValidation);
validationError = mean(YValidation ~= TValidation);
YTrain = classify(trainedNet,XTrain);
trainError = mean(YTrain ~= TTrain);

disp(["Training error: " + trainError*100 + " %";"Validation error: " + validationError*100 + " %"])
    "Training error: 2.5577%"
    "Validation error: 5.922%"

検証セットの混同行列をプロットするには、confusionchartを使用します。列と行の要約を使用して、各クラスの適合率と再現率を表示します。

figure(Units="normalized",Position=[0.2,0.2,0.5,0.5]);
cm = confusionchart(TValidation,YValidation, ...
    Title="Confusion Matrix for Validation Data", ...
    ColumnSummary="column-normalized",RowSummary="row-normalized");
sortClasses(cm,[commands,"unknown","background"])

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

for ii = 1:100
    x = randn([numHops,numBands]);
    predictionTimer = tic;
    [y,probs] = classify(trainedNet,x,ExecutionEnvironment="cpu");
    time(ii) = toc(predictionTimer);
end

disp(["Network size: " + whos("trainedNet").bytes/1024 + " kB"; ...
"Single-image prediction time on CPU: " + mean(time(11:end))*1000 + " ms"])
    "Network size: 289.6484 kB"
    "Single-image prediction time on CPU: 2.6226 ms"

サポート関数

バックグラウンド ノイズによるデータセットの拡張

function augmentDataset(datasetloc)
adsBkg = audioDatastore(fullfile(datasetloc,"background"));
fs = 16e3; % Known sample rate of the data set
segmentDuration = 1;
segmentSamples = round(segmentDuration*fs);

volumeRange = log10([1e-4,1]);

numBkgSegments = 4000;
numBkgFiles = numel(adsBkg.Files);
numSegmentsPerFile = floor(numBkgSegments/numBkgFiles);

fpTrain = fullfile(datasetloc,"train","background");
fpValidation = fullfile(datasetloc,"validation","background");

if ~datasetExists(fpTrain)

    % Create directories
    mkdir(fpTrain)
    mkdir(fpValidation)

    for backgroundFileIndex = 1:numel(adsBkg.Files)
        [bkgFile,fileInfo] = read(adsBkg);
        [~,fn] = fileparts(fileInfo.FileName);

        % Determine starting index of each segment
        segmentStart = randi(size(bkgFile,1)-segmentSamples,numSegmentsPerFile,1);

        % Determine gain of each clip
        gain = 10.^((volumeRange(2)-volumeRange(1))*rand(numSegmentsPerFile,1) + volumeRange(1));

        for segmentIdx = 1:numSegmentsPerFile

            % Isolate the randomly chosen segment of data.
            bkgSegment = bkgFile(segmentStart(segmentIdx):segmentStart(segmentIdx)+segmentSamples-1);

            % Scale the segment by the specified gain.
            bkgSegment = bkgSegment*gain(segmentIdx);

            % Clip the audio between -1 and 1.
            bkgSegment = max(min(bkgSegment,1),-1);

            % Create a file name.
            afn = fn + "_segment" + segmentIdx + ".wav";

            % Randomly assign background segment to either the train or
            % validation set.
            if rand > 0.85 % Assign 15% to validation
                dirToWriteTo = fpValidation;
            else % Assign 85% to train set.
                dirToWriteTo = fpTrain;
            end

            % Write the audio to the file location.
            ffn = fullfile(dirToWriteTo,afn);
            audiowrite(ffn,bkgSegment,fs)

        end

        % Print progress
        fprintf('Progress = %d (%%)\n',round(100*progress(adsBkg)))

    end
end
end

参考文献

[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. Speech Commands Dataset は、次で公開されている Creative Commons Attribution 4.0 license に従ってライセンスされています。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.

参考

| |

関連するトピック