[python] 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(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 코드에 다음과 같은 결과가 나타납니다.
여기에 이미지 설명을 입력하십시오