今回は、PythonでNumpyのベクタライズを学びます。
NumpyはPythonにおける配列のC言語実装で、同じPythonインタプリタでありながら比較的高速に動作します。
今回は、numpy.vectorize()を使って、配列に関数を要素ごとに適用する方法を探ります。
なぜforループはNumpyのベクタライゼーションに最適ではないのか?
リストやタプル、NumPyの配列の要素に関数を適用するには、Python の for ループ を簡単に利用することができます。
しかし、Pythonはインタプリタ型言語であり、CやC++と比較すると、ほとんどの実装が遅いです。
この遅い計算の主な理由は、Pythonの動的な性質と、メモリのオーバーヘッドを発生させるコンパイラレベルの最適化がないことに起因しています。
これは、Pythonを巨大な計算のために使用する人々にとって理想的な状況とは言えません。
NumPyはより高速な実装を提供していますが、forループはNumPyが提供するスピードの一部を奪っています。
このボトルネックに対処するために、NumPy はベクトル化機能を提供し、関数が効率的にシーケンスにマッピングされるようにした。
numpy.vectorize() 関数でNumpyのベクタライズを行う。
Numpyのvectorize関数はpythonの関数を取り込み、その関数をベクトル化したものを返します。
ベクトル化されたバージョンの関数は、オブジェクトのシーケンスまたはNumPyの配列を入力として取り、入力シーケンスの各要素に対してPython関数を評価します。
Numpyのベクトル化は、基本的には python map()のような機能ですが、 NumPy broadcastingというメカニズムが追加されています。
それでは、numpy.vectorize()関数をより詳しく理解しましょう。
numpy.vectorize(pyfunc, otypes= None , doc = None , excluded = None , cache = False , signature = None)
- otypes : 関数の出力型は、文字列またはデータ型のリストとして指定することができる。otypes が指定されず、キャッシュが True* に設定されている場合、出力タイプは入力の最初の要素を呼び出すことによって決定されます。
- doc : 作成されたdocstringを指定します。指定しない場合、関数のオリジナルのdocstring(もしあれば)が使用されます。
- cache : trueの場合、otypes*が提供されない場合、出力数を決定する最初の関数呼び出しをキャッシュします。
関数のベクトル化
def foo(a, b):
"""
If a > b return a + b,
else return a - b.
"""
if a > =b:
return a + b
else:
return a - b
# Create a vectorized version of foo
vecfoo = np.vectorize(foo)
vecfoo(np.arange(5),5)
array([- 5 , - 4 , - 3 , - 2 , - 1])
Numpyはotypesパラメータがfalseに設定されている場合、関数の出力型を自動的に評価します。
以下はそれを紹介する例です。
a = np.array([1 , 2 , 3 , 4])
b = 2
vecfoo = np.vectorize(foo)
res = vecfoo(a, b)
print(type(res[0]))
また、戻り値のデータ型を強制することで、ベクトル化関数の出力を制御することができます。
以下はその例です。
a = np.array([1 , 2 , 3 , 4])
b = 2
vecfoo = np.vectorize(foo, otypes= [ float])
res = vecfoo(a, b)
print(type(res[0]))
この記事もチェック:NumpyでPythonの関数をベクトル化する方法
Numpy のベクトル化におけるキャッシュについて
もし optypes が指定されていない場合、関数は入力の最初の引数を呼び出して入力の数を決定することを既に見ました。
この結果はキャッシュすることができるので、関数が同じ処理を何度も実行するのを防ぐことができます。
しかし,キャッシュを実装すると,その後の呼び出しが遅くなるので,関数の評価に計算が必要な場合のみ使用する必要があります.キャッシュは、パラメータ cache を True に設定することにより、設定することができる。
まとめ
np.vectorize() だけでなく、NumPy の日常的な操作でずっとベクトル化を使っています。
例えば、以下のように追加してみましょう。
np.arange(5)+ 4
array([4 , 5 , 6 , 7 , 8])
同じルールは、引き算、掛け算、sin、cosなどの異なるプリミティブ関数にも当てはまります。
これらの関数には、ベクトル化のサポートが組み込まれています。
しかし、私たちが使っているPythonは一般的にこのようなベクトル化をサポートしていないので、高速かつ効率的にベクトル化された操作を行うにはnumpy.vectorize()が必要です。