今回は、プロトコルの1つであるバッファーを配列(ndarray)に変換する関数の1つである、np.frombuffer関数について解説していきます。

Pythonが得意とする科学技術計算のアルゴリズムはNumPyもそうですが、高速化のためにCやFortranなどのコンパイルされた低レベルなコードが動いていることが多いです。このようなネイティブコードとPythonコードとをやりとりするために、Buffer Protocolという機構がPythonには備わっています。

例えば、bytesやarray.arrayなどのオブジェクトでは、Cレベルのバイト列を扱うことができるようになっています。

NumPyの関数にも、このようなバイト列を直接扱うことができます。np.frombuffer関数は、メモリのバイト列を直接読み込むため、大容量のデータをコピーせずに処理することが可能で、処理速度の高速化につながります。

np.frombuffer

まずは、この関数のAPIドキュメントから見ていきましょう。

numpy.frombuffer(buffer, dtype=float, count=-1, offset=0)

params:

パラメータ名 概要
buffer buffer_like
バッファーに相当するもの
バッファーとして読み込むオブジェクトを指定します。
dtype data-type
データ型
(省略可能)初期値float
配列を返すときの要素のデータ型を指定します。
count int (省略可能)初期値-1
いくつのアイテムを読み込むかを指定します。デフォルトの-1では全てのデータを読み込みます。
offset int (省略可能)初期値0
バイト単位で、どこの地点からデータを読み込むかを指定します。

returns:

渡されたバッファーを変換した配列を返します。

この関数は、引数bufferとして渡されたbufferを1次元配列に変換するものです。countやoffsetでデータを読み込む個数や開始点を指定できます。また、dtypeで返される配列のデータ型を指定することができます。

この関数を使うことでndarrayへの高速化が期待できるので、大容量のデータを扱う方にはおすすめです。

今回は、音声ファイル(waveファイル形式)を配列に収納する時間を比べて見ます。まずは下準備です。今回使用したファイルは、録音したステレオサウンドのデータとなります。

In [1]: import numpy as np

In [2]: import wave


In [4]: wf = wave.open('sample_sound.wav')

In [7]: channels = wf.getnchannels()

In [8]: wf.getparams()
Out[8]: _wave_params(nchannels=2, sampwidth=2, framerate=44100, nframes=5980680, comptype='NONE', compname='not compressed')

In [9]: chunk_size = wf.getnframes()

In [10]: chunk_size
Out[10]: 5980680

In [11]: data = wf.readframes(chunk_size) # まずは読み取ったファイルの全てを格納する。  

変数dataに格納したファイルデータを配列に変換していきます。ここで、np.frombufferを使ってみます。同様の関数として、np.fromiterがあるのでそれとの処理速度の差を確かめて見ます。

In [12]: %timeit data2 = np.frombuffer(data, dtype = 'int16')
1.82 µs ± 57.5 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

In [13]: %timeit data3 = np.fromiter(data, dtype='int16')
999 ms ± 29.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

np.frombufferのほうが100万倍ほど早い結果になりましたね。スケールが違いすぎてかなり戸惑うところですが、音声処理などをPythonでやりたい場合、まずはnp.frombuffer関数で配列に格納したほうが処理速度の高速化が見込めます。

参考