6.9. ユニバーサル関数

ユニバーサル関数(ufunc)は、1回の呼び出しで配列のすべての要素に適用される数学関数です。前のページの算術演算子は、演算子構文をまとったユニバーサル関数です。このページは、三角関数、指数/対数、丸め、その他いくつかを網羅する、名前付きユニバーサル関数のカタログです。

各 ufunc は、スカラー、Python のイテラブル、または ndarray を受け付け、(入力がスカラーの場合は)単一の float、または float の ndarray を返します:

from ulab import numpy as np

np.exp(2.0)                    # 7.389...
np.sin(range(4))               # 1-D float ndarray
np.sqrt([1, 4, 9, 16])         # array([1.0, 2.0, 3.0, 4.0])

a = np.arange(9).reshape((3, 3))
np.exp(a)                      # 3x3 float ndarray

6.9.1. カタログ

numpy は、組み込みアプリケーションが最もよく必要とする数学関数を提供します:

各関数は、1回のライブラリ呼び出しで配列全体を処理します。math.sin() を要素ごとに呼び出す Python のリスト内包表記に対する高速化は、典型的なバッファで10〜30倍になります。

6.9.2. out= キーワード

各 ufunc 呼び出しは通常、出力を保持するために新しい結果配列を割り当てます。1秒間に何度も実行されるループでは、これらの割り当てが積み重なり、RAM を浪費します。out= -- 入力と同じ形状で既存の float 配列 -- を渡すと、新しい配列を割り当てる代わりに、その配列へ結果を書き込みます:

x = np.linspace(0, 2 * np.pi, num=256)
y = np.zeros(256)

while True:
    np.sin(x, out=y)
    # use y ...

out の dtype や形状が誤っている場合、関数は例外を送出します。このキーワードは、このページのすべての ufunc でサポートされています。ストリーミング信号処理ループを割り当てなしに保つための、最もすっきりした方法です。

6.9.3. 2引数の ufunc

arctan2() は、上記のリストの中で唯一の真の2引数 ufunc です。y / x の象限を考慮したアークタンジェントを返し、2つのオペランドをブロードキャストします:

y = np.array([1, 2.2, 33.33, 444.444])
np.arctan2(y, 1.0)             # against a scalar
np.arctan2(1.0, y)             # the other way
np.arctan2(y, y)               # against another array

6.9.4. ユニバーサル関数の合成

ユニバーサル関数は、他の配列式と同様に合成できます。カメラ上でよく登場するいくつかのパターンを示します:

ガンマ補正(float 空間で):

gamma = 0.5
out = 255.0 * (frame / 255.0) ** gamma

単純なローパススムーザーalpha1.0 に近いほど更新が遅くなります):

alpha = 0.95
filtered = alpha * filtered + (1.0 - alpha) * sample

シグモイド:

sigmoid = 1.0 / (1.0 + np.exp(-x))

dB 単位のパワースペクトル:

spectrum = 20.0 * np.log10(np.abs(real) + 1e-12)

6.9.5. np.vectorize

通常の Python 関数は、vectorize() によって ufunc の形をした関数へと昇格させることができます。結果として得られる呼び出し可能オブジェクトは、スカラー、イテラブル、または ndarray の値を受け付けます:

def f(x):
    return x * x

vf = np.vectorize(f)

vf(44.0)                          # array([1936.0])
vf(np.array([1, 2, 3, 4]))        # array([1.0, 4.0, 9.0, 16.0])
vf([2, 3, 4])                     # array([4.0, 9.0, 16.0])

デフォルトでは結果の dtype は float です。otypes= でこれを上書きできます:

vf_u8 = np.vectorize(f, otypes=np.uint8)
vf_u8([1, 2, 3, 4])
# array([1, 4, 9, 16], dtype=uint8)

Python 関数は単一の引数を取り、単一の数値を返さなければなりません。

vectorize() はほとんど構文上のものです。ラップされた Python 関数は依然として要素ごとに1回実行する必要があるため、真の ufunc が回避する要素ごとのインタプリタコストの大部分が戻ってきます。真のユニバーサル関数の30倍ではなく、リスト内包表記に対して30%〜50%程度の控えめな高速化を見込んでください。これは、1つの関数を同じ名前でスカラー、リスト、そして配列に対して動作させる必要がある場合に適したツールであり、生の速度が目的の場合には適しません。

上記に列挙したすべてのユニバーサル関数の完全な呼び出しシグネチャについては、numpy --- numpy互換の配列操作 を参照してください。