Cách hiệu quả nhất để ánh xạ chức năng qua mảng numpy


337

Cách hiệu quả nhất để ánh xạ một hàm qua một mảng numpy là gì? Cách tôi đã làm trong dự án hiện tại của mình như sau:

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])

Tuy nhiên, điều này có vẻ như rất kém hiệu quả, vì tôi đang sử dụng một sự hiểu biết danh sách để xây dựng mảng mới dưới dạng danh sách Python trước khi chuyển đổi nó trở lại thành một mảng khó hiểu.

Chúng ta có thể làm tốt hơn không?


10
tại sao không "hình vuông = x ** 2"? Bạn có một chức năng phức tạp hơn nhiều bạn cần phải đánh giá?
22degrees 5/2/2016

4
Làm thế nào về chỉ squarer(x)?
Cuộc sống

1
Có thể điều này không trực tiếp trả lời câu hỏi, nhưng tôi đã nghe nói rằng numba có thể biên dịch mã python hiện có thành các hướng dẫn máy song song. Tôi sẽ xem lại và xem lại bài đăng này khi tôi thực sự có cơ hội sử dụng nó.
把 友情 留

x = np.array([1, 2, 3, 4, 5]); x**2hoạt động
Shark Đặng

Câu trả lời:


281

Tôi đã thử nghiệm tất cả các phương pháp được đề xuất cộng np.array(map(f, x))với perfplot(một dự án nhỏ của tôi).

Thông điệp # 1: Nếu bạn có thể sử dụng các hàm riêng của numpy, hãy làm điều đó.

Nếu chức năng bạn đang cố gắng vectorize đã được vector hóa (như x**2ví dụ trong các bài bản gốc), sử dụng đó là nhiều nhanh hơn so với bất cứ điều gì khác (lưu ý quy mô log):

nhập mô tả hình ảnh ở đây

Nếu bạn thực sự cần vector hóa, nó thực sự không quan trọng bằng việc bạn sử dụng biến thể nào.

nhập mô tả hình ảnh ở đây


Mã để tái tạo các ô:

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)",
)

7
Bạn dường như đã rời f(x)khỏi âm mưu của bạn. Nó có thể không áp dụng cho mọi người f, nhưng nó có thể áp dụng ở đây và nó dễ dàng là giải pháp nhanh nhất khi áp dụng.
user2357112 hỗ trợ Monica

2
Ngoài ra, cốt truyện của bạn không hỗ trợ cho tuyên bố của bạn rằng vf = np.vectorize(f); y = vf(x)chiến thắng cho các đầu vào ngắn.
user2357112 hỗ trợ Monica

Sau khi cài đặt perfplot (v0.3.2) qua pip ( pip install -U perfplot), tôi thấy thông báo: AttributeError: 'module' object has no attribute 'save'khi dán mã ví dụ.
tsherwen

Một vanilla cho vòng lặp thì sao?
Catiger3331

1
@Vlad chỉ cần sử dụng math.sqrt như đã nhận xét.
Nico Schlömer

138

Làm thế nào về việc sử dụng 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])

36
Điều này không hiệu quả hơn.
user2357112 hỗ trợ Monica

78
Từ tài liệu đó: The vectorize function is provided primarily for convenience, not for performance. The implementation is essentially a for loop. Trong các câu hỏi khác, tôi thấy rằng vectorizecó thể tăng gấp đôi tốc độ lặp của người dùng. Nhưng việc tăng tốc thực sự là với numpycác hoạt động mảng thực sự .
hpaulj

2
Lưu ý rằng vectorize ít nhất làm cho mọi thứ hoạt động cho các mảng không 1d
Eric

Nhưng squarer(x)sẽ làm việc cho các mảng không 1d. vectorizechỉ thực sự có bất kỳ lợi thế nào trong việc hiểu danh sách (như câu hỏi trong câu hỏi), không hơn squarer(x).
user2357112 hỗ trợ Monica

79

TL; DR

Như đã lưu ý bởi @ user2357112 , phương pháp "trực tiếp" áp dụng hàm luôn là cách nhanh nhất và đơn giản nhất để ánh xạ hàm qua các mảng Numpy:

import numpy as np
x = np.array([1, 2, 3, 4, 5])
f = lambda x: x ** 2
squares = f(x)

Nói chung là tránh np.vectorize, vì nó không hoạt động tốt, và có (hoặc có) một số vấn đề . Nếu bạn đang xử lý các loại dữ liệu khác, bạn có thể muốn điều tra các phương pháp khác được hiển thị bên dưới.

So sánh các phương pháp

Dưới đây là một số thử nghiệm đơn giản để so sánh ba phương thức để ánh xạ một hàm, ví dụ này sử dụng với Python 3.6 và NumPy 1.15.4. Đầu tiên, các chức năng thiết lập để thử nghiệm:

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))

Kiểm tra với năm yếu tố (được sắp xếp từ nhanh nhất đến chậm nhấ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

Với 100 yếu tố:

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

Và với 1000 phần tử mảng trở lên:

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

Các phiên bản khác nhau của Python / NumPy và tối ưu hóa trình biên dịch sẽ có kết quả khác nhau, do đó hãy thực hiện một thử nghiệm tương tự cho môi trường của bạn.


2
Nếu bạn sử dụng countđối số và biểu thức trình tạo thì np.fromiternhanh hơn đáng kể.
juanpa.arrivillaga

3
Vì vậy, ví dụ, sử dụng'np.fromiter((f(xi) for xi in x), x.dtype, count=len(x))'
juanpa.arrivillaga

4
Bạn đã không kiểm tra giải pháp trực tiếp f(x), trong đó đánh bại mọi thứ khác bằng một mức độ lớn .
user2357112 hỗ trợ Monica

4
Nếu fcó 2 biến và mảng là 2D thì sao?
Sigur

2
Tôi bối rối khi phiên bản 'f (x)' ("trực tiếp") thực sự được coi là có thể so sánh khi OP hỏi cách "ánh xạ" một chức năng trên một mảng? Trong trường hợp f (x) = x ** 2, ** đang được thực hiện bởi numpy trên toàn bộ mảng không phải trên cơ sở mỗi phần tử. Ví dụ: nếu f (x) là 'lambda x: x + x "thì câu trả lời rất khác nhau vì numpy ghép các mảng thay vì thực hiện mỗi phép cộng. Đây có thực sự là so sánh dự định không? Vui lòng giải thích.
Andrew Mellinger

49

numexpr , numbacython , mục tiêu của câu trả lời này là xem xét các khả năng này.

Nhưng trước tiên, hãy nêu rõ ràng: cho dù bạn ánh xạ hàm Python lên mảng numpy như thế nào, thì nó vẫn là hàm Python, có nghĩa là cho mọi đánh giá:

  • phần tử numpy-Array phải được chuyển đổi thành đối tượng Python (ví dụ a Float).
  • tất cả các tính toán được thực hiện với các đối tượng Python, có nghĩa là có chi phí hoạt động của trình thông dịch, công văn động và các đối tượng không thay đổi.

Vì vậy, máy móc nào được sử dụng để thực sự lặp qua mảng không đóng vai trò lớn vì chi phí được đề cập ở trên - nó chậm hơn nhiều so với sử dụng chức năng tích hợp của numpy.

Hãy xem ví dụ sau:

# 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được chọn làm đại diện cho lớp tiếp cận hàm python thuần. Sử dụng perfplot(xem mã trong phần phụ lục của câu trả lời này) chúng ta sẽ có được thời gian chạy sau:

nhập mô tả hình ảnh ở đây

Chúng ta có thể thấy, cách tiếp cận numpy nhanh hơn gấp 10 lần so với phiên bản python. Việc giảm hiệu năng cho kích thước mảng lớn hơn có lẽ là do dữ liệu không còn vừa với bộ đệm.

Điều đáng nói là, nó vectorizecũng sử dụng rất nhiều bộ nhớ, vì vậy thường sử dụng bộ nhớ là cổ chai (xem câu hỏi SO liên quan ). Cũng lưu ý rằng tài liệu của numpy np.vectorizenói rằng nó "được cung cấp chủ yếu để thuận tiện, không phải cho hiệu suất".

Các công cụ khác nên được sử dụng, khi hiệu suất được mong muốn, bên cạnh việc viết phần mở rộng C từ đầu, có các khả năng sau:


Mọi người thường nghe rằng hiệu suất numpy tốt như nó có được, bởi vì nó là C thuần túy dưới mui xe. Tuy nhiên, có rất nhiều phòng để cải thiện!

Phiên bản numpy được vector hóa sử dụng rất nhiều bộ nhớ và truy cập bộ nhớ bổ sung. Thư viện số mở rộng cố gắng xếp các mảng numpy và do đó sử dụng bộ đệm tốt hơn:

# 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")

Dẫn đến sự so sánh sau:

nhập mô tả hình ảnh ở đây

Tôi không thể giải thích mọi thứ trong cốt truyện ở trên: chúng ta có thể thấy chi phí lớn hơn cho thư viện numexpr lúc đầu, nhưng vì nó sử dụng bộ đệm tốt hơn nên nhanh hơn khoảng 10 lần cho các mảng lớn hơn!


Một cách tiếp cận khác là biên dịch hàm và do đó nhận được một UFunc thuần C thực sự. Đây là cách tiếp cận của 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

Nó nhanh hơn 10 lần so với phương pháp tiếp cận ban đầu:

nhập mô tả hình ảnh ở đây


Tuy nhiên, nhiệm vụ là song song lúng túng, do đó chúng ta cũng có thể sử dụng prangeđể tính toán vòng lặp song song:

@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

Như mong đợi, chức năng song song chậm hơn đối với đầu vào nhỏ hơn, nhưng nhanh hơn (gần như yếu tố 2) đối với kích thước lớn hơn:

nhập mô tả hình ảnh ở đây


Trong khi numba chuyên tối ưu hóa các hoạt động với mảng numpy, Cython là một công cụ tổng quát hơn. Nó phức tạp hơn để trích xuất hiệu suất tương tự như với numba - thường thì nó sẽ giảm xuống llvm (numba) so với trình biên dịch cục bộ (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 kết quả trong các chức năng hơi chậm:

nhập mô tả hình ảnh ở đây


Phần kết luận

Rõ ràng, thử nghiệm chỉ cho một chức năng không chứng minh bất cứ điều gì. Ngoài ra, bạn nên nhớ rằng, đối với ví dụ về chức năng được chọn, băng thông của bộ nhớ là cổ chai cho các kích thước lớn hơn 10 ^ 5 phần tử - do đó chúng tôi có cùng hiệu suất cho numba, numexpr và cython trong khu vực này.

Cuối cùng, câu trả lời tối ưu phụ thuộc vào loại chức năng, phần cứng, phân phối Python và các yếu tố khác. Ví dụ Anaconda-phân phối sử dụng VML của Intel cho các chức năng NumPy và do đó nhanh hơn so với numba (trừ khi nó sử dụng SVML, thấy điều này SO-bài ) dễ dàng cho các chức năng siêu việt như thế exp, sin, cosvà tương tự - xem ví dụ sau -SO bài .

Tuy nhiên, từ cuộc điều tra này và từ kinh nghiệm của tôi cho đến nay, tôi sẽ nói rằng, numba dường như là công cụ dễ nhất với hiệu suất tốt nhất miễn là không có chức năng siêu việt nào.


Vẽ sơ đồ thời gian chạy với 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)'
    )

1
Numba có thể sử dụng Intel SVML thường dẫn đến thời gian khá tương đương so với Intel VML, nhưng việc triển khai có một chút lỗi trong phiên bản (0.43-0.47). Tôi đã thêm một stackoverflow Performance Performance.com/a/56939240/4045774 để tổng hợp vào cy_expsum của bạn.
max9111

29
squares = squarer(x)

Các phép toán số học trên các mảng được tự động áp dụng theo từng phần tử, với các vòng lặp ở mức C hiệu quả để tránh tất cả các chi phí thông dịch sẽ áp dụng cho vòng lặp hoặc mức độ hiểu của Python.

Hầu hết các hàm bạn muốn áp dụng cho một mảng NumPy theo nguyên tố sẽ chỉ hoạt động, mặc dù một số có thể cần thay đổi. Ví dụ, ifkhông hoạt động theo nguyên tố. Bạn muốn chuyển đổi chúng để sử dụng các cấu trúc như numpy.where:

def using_if(x):
    if x < 5:
        return x
    else:
        return x**2

trở thành

def using_where(x):
    return numpy.where(x < 5, x, x**2)

8

Tôi tin vào phiên bản mới hơn (tôi sử dụng 1.13) của numpy, bạn có thể chỉ cần gọi hàm bằng cách chuyển mảng numpy đến ftion mà bạn đã viết cho kiểu vô hướng, nó sẽ tự động áp dụng lệnh gọi hàm cho từng phần tử trên mảng numpy và trả về cho bạn một mảng numpy khác

>>> 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])

3
Điều này không phải là từ xa mới - nó luôn luôn như vậy - đó là một trong những tính năng cốt lõi của numpy.
Eric

8
Đó là **toán tử đang áp dụng phép tính cho từng phần tử t của t. Đó là numpy bình thường. Gói nó trong lambdakhông làm gì thêm.
hpaulj

Điều này không hoạt động với câu lệnh if như nó hiện đang được hiển thị.
TriHard8

8

Trong nhiều trường hợp, numpy.apply_along_axis sẽ là lựa chọn tốt nhất. Nó tăng hiệu suất khoảng 100 lần so với các phương pháp khác - và không chỉ đối với các chức năng kiểm tra tầm thường, mà còn đối với các thành phần chức năng phức tạp hơn từ numpy và scipy.

Khi tôi thêm phương thức:

def along_axis(x):
    return np.apply_along_axis(f, 0, x)

đến mã perfplot, tôi nhận được các kết quả sau: nhập mô tả hình ảnh ở đây


Thủ thuật tuyệt vời!
Felipe SS Schneider

Tôi vô cùng sốc về thực tế là hầu hết mọi người dường như không nhận thức được điều này đơn giản, có thể mở rộng và không có trí tuệ trong nhiều năm qua ....
Bill Huang

7

Dường như không ai đề cập đến một phương pháp sản xuất tích hợp ufunctrong sản xuất trong gói numpy: np.frompyfuncmà tôi đã thử nghiệm lại np.vectorizevà đã vượt trội hơn khoảng 20 ~ 30%. Tất nhiên nó sẽ hoạt động tốt như mã C được quy định hoặc thậm chí numba(mà tôi chưa thử nghiệm), nhưng nó có thể thay thế tốt hơnnp.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

Tôi cũng đã thử nghiệm các mẫu lớn hơn, và sự cải thiện là tỷ lệ thuận. Xem tài liệu cũng ở đây


1
Tôi đã lặp lại các bài kiểm tra thời gian ở trên và cũng tìm thấy sự cải thiện hiệu suất (trên np.vectorize) khoảng 30%
Julian - BrainAnnex.org

2

Như đã đề cập trong bài viết này , chỉ cần sử dụng các biểu thức trình tạo như vậy:

numpy.fromiter((<some_func>(x) for x in <something>),<dtype>,<size of something>)

2

Tất cả các câu trả lời trên đều so sánh tốt, nhưng nếu bạn cần sử dụng chức năng tùy chỉnh để ánh xạ, và bạn có numpy.ndarray, và bạn cần giữ lại hình dạng của mảng.

Tôi đã so sánh chỉ hai, nhưng nó sẽ giữ lại hình dạng của ndarray. Tôi đã sử dụng mảng với 1 triệu mục để so sánh. Ở đây tôi sử dụng hàm vuông, cũng được tích hợp sẵn trong numpy và tăng hiệu suất tuyệt vời, vì khi cần một cái gì đó, bạn có thể sử dụng chức năng bạn chọn.

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)

Đầu ra

>>> timeit()
1.162431240081787    # list comprehension and then building numpy array
1.0775556564331055   # from numpy.fromiter
0.002948284149169922 # using inbuilt function

Ở đây bạn có thể thấy rõ numpy.fromitercác công trình tuyệt vời khi xem xét cách tiếp cận đơn giản, và nếu chức năng sẵn có sẵn, vui lòng sử dụng nó.


Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.