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 목록으로 구성하기 때문에 아마도 매우 비효율적 인 것 같습니다.
더 잘할 수 있을까요?
답변
제안 된 모든 방법 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)",
)
답변
사용 방법은 어떻습니까 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])
답변
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와 컴파일러 최적화는 다른 결과를 가지므로 환경에 대한 유사한 테스트를 수행하십시오.
답변
있다 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)'
)
답변
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)
답변
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])
답변
많은 경우에 numpy.apply_along_axis 가 최선의 선택입니다. 사소한 테스트 기능뿐만 아니라 numpy 및 scipy의 더 복잡한 기능 구성에 대해서도 다른 접근 방식에 비해 성능이 약 100 배 증가합니다.
내가 방법을 추가하면 :
def along_axis(x):
return np.apply_along_axis(f, 0, x)