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])

그러나 이것은 목록 이해를 사용하여 새 배열을 numpy 배열로 다시 변환하기 전에 Python 목록으로 구성하기 때문에 아마도 매우 비효율적 인 것 같습니다.

더 잘할 수 있을까요?


10
왜 "사각 = x ** 2"가 아닌가? 평가해야 할 훨씬 더 복잡한 기능이 있습니까?
22degrees

4
어때요 squarer(x)?
생활

1
어쩌면 이것은 직접 질문에 대답하지는 않지만 numba 는 기존 파이썬 코드를 병렬 컴퓨터 명령어로 컴파일 할 수 있다고 들었습니다 . 이 게시물을 실제로 사용할 기회가있을 때이 게시물을 다시 방문하고 수정하겠습니다.
把 友情 留 在 无 盐

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는 Monica

2
또한, 줄거리는 vf = np.vectorize(f); y = vf(x)짧은 입력에 대해이기는 주장을 지원하지 않습니다 .
user2357112는 Monica

pip ()를 통해 perfplot (v0.3.2)을 설치 한 후 예제 코드를 붙여 넣을 때 pip install -U perfplot메시지가 표시 AttributeError: 'module' object has no attribute 'save'됩니다.
tsherwen

바닐라 for 루프는 어떻습니까?
Catiger3331

1
@Vlad는 단순히 math.sqrt를 주석으로 사용합니다.
Nico Schlömer

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는

78
그 문서에서 : The vectorize function is provided primarily for convenience, not for performance. The implementation is essentially a for loop. 다른 질문 vectorize에서 사용자 반복 속도가 두 배가 될 수 있음을 발견했습니다 . 그러나 실제 속도 향상은 실제 numpy어레이 작업입니다.
hpaulj

2
vectorize는 적어도 1 차원이 아닌 배열에서 작동하도록합니다.
Eric

그러나 squarer(x)이미 1d가 아닌 배열에서는 작동합니다. vectorize실제로 목록 이해력 (질문과 같은)에 비해 장점이 없습니다 squarer(x).
user2357112는 Monica

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성능이 좋지 않고 여러 가지 기능이 있으므로 피하십시오. 문제가 . 다른 데이터 유형을 처리하는 경우 아래에 표시된 다른 방법을 조사 할 수 있습니다.

방법 비교

다음은 세 가지 메소드를 비교하여 함수를 맵핑하는 간단한 테스트입니다.이 예제는 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))

다섯 가지 요소를 사용한 테스트 (가장 빠름에서 느림으로 정렬) :

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

100 개의 요소로 :

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

3
예를 들어'np.fromiter((f(xi) for xi in x), x.dtype, count=len(x))'
juanpa.arrivillaga

4
당신의 직접적인 솔루션을 테스트하지 않았다 f(x), 크기 순서 이상으로 다른 모든 친다 .
user2357112는

4
f변수가 2 개이고 배열이 2D 인 경우 는 어떻습니까?
Sigur

2
OP가 배열에서 함수를 "매핑"하는 방법을 물었을 때 'f (x)'버전 ( "direct")이 실제로 비교 가능한 것으로 어떻게 혼란스러워합니까? f (x) = x ** 2의 경우 **는 요소 단위가 아닌 전체 배열에서 numpy에 의해 수행됩니다. 예를 들어 만약 F (X)는 '람다 X :.?. X + X "그 대답은 NumPy와 연접 배열 대신에이 요소 또한 당하고 있기 때문에 매우 다른이 정말 의도 된 비교가 설명해주십시오
앤드류 Mellinger

49

있다 numexpr , numba사이 썬은 주변에,이 답변의 목표는 고려 이러한 가능성을하는 것입니다.

그러나 먼저 명백한 것을 말합시다. 파이썬 함수를 numpy-array에 어떻게 매핑하든 관계없이 모든 평가에 대해 파이썬 함수를 유지합니다.

  • numpy-array 요소는 Python 객체로 변환되어야합니다 (예 : Float ) .
  • 모든 계산은 파이썬 객체를 사용하여 수행되므로 인터프리터, 동적 디스패치 및 불변 객체의 오버 헤드가 발생합니다.

따라서 실제로 배열을 반복하는 데 사용되는 기계는 위에서 언급 한 오버 헤드로 인해 큰 역할을하지 않습니다-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.vectorize순수한 파이썬 함수 클래스의 접근 방식을 대표하여 선정되었습니다. perfplot(이 답변의 부록에있는 코드 참조)을 사용 하면 다음과 같은 실행 시간이 발생합니다.

여기에 이미지 설명을 입력하십시오

우리는 numpy 접근 방식이 순수 파이썬 버전보다 10x-100x 빠릅니다. 더 큰 어레이 크기의 성능 저하는 데이터가 더 이상 캐시에 맞지 않기 때문일 수 있습니다.

또한 vectorize많은 메모리를 사용하므로 메모리 사용량이 종종 병목 현상 이라고 언급 할 가치가 있습니다. SO 질문 ). 또한 numpy의 문서는np.vectorize 는 "성능이 아닌 편의상 주로 제공된다"고 명시하고 있습니다.

처음부터 C-extension을 작성하는 것 외에 성능이 필요한 경우 다른 도구를 사용해야합니다.


numpy-performance는 후드 아래 순수한 C이기 때문에 numpy-performance가 얻는 것만 큼 자주 듣는다고 들었습니다. 그러나 개선의 여지가 많이 있습니다!

벡터화 된 numpy 버전은 많은 추가 메모리와 메모리 액세스를 사용합니다. Numexp 라이브러리는 numpy 배열을 바둑판 식으로 정리하여 더 나은 캐시 활용도를 얻습니다.

# 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 컴파일하여 실제 pure-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-array를 사용한 작업 최적화를 전문으로하는 반면 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은 다소 느린 기능을 수행합니다.

여기에 이미지 설명을 입력하십시오


결론

분명히 하나의 기능에 대해서만 테스트해도 아무런 효과가 없습니다. 또한 선택한 함수 예제의 경우 메모리의 대역폭이 10 ^ 5 요소보다 큰 크기의 병목 현상이라는 점을 명심해야합니다. 따라서이 지역의 numba, numexpr 및 cython에 대해 동일한 성능을 보였습니다.

결국, 궁극의 대답은 함수의 유형, 하드웨어, 파이썬 배포 및 기타 요인에 따라 다릅니다. 예를 아나콘다 분포를 들어 NumPy와의 기능에 대한 인텔의 VML을 사용하여 numba 능가하는 성능 (이 SVML를 사용하지 않는 한,이 참조 SO-게시물을 초월 기능이 좋아 쉽게 용) exp, sin, cos및 유사 - 참조 예를 들어 다음과 같은 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)

배열의 산술 연산은 파이썬 수준의 루프 또는 이해에 적용되는 모든 인터프리터 오버 헤드를 피하는 효율적인 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 배열을 통해 각 요소에 함수 호출을 자동으로 적용하고 반환합니다. 다른 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의 핵심 기능 중 하나입니다.
Eric

8
**각 요소 t에 계산을 적용하는 것은 연산자입니다 t. 그것은 평범한 숫자입니다. 그것을 감싸는 lambda것은 추가 작업을 수행하지 않습니다.
hpaulj

현재 표시된 것처럼 if 문에서는 작동하지 않습니다.
TriHard8

8

많은 경우에 numpy.apply_along_axis 가 최선의 선택입니다. 사소한 테스트 기능뿐만 아니라 numpy 및 scipy의 더 복잡한 기능 구성에 대해서도 다른 접근 방식에 비해 성능이 약 100 배 증가합니다.

내가 방법을 추가하면 :

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

perfplot 코드에 다음과 같은 결과가 나타납니다. 여기에 이미지 설명을 입력하십시오


대부분의 사람들이이 단순하고 확장 가능하며 빌트인 노브레 이너를 오랫동안 알고 있지 않은 것 같습니다.
Bill Huang

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
I는 상기 타이밍 테스트를 반복하고, 또한 약 30 %의 (np.vectorize) 이상 성능 향상을 볼
BrainAnnex.org - 줄리안

2

이 게시물 에서 언급했듯이 다음 과 같이 생성기 표현식을 사용하십시오.

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

2

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

두 개만 비교했지만의 모양은 유지됩니다 ndarray. 비교를 위해 1 백만 개의 항목이있는 배열을 사용했습니다. 여기서는 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.