音声データの特徴抽出(メル尺度、MFCC)処理をDALIで高速化できるか試した。

前回の記事でスペクトラム変換は高速に処理できることが確認できました。音声処理ではスペクトラム変換以外にメル尺度に基づいたスペクトラム変換やMFCC変換処理もよく使用されます。これらの処理も高速に処理できるか試してみます。

音声認識の際に有効な特徴抽出処理であるMFCCにおいても高速に処理できるか確かめて見たぞ

結論から言うとメル尺度のスペクトラムはGPUで高速化でき、MFCCは動作確認できた形だ

メル尺度

音高の知覚尺度です。下記の図を見れば分かりますが約5000[Hz]以下では急激に増加し、そこからなだらかになっています。つまり周波数の増加量と人間が知覚する音の高さは比例せずに一定の周波数以上になるとその差が大きくなっていきます。

図はメル尺度(Wikipedia)から引用しました。

詳細は下記をご覧ください

https://ja.wikipedia.org/wiki/%E3%83%A1%E3%83%AB%E5%B0%BA%E5%BA%A6

ケプストラム

音声は音の発生源の周波数と声道の周波数で構成されています。これを分解するための処理がケプストラムです。

低ケフレンシー(周波数の逆数)成分が声道の成分になり、高ケフレンシー成分が音源の成分になります。

図はメル周波数ケプストラム(MFCC)から引用しました。

MFCC

メル尺度は尺度が大きくなればなるほどゆるやかになる尺度なのでこれをスペクトラムに適用します。そうすると下記の図のように低周波数成分が引き伸ばされ、高周波成分が圧縮されます。

図はメル尺度(Wikipedia)から引用しました。

このメル周波数をケプストラムに変換すると声道と音源の成分に分解できます。

音声認識において有用な声道の成分である低次元部分(12次元)を取得する手法がMFCCです。

DALIでメル尺度の処理が高速化できるか試す

Google Colabで試しています。環境構築方法は下記をご覧ください。

音声データの準備などは前記事を参考にしてください。

今回のGPUはK80でした。

Mon Aug 24 02:41:27 2020       
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 450.57       Driver Version: 418.67       CUDA Version: 10.1     |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|                               |                      |               MIG M. |
|===============================+======================+======================|
|   0  Tesla K80           Off  | 00000000:00:04.0 Off |                    0 |
| N/A   36C    P8    27W / 149W |      0MiB / 11441MiB |      0%      Default |
|                               |                      |                 ERR! |
+-------------------------------+----------------------+----------------------+
                                                                               
+-----------------------------------------------------------------------------+
| Processes:                                                                  |
|  GPU   GI   CI        PID   Type   Process name                  GPU Memory |
|        ID   ID                                                   Usage      |
|=============================================================================|
|  No running processes found                                                 |
+-----------------------------------------------------------------------------+

メルスペクトラム変換のパイプラインのコードです。

スペクトラム変換との違いですが、下記が追加されています。

ops.MelFilterBank:メル尺度を適用しています。

ops.ToDecibels:デシベル表記変換しています。

class MelSpectrogramPipeline(Pipeline):
    def __init__(self, device, batch_size, nfft, window_length, window_step, num_threads=1, device_id=0):
        super(MelSpectrogramPipeline, self).__init__(batch_size, num_threads, device_id)
        self.device = device
        
        self.batch_data = []
        y, sr = librosa.load(librosa.util.example_audio_file())
        for _ in range(batch_size):
            self.batch_data.append(np.array(y, dtype=np.float32))
        
        self.external_source = ops.ExternalSource()
        self.spectrogram = ops.Spectrogram(device=self.device, 
                                           nfft=nfft, 
                                           window_length=window_length,
                                           window_step=window_step)
        
        self.mel_fbank = ops.MelFilterBank(device=self.device,
                                           sample_rate=sr,
                                           nfilter = 128,
                                           freq_high = 8000.0)
        
        self.dB = ops.ToDecibels(device=self.device, 
                                 multiplier = 10.0,
                                 cutoff_db = -80)

    def define_graph(self):
        self.data = self.external_source()
        out = self.data.gpu() if self.device == 'gpu' else self.data
        out = self.spectrogram(out)
        out = self.mel_fbank(out)
        out = self.dB(out)
        return out

    def iter_setup(self):
        self.feed_input(self.data, self.batch_data)

メルスペクトラムのパイプラインを作成します。まずはCPUのパフォーマンスを確認します。

pipe = MelSpectrogramPipeline(device='cpu', batch_size=1, nfft=n_fft, window_length=n_fft, window_step=hop_length)
pipe.build()

下記のコードで実行して、速度を計測します。372msでした。

%%time
outputs = pipe.run()
mel_spectrogram_dali_db = outputs[0].at(0)

続いてGPUのパフォーマンスを計測するため、パイプラインを作成します。

pipe = MelSpectrogramPipeline(device='gpu', batch_size=1, nfft=n_fft, window_length=n_fft, window_step=hop_length)
pipe.build()

下記のコードを実行して、速度を計測します。6.2msでした。

%%time

outputs = pipe.run()
mel_spectrogram_dali_db = outputs[0].as_cpu().at(0)

下記のコードで結果を確認します。

librosa.display.specshow(mel_spectrogram_dali_db, sr=sr, y_axis='mel', x_axis='time', hop_length=hop_length)
plt.title('DALI Mel-frequency spectrogram')
plt.colorbar(format='%+2.0f dB')
plt.tight_layout()
plt.show()

librosaの動作と差分がないかをチェックします。1以上の絶対値エラーが出ないかをチェックします。

mel_spectrogram_librosa = librosa.feature.melspectrogram(y=y, sr=sr, n_mels=128, fmax=8000)
mel_spectrogram_librosa_db = librosa.power_to_db(mel_spectrogram_librosa, ref=np.max)
assert(np.allclose(mel_spectrogram_dali_db, mel_spectrogram_librosa_db, atol=1))

メルスペクトラム変換では60倍程度の速度向上ができました。続いてMFCCの動作確認を行います。

下記の表を見るとCPUのみ対応なのでCPU処理のみ確認します。

https://docs.nvidia.com/deeplearning/dali/user-guide/docs/supported_ops.html

下記がMFCCのコードです。メルスペクトラムとの違いはops.MFCCが追加されています。

class MFCCPipeline(Pipeline):
    def __init__(self, device, batch_size, nfft, window_length, window_step, 
                 dct_type, n_mfcc, normalize, lifter, num_threads=1, device_id=0):
        super(MFCCPipeline, self).__init__(batch_size, num_threads, device_id)
        self.device = device
        
        self.batch_data = []
        y, sr = librosa.load(librosa.util.example_audio_file())
        for _ in range(batch_size):
            self.batch_data.append(np.array(y, dtype=np.float32))
        
        self.external_source = ops.ExternalSource()
        self.spectrogram = ops.Spectrogram(device=self.device, 
                                           nfft=nfft, 
                                           window_length=window_length,
                                           window_step=window_step)
        
        self.mel_fbank = ops.MelFilterBank(device=self.device,
                                           sample_rate=sr,
                                           nfilter = 128,
                                           freq_high = 8000.0)
        
        self.dB = ops.ToDecibels(device=self.device, 
                                 multiplier = 10.0,
                                 cutoff_db = -80.0)
        
        self.mfcc = ops.MFCC(device=self.device, 
                             axis=0, 
                             dct_type=dct_type, 
                             n_mfcc=n_mfcc, 
                             normalize=normalize, 
                             lifter=lifter)

    def define_graph(self):
        self.data = self.external_source()
        out = self.data.gpu() if self.device == 'gpu' else self.data
        out = self.spectrogram(out)
        out = self.mel_fbank(out)
        out = self.dB(out)
        out = self.mfcc(out)
        return out

    def iter_setup(self):
        self.feed_input(self.data, self.batch_data)

MFCCのパイプラインを作成します。

pipe = MFCCPipeline(device='cpu', batch_size=1, nfft=n_fft, window_length=n_fft, window_step=hop_length, 
                    dct_type=2, n_mfcc=40, normalize=True, lifter=0)

動作確認をします。

%%time

pipe.build()
outputs = pipe.run()
mfccs_dali = outputs[0].at(0)

MFCCの結果画像を確認します。

plt.figure(figsize=(10, 4))
librosa.display.specshow(mfccs_dali, x_axis='time')
plt.colorbar()
plt.title('MFCC (DALI)')
plt.tight_layout()
plt.show()

librosaの動作と差分がないかをチェックします。1以上の絶対値エラーが出ないかをチェックします。

mfccs_librosa = librosa.feature.mfcc(S=mel_spectrogram_librosa_db,
                                     dct_type=2, n_mfcc=40, norm='ortho', lifter=0)
assert(np.allclose(mfccs_librosa, mfccs_dali, atol=1))

Close Bitnami banner
Bitnami