目次
画像データに対するDALIの適用
以前、画像データをDALIで高速化できるか試してみました。
音声データも処理できるようなので試してみました。
https://github.com/NVIDIA/DALI/blob/master/docs/examples/audio_processing/spectrogram.ipynb
音声データは今後、需要が高まる分野だ
先行してやっておくとドヤれる可能性が!!
環境構築
今回もGoogle Colaboratoryで動作確認します。Google Colaboratoryの使用方法は下記の記事をご覧ください。
実際のコード
音声ファイルを扱うために必要なライブラリをインポートします。
import librosa as librosa
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
import librosa.display
import IPython.display as ipd
Exampleの音声データファイルを取得して、音声ファイルの波形を確認、音声ファイルを聞いて確認できるようにします。
y, sr = librosa.load(librosa.util.example_audio_file())
plt.figure(figsize=(14, 5))
librosa.display.waveplot(y, sr=sr)
ipd.Audio(y, rate=sr)
上手く動作すれば下記のようになります。

まずは音声の処理で一般的なスペクトログラムの処理をCPUで行います。変換時間は104 msでした。
- n_fft:ウィンドウ幅
- hop_length:スライド幅
- librosa.stft(y, n_fft=n_fft, hop_length=hop_length, win_length=n_fft, window=’hann’):短時間フーリエ変換処理を行っています。時間幅をwin_lengthで設定しています。この値が大きいほど対象となる音声データが広くなります。スライド幅をhop_lengthで設定しています。この値が小さいほど、重複が多くなります。windowでフーリエ変換時の窓の種類を設定しています。内部でscipyのget_windowをラップして呼んでいてるため、この関数でサポートしている窓を呼べます。詳細はこちら
%%time
# Size of the FFT, which will also be used as the window length
n_fft=2048
# Step or stride between windows. If the step is smaller than the window lenght, the windows will overlap
hop_length=512
# Calculate the spectrogram as the square of the complex magnitude of the STFT
spectrogram_librosa = np.abs(librosa.stft(
y, n_fft=n_fft, hop_length=hop_length, win_length=n_fft, window='hann')) ** 2
librosa.power_to_db(spectrogram_librosa, ref=np.max):スペクトル表現をデシベル表現に変換しています。
spectrogram_librosa_db = librosa.power_to_db(spectrogram_librosa, ref=np.max)
下記のコードでスペクトログラムを確認します。
librosa.display.specshow(spectrogram_librosa_db, sr=sr, y_axis='log', x_axis='time', hop_length=hop_length)
plt.title('Reference power spectrogram')
plt.colorbar(format='%+2.0f dB')
plt.tight_layout()
plt.show()

DALIでGPUにより高速化して動作確認
使用するGPUを確認します。今回はT4のGPUです。
Fri Aug 21 04:56:39 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 T4 Off | 00000000:00:04.0 Off | 0 |
| N/A 49C P8 10W / 70W | 0MiB / 15079MiB | 0% Default |
| | | ERR! |
+-------------------------------+----------------------+----------------------+
+-----------------------------------------------------------------------------+
| Processes: |
| GPU GI CI PID Type Process name GPU Memory |
| ID ID Usage |
|=============================================================================|
| No running processes found |
+-----------------------------------------------------------------------------+
DALIはGoogle Colaboratoryにデフォルトで入っていないため、pipで導入します。
!pip install --extra-index-url https://developer.download.nvidia.com/compute/redist nvidia-dali-cuda100
スペクトログラム用のDALIパイプラインを作成します。
- self.device = device:CPUかGPUか使用するデバイスを設定できます。
- y, sr = librosa.load(librosa.util.example_audio_file()):音声データを読み込んでいます。
- ops.ExternalSource():DALIのパイプラインで外部のデータを使用するための設定です。
- ops.Spectrogram:DALIで用意されているスペクトログラム作成のコードです。これによって使用デバイスをCPUもしくはGPUに変えられます。
- def define_graph:ここでデータのパイプラインを作成します。データを取得して、CPUもしくはGPUに変換してスペクトログラム作成を行います。
from nvidia.dali.pipeline import Pipeline
import nvidia.dali.ops as ops
import nvidia.dali.types as types
import nvidia.dali as dali
class SpectrogramPipeline(Pipeline):
def __init__(self, device, batch_size, nfft, window_length, window_step, num_threads=1, device_id=0):
super(SpectrogramPipeline, 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)
def define_graph(self):
self.data = self.external_source()
out = self.data.gpu() if self.device == 'gpu' else self.data
out = self.spectrogram(out)
return out
def iter_setup(self):
self.feed_input(self.data, self.batch_data)
DALIのパイプラインのインスタンスを作成します。deviceはGPUを指定しています。
pipe = SpectrogramPipeline(device='gpu', batch_size=1, nfft=n_fft, window_length=n_fft, window_step=hop_length)
pipe.build:DALIパイプラインを作成します。
pipe.run:DALIパイプラインを実行します。
outputs[0].as_cpu().at(0):GPUにデータがあるため、CPUにデータを移動します。
librosa.power_to_db(spectrogram_dali, ref=np.max):デシベル表記に変換します。
pipe.build()
outputs = pipe.run()
spectrogram_dali = outputs[0].as_cpu().at(0)
spectrogram_dali_db = librosa.power_to_db(spectrogram_dali, ref=np.max)
ベンチマークを行うため、もう一度、動作を行います。実行時間は55msでした。
%%time
pipe.build()
outputs = pipe.run()
spectrogram_dali = outputs[0].as_cpu().at(0)
spectrogram_dali_db = librosa.power_to_db(spectrogram_dali, ref=np.max)
CPUにデータを移す処理があるので、それを除いた速度を確認します。速度は3.05msでした。
%%time
pipe.build()
outputs = pipe.run()
spectrogram_dali = outputs[0]
CPUとGPUで処理した際のデータの差分がないかを確認します。平均誤差は 0.00359 dBでした。
print("Average error: {0:.5f} dB".format(np.mean(np.abs(spectrogram_dali_db - spectrogram_librosa_db))))
assert(np.allclose(spectrogram_dali_db, spectrogram_librosa_db, atol=2))
結果をまとめると下記になります。
- CPU:104 ms
- GPUで処理、CPUへ移動:55ms
- GPUで処理:3.05ms
CPUへデータ移動をしても2倍、処理時間だけであれば33倍程度、高速化したぞ