numpy配列に関数をマッピングする最も効率的な方法


337

numpy配列に関数をマップする最も効率的な方法は何ですか?私の現在のプロジェクトでそれをやってきた方法は次のとおりです:

import numpy as np 

x = np.array([1, 2, 3, 4, 5])

# Obtain array of square of each element in x
squarer = lambda t: t ** 2
squares = np.array([squarer(xi) for xi in x])

しかし、これはおそらく非常に非効率的であるように思われます。リスト内包表記を使用して新しい配列をPythonリストとして作成し、それをnumpy配列に変換して戻すためです。

もっと上手くできる?


10
なぜ「squares = x ** 2」ではないのですか?評価する必要があるはるかに複雑な関数はありますか?
22度2016

4
どうsquarer(x)ですか?
ライフ

1
多分これは質問に直接答えていませんが、numbaは既存のpythonコードを並列マシン命令にコンパイルできると聞いたことがあります。私は実際にそれを使用する機会があったときに、この投稿を再訪して改訂します。
把友情留在無盐

x = np.array([1, 2, 3, 4, 5]); x**2作品
Shark Deng

回答:


281

私は提案されたすべての方法np.array(map(f, x))perfplot(私の小さなプロジェクト)をテストしました。

メッセージ#1:numpyのネイティブ関数を使用できる場合は、それを実行します。

すでにベクトル化しようとしている機能が場合(のようなベクトル化x**2つまり使用して、オリジナルのポストの例)非常に速く、何よりも(対数目盛に注意してください):

ここに画像の説明を入力してください

実際にベクトル化が必要な場合、どのバリアントを使用してもそれほど問題にはなりません。

ここに画像の説明を入力してください


プロットを再現するコード:

import numpy as np
import perfplot
import math


def f(x):
    # return math.sqrt(x)
    return np.sqrt(x)


vf = np.vectorize(f)


def array_for(x):
    return np.array([f(xi) for xi in x])


def array_map(x):
    return np.array(list(map(f, x)))


def fromiter(x):
    return np.fromiter((f(xi) for xi in x), x.dtype)


def vectorize(x):
    return np.vectorize(f)(x)


def vectorize_without_init(x):
    return vf(x)


perfplot.show(
    setup=lambda n: np.random.rand(n),
    n_range=[2 ** k for k in range(20)],
    kernels=[f, array_for, array_map, fromiter, vectorize, vectorize_without_init],
    xlabel="len(x)",
)

7
あなたf(x)はあなたの陰謀を去ったようです。すべてのfに適用できるわけではありませんが、ここで適用できます。適用可能な場合、それは簡単に最速のソリューションです。
user2357112は

2
また、あなたのプロットはvf = np.vectorize(f); y = vf(x)、短い入力で勝つというあなたの主張をサポートしていません。
user2357112は

pip()を介してperfplot(v0.3.2)をインストールした後、サンプルコードを貼り付けると次のpip install -U perfplotメッセージが表示されAttributeError: 'module' object has no attribute 'save'ます。
tsherwen、

バニラのforループはどうですか?
Catiger3331

1
@Vladはコメントされたとおりに単にmath.sqrtを使用します。
NicoSchlömer2018年

138

使用についてはどうですかnumpy.vectorize

import numpy as np
x = np.array([1, 2, 3, 4, 5])
squarer = lambda t: t ** 2
vfunc = np.vectorize(squarer)
vfunc(x)
# Output : array([ 1,  4,  9, 16, 25])

36
これはこれ以上効率的ではありません。
user2357112は、モニカ2016

78
そのドキュメントから:The vectorize function is provided primarily for convenience, not for performance. The implementation is essentially a for loop. 他の質問でvectorizeは、ユーザーの反復速度が2倍になる可能性があることがわかりました。しかし、本当のスピードアップは、本当のnumpy配列操作です。
hpaulj 2016

2
vectorizeは少なくとも1次元以外の配列で機能することに注意してください
Eric

しかし、squarer(x)1D以外の配列ではすでに機能します。vectorize実際には、リストの理解力(問題の質問のようなもの)よりも優れており、を超えていませんsquarer(x)
user2357112は

79

TL; DR

によって指摘されたように @ user2357112で、関数を適用する「直接」方法は、常にNumpy配列に関数をマップするための最も速くて最も簡単な方法です。

import numpy as np
x = np.array([1, 2, 3, 4, 5])
f = lambda x: x ** 2
squares = f(x)

一般的に避ける np.vectorizeパフォーマンスがよくなく、多くの問題がある(またはあった)、ます。他のデータ型を処理している場合は、以下に示す他の方法を調査することをお勧めします。

メソッドの比較

3つのメソッドを比較して関数をマップする簡単なテストをいくつか示します。この例では、Python 3.6とNumPy 1.15.4を使用しています。まず、テスト用のセットアップ関数:

import timeit
import numpy as np

f = lambda x: x ** 2
vf = np.vectorize(f)

def test_array(x, n):
    t = timeit.timeit(
        'np.array([f(xi) for xi in x])',
        'from __main__ import np, x, f', number=n)
    print('array: {0:.3f}'.format(t))

def test_fromiter(x, n):
    t = timeit.timeit(
        'np.fromiter((f(xi) for xi in x), x.dtype, count=len(x))',
        'from __main__ import np, x, f', number=n)
    print('fromiter: {0:.3f}'.format(t))

def test_direct(x, n):
    t = timeit.timeit(
        'f(x)',
        'from __main__ import x, f', number=n)
    print('direct: {0:.3f}'.format(t))

def test_vectorized(x, n):
    t = timeit.timeit(
        'vf(x)',
        'from __main__ import x, vf', number=n)
    print('vectorized: {0:.3f}'.format(t))

5つの要素を使用したテスト(最も速いものから最も遅いものへのソート):

x = np.array([1, 2, 3, 4, 5])
n = 100000
test_direct(x, n)      # 0.265
test_fromiter(x, n)    # 0.479
test_array(x, n)       # 0.865
test_vectorized(x, n)  # 2.906

要素が数百あります:

x = np.arange(100)
n = 10000
test_direct(x, n)      # 0.030
test_array(x, n)       # 0.501
test_vectorized(x, n)  # 0.670
test_fromiter(x, n)    # 0.883

そして、1000以上の配列要素がある場合:

x = np.arange(1000)
n = 1000
test_direct(x, n)      # 0.007
test_fromiter(x, n)    # 0.479
test_array(x, n)       # 0.516
test_vectorized(x, n)  # 0.945

Python / NumPyとコンパイラの最適化のバージョンが異なると結果も異なるため、環境に対して同様のテストを行ってください。


2
count引数とジェネレータ式を使用すると、処理np.fromiter速度が大幅に向上します。
juanpa.arrivillaga 2017年

3
たとえば、次のように使用します'np.fromiter((f(xi) for xi in x), x.dtype, count=len(x))'
juanpa.arrivillaga 2017年

4
あなたはの直接解テストしていないf(x)一桁を超えることにより、他のすべてを打ちます
user2357112は、

4
f2つの変数があり、配列が2Dである場合はどうでしょうか?
Sigur 2018年

2
OPが配列全体に関数を「マッピング」する方法を尋ねたときに、「f(x)」バージョン(「直接」)が実際に同等と見なされる方法と混同されていますか?f(x)= x ** 2の場合、**は要素ごとではなく、配列全体に対してnumpyによって実行されます。たとえば、f(x)が 'lambda x:x + x "の場合、numpyは要素ごとの追加ではなく配列を連結するため、答えは大きく異なります。これは本当に意図された比較ですか?
Andrew Mellinger

49

numexprnumba、およびcythonがあります。この回答の目的は、これらの可能性を考慮することです。

しかし、最初に明白なことを述べましょう。Python関数をnumpy配列にどのようにマップしても、それはPython関数のままであり、すべての評価に対して意味があります。

  • numpy-array要素はPythonオブジェクト(例: Float
  • すべての計算はPythonオブジェクトで行われます。つまり、インタープリター、動的ディスパッチ、および不変オブジェクトのオーバーヘッドが発生します。

そのため、実際に配列をループするために使用される機構は、上記のオーバーヘッドのため、大きな役割を果たすことはありません。numpyの組み込み機能を使用するよりもずっと遅いままです。

次の例を見てみましょう。

# numpy-functionality
def f(x):
    return x+2*x*x+4*x*x*x

# python-function as ufunc
import numpy as np
vf=np.vectorize(f)
vf.__name__="vf"

np.vectorizepure-python関数クラスのアプローチの代表として選択されています。を使用してperfplot(この回答の付録のコードを参照)、次の実行時間を取得します。

ここに画像の説明を入力してください

numpy-approachは、純粋なpythonバージョンよりも10倍から100倍速いことがわかります。配列サイズが大きくなるとパフォーマンスが低下するのは、おそらくデータがキャッシュに収まらないためです。

これも言及する価値vectorizeがあります。これも多くのメモリを使用するため、メモリ使用量がボトルネックになることがよくあります(関連するSO質問を参照)。また、そのnumpyのドキュメントnp.vectorizeは、「パフォーマンスではなく、主に利便性のために提供されている」と記載されています。

他のツールを使用する必要があります。パフォーマンスが必要な場合は、C拡張を最初から作成する以外に、次の可能性があります。


それはボンネットの下で純粋なCであるので、派手なパフォーマンスはそれが得られるのと同じくらい良いとよく聞かれます。しかし、改善の余地はたくさんあります!

ベクトル化されたnumpy-versionは、多くの追加メモリとメモリアクセスを使用します。numexp-libraryはnumpy-arrayを並べて表示し、キャッシュの使用率を向上させます。

# less cache misses than numpy-functionality
import numexpr as ne
def ne_f(x):
    return ne.evaluate("x+2*x*x+4*x*x*x")

次の比較につながります。

ここに画像の説明を入力してください

上記のプロットですべてを説明することはできません。最初にnumexpr-libraryのオーバーヘッドが大きくなっているのがわかりますが、キャッシュの利用効率が高いため、配列が大きいほど約10倍速くなります。


別のアプローチは、関数をjitコンパイルして、実際の純粋なC UFuncを取得することです。これはnumbaのアプローチです:

# runtime generated C-function as ufunc
import numba as nb
@nb.vectorize(target="cpu")
def nb_vf(x):
    return x+2*x*x+4*x*x*x

元のnumpyアプローチよりも10倍高速です。

ここに画像の説明を入力してください


ただし、タスクは非常に並列化可能prangeであるため、ループを並列で計算するために使用することもできます。

@nb.njit(parallel=True)
def nb_par_jitf(x):
    y=np.empty(x.shape)
    for i in nb.prange(len(x)):
        y[i]=x[i]+2*x[i]*x[i]+4*x[i]*x[i]*x[i]
    return y

予想どおり、並列関数は入力が小さい場合は遅くなりますが、サイズが大きい場合は速くなります(ほぼ2)。

ここに画像の説明を入力してください


numbaはnumpy配列を使用した操作の最適化を専門としていますが、Cythonはより一般的なツールです。numbaと同じパフォーマンスを抽出するのはより複雑です-多くの場合、llvm(numba)とローカルコンパイラ(gcc / MSVC)の違いになります。

%%cython -c=/openmp -a
import numpy as np
import cython

#single core:
@cython.boundscheck(False) 
@cython.wraparound(False) 
def cy_f(double[::1] x):
    y_out=np.empty(len(x))
    cdef Py_ssize_t i
    cdef double[::1] y=y_out
    for i in range(len(x)):
        y[i] = x[i]+2*x[i]*x[i]+4*x[i]*x[i]*x[i]
    return y_out

#parallel:
from cython.parallel import prange
@cython.boundscheck(False) 
@cython.wraparound(False)  
def cy_par_f(double[::1] x):
    y_out=np.empty(len(x))
    cdef double[::1] y=y_out
    cdef Py_ssize_t i
    cdef Py_ssize_t n = len(x)
    for i in prange(n, nogil=True):
        y[i] = x[i]+2*x[i]*x[i]+4*x[i]*x[i]*x[i]
    return y_out

Cythonの結果、機能が多少遅くなります。

ここに画像の説明を入力してください


結論

明らかに、1つの関数のみをテストしても何も証明されません。また、選択した関数の例では、メモリの帯域幅が10 ^ 5要素より大きいサイズのボトルネックであったことにも注意してください。この領域では、numba、numexpr、およびcythonで同じパフォーマンスが得られました。

結局のところ、最終的な答えは、機能の種類、ハードウェア、Pythonの配布、その他の要因によって異なります。たとえば、Anacondaディストリビューションは、numpyの関数にIntelのVMLを使用しているため、numbaよりも優れています(SVMLを使用しない限り、これを参照してください) SO-ポストを)簡単のような超越関数のためにexpsincosおよび類似-例えば以下を参照してくださいSO-ポストを

しかし、この調査とこれまでの私の経験から、超越関数が含まれていない限り、numbaは最高のパフォーマンスを発揮する最も簡単なツールであるように思われます。


perfplot -package を使用して実行時間をプロットする:

import perfplot
perfplot.show(
    setup=lambda n: np.random.rand(n),
    n_range=[2**k for k in range(0,24)],
    kernels=[
        f, 
        vf,
        ne_f, 
        nb_vf, nb_par_jitf,
        cy_f, cy_par_f,
        ],
    logx=True,
    logy=True,
    xlabel='len(x)'
    )

1
Numbaは通常Intel SVMLを利用できますが、これはIntel VMLと比較してかなり同等のタイミングになりますが、実装はバージョン(0.43-0.47)で少しバグがあります。 cy_expsumと比較するために、パフォーマンスプロットstackoverflow.com/a/56939240/4045774を追加しました。
max9111

29
squares = squarer(x)

配列の算術演算は要素ごとに自動的に適用され、Pythonレベルのループまたは内包表記に適用されるすべてのインタープリターオーバーヘッドを回避する効率的なCレベルのループを備えています。

要素ごとにNumPy配列に適用するほとんどの関数は機能しますが、変更が必要な場合もあります。たとえば、if要素ごとに機能しません。これらを次のような構成を使用するように変換する必要がありますnumpy.where

def using_if(x):
    if x < 5:
        return x
    else:
        return x**2

なる

def using_where(x):
    return numpy.where(x < 5, x, x**2)

8

私はnumpyの新しいバージョン(私は1.13を使用)を信じています。スカラー型用に記述した関数にnumpy配列を渡すだけで関数を呼び出すことができ、numpy配列の各要素に関数呼び出しが自動的に適用されて返されます別の派手な配列

>>> import numpy as np
>>> squarer = lambda t: t ** 2
>>> x = np.array([1, 2, 3, 4, 5])
>>> squarer(x)
array([ 1,  4,  9, 16, 25])

3
これはリモートで新しくはありません-常にそうでした-それはnumpyのコア機能の1つです。
エリック

8
**各要素tに計算を適用するのは演算子ですt。それは普通の派手なことです。それをでラップしてlambdaも、余分なことはありません。
hpaulj

これは、現在表示されているifステートメントでは機能しません。
TriHard8

8

多くの場合、numpy.apply_along_axisが最適です。他のアプローチと比較して、パフォーマンスが約100倍向上します。また、単純なテスト関数だけでなく、numpyやscipyからのより複雑な関数構成でもパフォーマンスが向上します。

メソッドを追加すると:

def along_axis(x):
    return np.apply_along_axis(f, 0, x)

perfplotコードに、私は次の結果を得ます: ここに画像の説明を入力してください


素晴らしいトリック!
フェリペSSシュナイダー

私は非常に....非常に多くの年のため、ほとんどの人が、このシンプルを意識するようには見えないという事実についてショックを受け、スケーラブルで、内蔵誰でも思いつきそうしています
ビル・黄

7

ufuncnumpyパッケージで生成する組み込みのファクトリーメソッドについて誰も言及していないようです:np.frompyfunc私はもう一度テストしnp.vectorize、それを約20〜30%アウトパフォームしました。もちろん、規定されたCコードまたはnumba(テストしていません)としても十分に機能しますが、np.vectorize

f = lambda x, y: x * y
f_arr = np.frompyfunc(f, 2, 1)
vf = np.vectorize(f)
arr = np.linspace(0, 1, 10000)

%timeit f_arr(arr, arr) # 307ms
%timeit vf(arr, arr) # 450ms

大きなサンプルもテストしましたが、改善は比例しています。こちらのドキュメントもご覧ください


1
上記のタイミングテストを繰り返したところ、約30%のパフォーマンスの改善(np.vectorizeを超える)も見つかりました。
BrainAnnex.org -ジュリアン

2

で述べたように この投稿でように、次のようなジェネレータ式を使用します。

numpy.fromiter((<some_func>(x) for x in <something>),<dtype>,<size of something>)

2

上記のすべての答えはよく比較されますが、マッピングにカスタム関数を使用する必要があり、 numpy.ndarray、あり、があり、配列の形状を保持する必要がある場合。

2つだけ比較しましたが、の形状は保持されndarrayます。比較のために100万エントリの配列を使用しました。ここでは正方形関数を使用します。これもnumpyに組み込まれており、パフォーマンスが大幅に向上しています。何かが必要だったので、任意の関数を使用できます。

import numpy, time
def timeit():
    y = numpy.arange(1000000)
    now = time.time()
    numpy.array([x * x for x in y.reshape(-1)]).reshape(y.shape)        
    print(time.time() - now)
    now = time.time()
    numpy.fromiter((x * x for x in y.reshape(-1)), y.dtype).reshape(y.shape)
    print(time.time() - now)
    now = time.time()
    numpy.square(y)  
    print(time.time() - now)

出力

>>> timeit()
1.162431240081787    # list comprehension and then building numpy array
1.0775556564331055   # from numpy.fromiter
0.002948284149169922 # using inbuilt function

ここではnumpy.fromiter、単純なアプローチを考慮した素晴らしい作品をはっきりと見ることができます。組み込みの機能が利用可能な場合は、それを使用してください。


弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.