音声データをDALIで高速に処理できるようなので試した

画像データに対する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倍程度、高速化したぞ

Close Bitnami banner
Bitnami