기능을 numpy 어레이에 매핑하는 가장 효율적인 방법
함수를 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(list(map(f, x)))
(나의 작은 프로젝트와 함께)
메시지 #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)
b = perfplot.bench(
setup=np.random.rand,
n_range=[2 ** k for k in range(20)],
kernels=[
f,
array_for,
array_map,
fromiter,
vectorize,
vectorize_without_init,
],
xlabel="len(x)",
)
b.save("out1.svg")
b.show()
를 사용하면 어떨까요?
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))
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
100s 요소의 경우:
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, sython이 있는데, 이 답변의 목표는 이러한 가능성을 고려하는 것입니다.
하지만 먼저 Python 함수를 numpy-array에 매핑하는 방법에 관계없이 Python 함수는 그대로 유지되며, 이는 모든 평가에서 다음과 같은 의미를 가집니다.
- numpy-array 요소는 Python 개체로 변환해야 합니다(예:
Float
). - 모든 계산은 Python-objects로 이루어집니다.즉, 인터프리터, 다이내믹 디스패치 및 불변의 객체의 오버헤드가 발생합니다.
따라서 어레이를 실제로 루프하는 데 사용되는 기계는 위에서 언급한 오버헤드로 인해 큰 역할을 하지 않습니다. 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-approach가 순수 python 버전보다 10배-100배 빠른 것을 알 수 있습니다.어레이 사이즈가 클수록 퍼포먼스가 저하되는 것은 데이터가 캐시에 맞지 않기 때문일 것입니다.
언급할 가 있습니다.vectorize
또한 메모리를 많이 사용하기 때문에 메모리 사용량이 병목인 경우가 많습니다(관련 SO-질문 참조).또한 이 numpy의 문서에는 "성능이 아닌 주로 편의성을 위해 제공됨"이라고 명시되어 있습니다.
성능이 필요한 경우 C 확장자를 처음부터 쓰는 것 외에 다음과 같은 다른 도구를 사용해야 합니다.
Numpy 퍼포먼스가 최고라는 말은 자주 듣습니다.그건 순전히 C이기 때문입니다.아직 개선의 여지가 많다!
벡터화된 numpy 버전은 많은 추가 메모리 및 메모리 액세스를 사용합니다.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-라이브러리의 오버헤드가 커지지만 캐시를 더 잘 활용하기 때문에 어레이 크기가 클수록 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-approach보다 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은 다음과 같이 기능이 다소 느려집니다.
결론
한 가지 기능만을 테스트한다고 해서 아무 것도 증명할 수 없습니다.또한 Choosen 기능(예: 메모리 대역폭)의 경우 10^5 요소보다 큰 크기의 병목이었기 때문에 이 영역에서는 numba, numexpr 및 cython에 대해 동일한 성능을 보였습니다.
결국 최종적인 답변은 함수 유형, 하드웨어, Python 배포 및 기타 요인에 따라 달라집니다.예를 들어 Anaconda-distribution은 numpy 함수에 Intel의 VML을 사용하므로 다음과 같은 초월 함수에 대해서는 numba(SVML을 사용하지 않는 한 이 SO-post 참조)를 쉽게 능가합니다.exp
,sin
,cos
및 유사 - 예를 들어 다음 SO 포스트를 참조하십시오.
그러나 이 조사와 지금까지의 경험에 비추어 볼 때, 초월적인 함수가 포함되지 않는 한 numba가 최고의 성능을 가진 가장 쉬운 도구인 것 같습니다.
성능도 패키지를 사용하여 실행 시간 표시:
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 레벨 루프를 통해 Python 레벨 루프 또는 이해에 적용되는 모든 인터프리터 오버헤드를 방지합니다.
NumPy 어레이 요소에 적용하는 대부분의 기능은 정상적으로 동작하지만 일부 기능은 변경이 필요할 수 있습니다.를 들어, 「」라고 하는 것은,if
요소별로 동작하지 않습니다.다음과 같은 구성을 사용하도록 변환해야 합니다.
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)
언급하지 않은 것 .ufunc
패키지 numpy "py":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
저는 더 큰 샘플도 테스트해봤고, 개선은 비례합니다.메뉴얼도 참조해 주세요.
★★★★의 소지가 있었다. np.sqrt
어레이에 직접 적용되어 약간의 오버헤드만 발생하였습니다.
1d 배열로 동작하는 빌트인 함수를 적용하는 다차원적인 경우 numpy.apply_along_axis를 선택하는 것이 좋습니다.numpy 및 scipy에서 보다 복잡한 함수 구성에도 적합합니다.
이전 오해의 소지가 있는 진술:
메서드 추가:
def along_axis(x):
return np.apply_along_axis(f, 0, x)
성능도 코드에 대한 성능 결과는 다음과 같습니다.np.sqrt
.
새로운 버전의 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.fromiter((<some_func>(x) for x in <something>),<dtype>,<size of something>)
은 모두 잘 " "가 있는 "가 있습니다.numpy.ndarray
어레이의 형태를 유지해야 합니다.
두 개만 비교했는데, 이 두 개만 비교해도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
심플한 어프로치를 생각하면, 매우 효과적입니다.내장 기능을 사용할 수 있는 경우는, 그것을 사용해 주세요.
numpy.fromfunction(function, shape, **kwargs)
「https://docs.scipy.org/doc/numpy/reference/generated/numpy.fromfunction.html」를 참조해 주세요.
언급URL : https://stackoverflow.com/questions/35215161/most-efficient-way-to-map-function-over-numpy-array
'source' 카테고리의 다른 글
Python의 자녀 클래스에서 부모 클래스의 메서드를 호출하려면 어떻게 해야 합니까? (0) | 2022.11.15 |
---|---|
Maven 저장소에서 소스 JAR 가져오기 (0) | 2022.11.15 |
create-react-app 4.0.3을 실행하고 있으며 최신 릴리스(5.0.0)보다 뒤처져 있습니다. (0) | 2022.11.05 |
Python을 사용하여 Gmail을 프로바이더로 이메일을 보내는 방법은 무엇입니까? (0) | 2022.11.05 |
JavaScript 개체를 표시하려면 어떻게 해야 합니까? (0) | 2022.11.05 |