Main Content

カスタム対数スペクトログラム層と深層学習を使用した数字音声認識

この例では、深層畳み込みニューラル ネットワーク (CNN) とカスタム対数スペクトログラム層を使用して数字音声を分類する方法を説明します。このカスタム層では、関数 dlstft を使用して、自動逆伝播をサポートするように短時間フーリエ変換を計算します。

データ

Free Spoken Digit データセット (FSDD) をクローンするかまたはダウンロードします。このデータセットは、https://github.com/Jakobovski/free-spoken-digit-dataset から入手できます。FSDD はオープンなデータセットなので、時間の経過とともに大きくなる可能性があります。この例では、2020 年 8 月 12 日にまとめられたバージョンを使用します。このバージョンには、6 人の話者が英語で発話した 0 ~ 9 の数字が 3000 件記録されています。各話者は 1 つの数字を 50 回発話しています。データは 8000 Hz でサンプリングされます。

audioDatastore を使用してデータ アクセスを管理します。location プロパティに、コンピューター上で FSDD の録音が保存されているフォルダーの場所を設定します。この例では、MATLAB の tempdir コマンドから返されるベース フォルダーを使用します。

pathToRecordingsFolder = fullfile(tempdir,"free-spoken-digit-dataset","recordings");
location = pathToRecordingsFolder;
ads = audioDatastore(location);

補助関数 helpergenLabels は、FSDD ファイルから取得したラベルの categorical 配列を作成します。helpergenLabels のソース コードの一覧を付録に示します。クラスと、各クラスに含まれる例の数をリストします。

ads.Labels = helpergenLabels(ads);
summary(ads.Labels)
     0      300 
     1      300 
     2      300 
     3      300 
     4      300 
     5      300 
     6      300 
     7      300 
     8      300 
     9      300 

各数字に対応する 4 つのオーディオ ファイルを抽出します。stft を使用して、デシベル単位でスペクトログラムをプロットします。発話のフォルマント構造の違いが、スペクトログラムで識別できます。このため、スペクトログラムはディープ ネットワークで数字の識別を学習する場合に妥当な信号表現となります。

adsSample = subset(ads,[1,301,601,901]);
SampleRate = 8000;
for i = 1:4
    [audioSamples,info] = read(adsSample); 
    subplot(2,2,i)
    stft(audioSamples,SampleRate,"FrequencyRange","onesided");
    title("Digit: "+string(info.Label))
end

各サブセットで均等なラベル比率を維持しながら、FSDD を学習セットとテスト セットに分割します。再現可能な結果を得るために、乱数発生器を既定値に設定します。80%、つまり 2400 件の録音が学習に使用されます。残りの 600 件の録音、つまり全体の 20% がテスト用に取り分けられます。

rng default;
ads = shuffle(ads);
[adsTrain,adsTest] = splitEachLabel(ads,0.8);

学習セットとテスト セットのどちらにも、各クラスが正しい割合で含まれていることを確認します。

disp(countEachLabel(adsTrain))
    Label    Count
    _____    _____

      0       240 
      1       240 
      2       240 
      3       240 
      4       240 
      5       240 
      6       240 
      7       240 
      8       240 
      9       240 
disp(countEachLabel(adsTest))
    Label    Count
    _____    _____

      0       60  
      1       60  
      2       60  
      3       60  
      4       60  
      5       60  
      6       60  
      7       60  
      8       60  
      9       60  

FSDD の録音では、サンプルの長さが同じではありません。ディープ ネットワークの信号表現としてスペクトログラムを使用するには、同じ長さの入力が必要になります。このバージョンの FSDD に含まれる音声録音の解析から、数字音声が切断されないようにするには、一般的な長さである 8192 サンプルとするのが適していることが示されます。8192 サンプルより長い録音は 8192 サンプルに打ち切られます。8192 サンプルより短い録音は長さが 8192 になるように対称的にパディングされます。補助関数 helperReadSPData は、8192 サンプルとなるようにデータを打ち切るかパディングして、各記録データを最大値で正規化します。helperReadSPData のソース コードの一覧を付録に示します。この補助関数は、データストアの変換と audioDatastore を併用して各録音に適用されます。

transTrain = transform(adsTrain,@(x,info)helperReadSPData(x,info),"IncludeInfo",true);
transTest = transform(adsTest,@(x,info)helperReadSPData(x,info),"IncludeInfo",true);

カスタム対数スペクトログラム層の定義

ネットワークの外部で前処理手順として信号処理を実施した場合、ネットワーク学習で使用される前処理設定とは異なる設定でネットワークの予測が行われる可能性が大きくなります。これはネットワーク性能に大きく影響する可能性があり、一般的には想定したものより性能が落ちることになります。スペクトログラムまたは他の前処理計算をネットワーク内部に層として配置すると、自己完結型モデルが得られ、展開用のパイプラインが簡略化されます。これにより、必要な信号処理演算が含まれる状態でネットワークを学習、展開または共有できるようになります。この例では、スペクトログラムの計算が主な信号処理演算となります。ネットワーク内部でスペクトログラムを計算する機能は、推定を行う場合にも、スペクトログラムを保存するのに十分なストレージ容量がデバイスにない場合にも有効です。ネットワーク内でスペクトログラムを計算する場合に必要となるのは、スペクトログラムの現在のバッチに十分なメモリを割り当てることのみです。ただし、これは学習速度の点からすると最適な選択肢ではないことに注意してください。十分なメモリがある場合は、すべてのスペクトログラムを事前に計算し、その結果を保存しておくことで学習時間が大幅に短縮されます。次に、ネットワークに学習させるため、生音声の代わりにストレージからスペクトログラムの "イメージ" を読み取り、ネットワークにスペクトログラムを直接入力します。これにより、学習時間が最も高速になり、さらに、先に述べた理由から、ネットワーク内部で信号処理を実行する機能にはまだかなりの利点があることに留意してください。

ディープ ネットワークを学習させる際は、信号の対数表現を使用すると有利な場合が多くありますが、これは対数がダイナミック レンジの圧縮器のように機能し、大きさ (振幅) は小さくても重要な情報を保持している表現値をブーストするためです。この例では、対数スペクトログラムの方がスペクトログラムより性能が優れています。そのため、この例ではカスタム対数スペクトログラム層を作成して、この層を入力層の後のネットワークに挿入します。カスタム層の作成方法の詳細については、カスタム深層学習層の定義 (Deep Learning Toolbox)を参照してください。

パラメーターの宣言と "コンストラクター関数の作成"

logSpectrogramLayer は学習可能なパラメーターのない層であるため、必要となるのは学習可能なプロパティ以外のプロパティに限られます。ここで必要となるプロパティは、スペクトログラムの計算に必要なプロパティのみです。それらのプロパティを properties セクションで宣言します。この層の関数 predict では、dlarray をサポートする短時間フーリエ変換関数 dlstft を使用してスペクトログラムを計算します。dlstft およびこれらのパラメーターの詳細については、dlstftのドキュメンテーションを参照してください。層を構築する関数を作成し、層のプロパティを初期化します。層を作成するために必要な変数をコンストラクター関数への入力として指定します。

classdef logSpectrogramLayer < nnet.layer.Layer    
    properties
        % (Optional) Layer properties.
        % Spectral window
        Window
        % Number of overlapped smaples
        OverlapLength
        % Number of DFT points
        FFTLength
        % Signal Length
        SignalLength
    end

    method
        function layer = logSpectrogramLayer(sigLength,NVargs)
            arguments
                sigLength {mustBeNumeric}
                NVargs.Window {mustBeFloat,mustBeNonempty,mustBeFinite,mustBeReal,mustBeVector}= hann(128,"periodic")
                NVargs.OverlapLength {mustBeNumeric} = 96
                NVargs.FFTLength {mustBeNumeric} = 128
                NVargs.Name string = "logspec"
            end
            layer.Type = "logSpectrogram";
            layer.Name =  NVargs.Name;
            layer.SignalLength = sigLength;
            layer.Window = NVargs.Window;
            layer.OverlapLength = NVargs.OverlapLength;
            layer.FFTLength = NVargs.FFTLength;
        end

        ...
     end

予測関数

前述のように、カスタム層は dlstft を使用して STFT を取得してから、振幅二乗の STFT の対数を計算して対数スペクトログラムを求めます。dlarray をサポートする他の関数を使用または追加して出力をカスタマイズしたい場合は、関数 log を削除することもできます。予測関数の別の出力を試したい場合は、logSpectrogramLayer.m を別のフォルダーにコピーすることができます。この例で使用するバージョンと競合しないように、カスタム層は別の名前で保存することをお勧めします。

        function Z = predict(layer, X)
            % Forward input data through the layer at prediction time and
            % output the result.
            %
            % Inputs:
            %         layer - Layer to forward propagate through
            %         X     - Input data, specified as a 1-by-1-by-C-by-N 
            %                 dlarray, where N is the mini-batch size.
            % Outputs:
            %         Z     - Output of layer forward function returned as 
            %                 an sz(1)-by-sz(2)-by-sz(3)-by-N dlarray,
            %                 where sz is the layer output size and N is
            %                 the mini-batch size.
            
            % Use dlstft to compute short-time Fourier transform.
            % Specify the data format as SSCB to match the output of 
            % imageInputLayer.      
            X = squeeze(X);                      
            Y = dlstft(X,"Window",layer.Window,...
                "FFTLength",layer.FFTLength,"OverlapLength",layer.OverlapLength,...
                "DataFormat","TBC");
            
            % This code is needed to handle the fact that 2D convolutional
            % DAG networks expect SSCB
            Y = permute(Y,[1 4 2 3]);                 
            
            % Take the logarithmic squared magnitude of short-time Fourier
            % transform.
            Z = log(abs(Y).^2+eps("single"));
        end

logSpectrogramLayer は学習と予測 (推定) に同じフォワード パスを使用するため、必要なのは関数 predict のみで、関数 forward は必要ありません。さらに、関数 predictdlstft を使用しますが、これは dlarray をサポートしているため、逆伝播の微分が自動的に行われます。つまり、関数 backward を記述する必要がありません。これは、dlarray をサポートするカスタム層を記述する際の大きな利点です。dlarray オブジェクトをサポートしている関数の一覧については、dlarray をサポートする関数の一覧 (Deep Learning Toolbox)を参照してください。

深層畳み込みニューラル ネットワーク (DCNN) アーキテクチャ

Deep Learning Toolbox では、カスタム層を他の層と同じように使用できます。カスタム層 logSpectrogramLayer を含む小さい DCNN を層の配列として構築します。畳み込み層とバッチ正規化層を使用します。また、最大プーリング層を使って特徴マップをダウンサンプリングします。過適合から保護するために、最後の全結合層への入力に少量のドロップアウトを追加します。

sigLength = 8192;
dropoutProb = 0.2;
numF = 12;
layers = [
    imageInputLayer([sigLength 1])
    
    logSpectrogramLayer(sigLength,"Window",hamming(1280),"FFTLength",1280,...
        "OverlapLength",900)
    
    convolution2dLayer(5,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(2)

    dropoutLayer(dropoutProb)
    fullyConnectedLayer(numel(categories(ads.Labels)))

    softmaxLayer
    ];

ネットワークの学習に使用するハイパーパラメーターを設定します。ミニバッチ サイズとして 50 を使用し、学習率として 1e-4 を使用します。Adam 最適化を指定します。PrefetchMethod parallel に設定してデータの非同期前処理とキューを有効にし、学習性能を最適化します。データを並列で前処理し、GPU を使用してネットワークに学習させるには、Parallel Computing Toolbox™ が必要です。

PrefetchMethod = "serial";
options = trainingOptions("adam", ...
    InitialLearnRate=1e-4, ...
    MaxEpochs=30, ...
    MiniBatchSize=50, ...
    Shuffle="every-epoch", ...
    PreprocessingEnvironment=PrefetchMethod, ...
    Plots="training-progress",...
    Metrics="accuracy", ...
    Verbose=false);

ネットワークに学習をさせます。

[trainedNet,trainInfo] = trainnet(transTrain,layers,"crossentropy",options);

学習済みのネットワークを使用して、テスト セットに含まれる数字ラベルを予測します。予測精度を計算します。

probs = minibatchpredict(trainedNet,transTest);
YPred = scores2label(probs,categories(ads.Labels));
cnnAccuracy = sum(YPred==adsTest.Labels)/numel(YPred)*100
cnnAccuracy = 
  97.333333333333343

テスト セットに対する学習済みのネットワークのパフォーマンスを混同チャートに要約します。列と行の要約を使用して、各クラスの適合率と再現率を表示します。混同チャートの下にあるテーブルに、精度が示されます。混同チャートの右側にあるテーブルに、再現率が示されます。

figure("Units","normalized","Position",[0.2 0.2 0.5 0.5]);
ccDCNN = confusionchart(adsTest.Labels,YPred);
ccDCNN.Title = "Confusion Chart for DCNN";
ccDCNN.ColumnSummary = "column-normalized";
ccDCNN.RowSummary = "row-normalized";

まとめ

この例では、dlstft を使用してカスタム スペクトログラム層を作成する方法を説明しました。この例では、dlarray をサポートする機能を使用して、逆伝播および GPU の使用をサポートするようにネットワーク内に信号処理演算を組み込む方法について説明しました。

付録: 補助関数

function Labels = helpergenLabels(ads)
% This function is only for use in the "Spoken Digit Recognition with
% Custom Log Spectrogram Layer and Deep Learning" example. It may change or
% be removed in a future release.

tmp = cell(numel(ads.Files),1);
expression = "[0-9]+_";
for nf = 1:numel(ads.Files)
    idx = regexp(ads.Files{nf},expression);
    tmp{nf} = ads.Files{nf}(idx);
end
Labels = categorical(tmp);
end
function [out,info] = helperReadSPData(x,info)
% This function is only for use in the "Spoken Digit Recognition with
% Custom Log Spectrogram Layer and Deep Learning" example. It may change or
% be removed in a future release.

N = numel(x);
if N > 8192
    x = x(1:8192);
elseif N < 8192
    pad = 8192-N;
    prepad = floor(pad/2);
    postpad = ceil(pad/2);
    x = [zeros(prepad,1) ; x ; zeros(postpad,1)];
end
x = x./max(abs(x));
out = {x./max(abs(x)),info.Label};
end

参考

(Deep Learning Toolbox)