深層学習を使用した音声コマンド認識モデルの学習
この例では、オーディオに存在する音声コマンドを検出する深層学習モデルに学習させる方法を説明します。この例では、Speech Commands Dataset [1] を使用して、一連のコマンドを認識する畳み込みニューラル ネットワークに学習させます。
事前学習済みの音声コマンド認識システムを使用する方法については、Speech Command Recognition Using Deep Learning (Audio Toolbox)を参照してください。
この例を短時間で実行するには、speedupExample
を true
に設定します。公開されている例をすべて実行するには、speedupExample
を false
に設定します。
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)
Progress = 17 (%) Progress = 33 (%) Progress = 50 (%) Progress = 67 (%) Progress = 83 (%) Progress = 100 (%)
学習データストアの作成
学習データ セットを指す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 the parallel pool (number of workers: 6).
特徴量の抽出
オーディオ入力から聴覚スペクトログラムを抽出するパラメーターを定義します。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]); tiledlayout(2,3) for ii = 1:3 [x,fs] = audioread(adsTrain.Files{idx(ii)}); nexttile(ii) plot(x) axis tight title(string(adsTrain.Labels(idx(ii)))) nexttile(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.7263%" "Validation error: 6.3968%"
検証セットの混同行列をプロットするには、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: 292.2842 kB" "Single-image prediction time on CPU: 3.7237 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.
参考
trainNetwork
| classify
| analyzeNetwork