[python] NumPy 배열의 모든 셀에서 효율적인 함수 평가

NumPy 배열 A가 주어지면 동일한 함수 f모든 셀 에 적용하는 가장 빠르고 효율적인 방법은 무엇입니까?

  1. A (i, j)f (A (i, j)) 를 할당한다고 가정합니다 .

  2. 함수 f 에는 바이너리 출력이 없으므로 mask (ing) 연산이 도움이되지 않습니다.

“명백한”이중 루프 반복 (모든 셀)이 최적의 솔루션입니까?



답변

벡터화 할 수 있습니다. 기능을 다음 NumPy와 배열에 직접 당신이 그것을 필요로 할 때마다 적용 :

import numpy as np

def f(x):
    return x * x + 3 * x - 2 if x > 0 else x * 5 + 8

f = np.vectorize(f)  # or use a different name if you want to keep the original f

result_array = f(A)  # if A is your Numpy array

벡터화 할 때 명시 적 출력 유형을 직접 지정하는 것이 좋습니다.

f = np.vectorize(f, otypes=[np.float])


답변

비슷한 질문은 다음과 같습니다 : NumPy 배열을 제자리에 매핑 . f ()에 대한 ufunc 를 찾을 수 있다면 out 매개 변수를 사용해야합니다.


답변

당신이 숫자와 협력하는 경우 f(A(i,j)) = f(A(j,i)), 당신은 사용할 수 scipy.spatial.distance.cdist 사이의 거리로 정의 F를 A(i)하고 A(j).


답변

더 나은 해결책을 찾았다 고 생각합니다. 함수를 파이썬 범용 함수로 변경하는 아이디어 ( 문서 참조 ). 내부에서 병렬 계산을 실행할 수 있습니다.

ufuncC로 커스터마이징 된 자신 만의 코드를 작성할 수 있는데, 이는 확실히 더 효율적이거나 np.frompyfunc내장 된 팩토리 메소드 인 을 호출하여 작성할 수 있습니다 . 테스트 후 다음보다 더 효율적입니다 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 f_arr(arr, arr) # 450ms

나는 또한 더 큰 샘플을 테스트했으며 개선은 비례합니다. 다른 방법의 성능 비교는 이 게시물을 참조하십시오.


답변

2d-array (또는 nd-array)가 C- 또는 F- 연속적 일 때, 함수를 2d-array에 매핑하는이 작업은 1d-array에 함수를 매핑하는 작업과 실질적으로 동일합니다. 예를 들어 np.ravel(A,'K') .

1d-array에 대한 가능한 솔루션은 여기 에서 예를 들어 논의되었습니다 .

그러나 2d 배열의 메모리가 연속적이지 않으면 상황이 조금 더 복잡해집니다. 축이 잘못된 순서로 처리 될 경우 가능한 캐시 미스를 피하고 싶기 때문입니다.

Numpy는 이미 최상의 순서로 축을 처리하는 기계를 갖추고 있습니다. 이 기계를 사용할 수있는 한 가지 가능성은 np.vectorize. 그러나 numpy의 문서에 np.vectorize따르면 “성능이 아닌 편의를 위해 주로 제공”됩니다. 느린 파이썬 함수는 전체 관련 오버 헤드와 함께 느린 파이썬 함수로 유지됩니다! 또 다른 문제는 엄청난 메모리 소비입니다. 예를 들어이 SO-post를 참조하십시오 .

C 함수의 성능을 원하지만 numpy의 기계를 사용하려는 경우 좋은 해결책은 ufuncs 생성에 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

쉽게 이길 수 np.vectorize있지만 동일한 기능이 numpy-array 곱셈 / 덧셈과 같이 수행 될 때도 마찬가지입니다.

# 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"

시간 측정 코드에 대해서는이 답변의 부록을 참조하십시오.

여기에 이미지 설명 입력

Numba의 버전 (녹색)은 python-function (예 :)보다 약 100 배 빠릅니다 np.vectorize. 이는 놀라운 일이 아닙니다. 그러나 numbas 버전은 중간 배열이 필요하지 않으므로 캐시를 더 효율적으로 사용하기 때문에 numpy- 기능보다 약 10 배 더 빠릅니다.


numba의 ufunc 접근 방식은 유용성과 성능 사이의 좋은 절충안이지만 여전히 우리가 할 수있는 최선은 아닙니다. 그러나 모든 작업에 가장 적합한 은색 총알이나 접근 방식은 없습니다. 한계가 무엇이며 어떻게 완화 될 수 있는지 이해해야합니다.

예를 들어, 초월 함수 (예를 들어 exp, sin, cos) numba을 통해 어떤 이점을 제공하지 않습니다 NumPy와의 np.exp(생성 된 임시 배열이없는 – 속도 업의 주요 소스). 그러나 내 Anaconda 설치는 8192보다 큰 벡터에 대해 Intel의 VML을 사용 합니다. 메모리가 인접하지 않으면 사용할 수 없습니다. 따라서 Intel의 VML을 사용할 수 있으려면 요소를 인접한 메모리에 복사하는 것이 좋습니다.

import numba as nb
@nb.vectorize(target="cpu")
def nb_vexp(x):
    return np.exp(x)

def np_copy_exp(x):
    copy = np.ravel(x, 'K')
    return np.exp(copy).reshape(x.shape) 

비교의 공정성을 위해 VML의 병렬화를 해제했습니다 (부록의 코드 참조).

여기에 이미지 설명 입력

보시다시피 VML이 시작되면 복사 오버 헤드가 보상되는 것 이상입니다. 그러나 데이터가 L3 캐시에 비해 너무 커지면 작업이 다시 한 번 메모리 대역폭에 제한되므로 이점이 최소화됩니다.

반면에 numba는 이 게시물에 설명 된대로 Intel의 SVML도 사용할 수 있습니다 .

from llvmlite import binding
# set before import
binding.set_option('SVML', '-vector-library=SVML')

import numba as nb

@nb.vectorize(target="cpu")
def nb_vexp_svml(x):
    return np.exp(x)

병렬화와 함께 VML을 사용하면 다음이 생성됩니다.

여기에 이미지 설명 입력

numba의 버전은 오버 헤드가 적지 만, 일부 크기의 경우 추가 복사 오버 헤드에도 불구하고 VML이 SVML을 능가합니다. 이는 numba의 ufunc가 병렬화되지 않기 때문에 그리 놀라운 일이 아닙니다.


목록 :

A. 다항 함수 비교 :

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

B. 비교 exp:

import perfplot
import numexpr as ne # using ne is the easiest way to set vml_num_threads
ne.set_vml_num_threads(1)
perfplot.show(
    setup=lambda n: np.random.rand(n,n)[::2,::2],
    n_range=[2**k for k in range(0,12)],
    kernels=[
        nb_vexp,
        np.exp,
        np_copy_exp,
        ],
    logx=True,
    logy=True,
    xlabel='len(x)',
    )


답변

위의 모든 답변은 잘 비교되지만 매핑을 위해 사용자 정의 함수를 사용해야하고을 가지고 numpy.ndarray있고 배열의 모양을 유지해야하는 경우.

두 개만 비교했지만 ndarray. 비교를 위해 1 백만 개의 항목이있는 배열을 사용했습니다. 여기에서는 제곱 함수를 사용합니다. n 차원 배열에 대한 일반적인 사례를 제시하고 있습니다. 2 차원의 iter경우 2D로 만듭니다.

import numpy, time

def A(e):
    return e * e

def timeit():
    y = numpy.arange(1000000)
    now = time.time()
    numpy.array([A(x) for x in y.reshape(-1)]).reshape(y.shape)
    print(time.time() - now)
    now = time.time()
    numpy.fromiter((A(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사용자 평방 함수를 명확하게 볼 수 있으며 원하는 것을 사용하십시오. 당신에 의존하는 기능을 경우 i, j 그 반복 처리 같은 배열의 크기, 배열의 인덱스이며 for ind in range(arr.size), 사용 numpy.unravel_index얻을 i, j, ..배열의 당신의 1D 지수와 모양에 따라 numpy.unravel_index

이 답변은 여기 다른 질문에 대한 제 답변에서 영감을 얻었습니다.


답변