source

기능을 numpy 어레이에 매핑하는 가장 효율적인 방법

manycodes 2022. 11. 5. 09:04
반응형

기능을 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.vectorize20~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

반응형