前回の記事でスペクトラム変換は高速に処理できることが確認できました。音声処理ではスペクトラム変換以外にメル尺度に基づいたスペクトラム変換や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))