numpy 배열에서 함수를 매핑하는 가장 효율적인 방법 다음과 같습니다. import numpy as

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)

perfplot 코드에 다음과 같은 결과가 나타납니다.