Đánh giá hiệu quả một hàm tại mọi ô của mảng NumPy


124

Cho một mảng NumPy A , cách nhanh nhất / hiệu quả nhất để áp dụng cùng một hàm, f , cho mọi ô là gì?

  1. Giả sử chúng ta sẽ gán cho A (i, j)f (A (i, j)) .

  2. Hàm f , không có đầu ra nhị phân, do đó các thao tác mặt nạ (ing) sẽ không giúp ích.

Là phép lặp vòng lặp "rõ ràng" (thông qua mọi ô) là giải pháp tối ưu?


Câu trả lời:


165

Bạn chỉ có thể vector hóa hàm và sau đó áp dụng nó trực tiếp vào một mảng Numpy mỗi khi bạn cần nó:

import numpy as np

def f(x):
    return x * x + 3 * x - 2 if x > 0 else x * 5 + 8

f = np.vectorize(f)  # or use a different name if you want to keep the original f

result_array = f(A)  # if A is your Numpy array

Có lẽ tốt hơn để chỉ định một loại đầu ra rõ ràng trực tiếp khi vector hóa:

f = np.vectorize(f, otypes=[np.float])

19
Tôi e rằng hàm vectơ có thể nhanh hơn phép lặp và gán vòng lặp kép "thủ công" thông qua tất cả các phần tử mảng. Đặc biệt, vì nó lưu kết quả vào một biến mới được tạo (và không trực tiếp với đầu vào ban đầu). Cảm ơn rất nhiều vì đã trả lời của bạn :)
Peter

1
@Peter: Ah, bây giờ tôi thấy rằng bạn đã đề cập đến việc gán kết quả trở lại mảng cũ trong câu hỏi ban đầu của bạn. Tôi xin lỗi tôi đã bỏ lỡ điều đó khi lần đầu tiên đọc nó. Vâng, trong trường hợp đó, vòng lặp kép phải nhanh hơn. Nhưng bạn cũng đã thử một vòng lặp duy nhất trên khung nhìn phẳng của mảng chưa? Điều đó có thể nhanh hơn một chút , vì bạn tiết kiệm được một ít chi phí vòng lặp và Numpy cần thực hiện một phép nhân và phép cộng ít hơn (để tính toán bù dữ liệu) ở mỗi lần lặp. Thêm vào đó, nó hoạt động cho các mảng kích thước tùy ý. Có thể chậm hơn trên các mảng rất nhỏ, tho.
blubberdiblub

45
Lưu ý cảnh báo được đưa ra trong vectorizemô tả chức năng: Hàm vectorize được cung cấp chủ yếu để thuận tiện, không phải cho hiệu suất. Việc thực hiện về cơ bản là một vòng lặp for. Vì vậy, điều này rất có thể sẽ không tăng tốc quá trình.
Gabriel

Hãy chú ý đến cách vectorizexác định loại trả lại. Điều đó đã tạo ra lỗi. frompyfuncnhanh hơn một chút, nhưng trả về một mảng đối tượng dtype. Cả hai vô hướng thức ăn, không phải hàng hoặc cột.
hpaulj

1
@Gabriel Chỉ cần np.vectorizesử dụng chức năng của tôi (sử dụng RK45) sẽ tăng tốc cho hệ số ~ 20.
Suuuehgi



0

Tôi tin rằng tôi đã tìm thấy một giải pháp tốt hơn. Ý tưởng thay đổi hàm thành hàm phổ python (xem tài liệu ), có thể thực hiện tính toán song song dưới mui xe.

Người ta có thể viết tùy chỉnh của riêng mình ufuncbằng C, chắc chắn là hiệu quả hơn, hoặc bằng cách gọi np.frompyfunc, đó là phương pháp nhà máy tích hợp. Sau khi thử nghiệm, điều này hiệu quả hơn 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 f_arr(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. Để so sánh hiệu suất của các phương pháp khác, xem bài này


0

Khi mảng 2d (hoặc mảng thứ hai) là C- hoặc F-liền kề, thì nhiệm vụ ánh xạ một hàm lên mảng 2d thực tế giống như nhiệm vụ ánh xạ một hàm lên mảng 1d - chúng ta chỉ phải xem nó theo cách đó, ví dụ thông qua np.ravel(A,'K').

Giải pháp có thể cho mảng 1d đã được thảo luận ví dụ ở đây .

Tuy nhiên, khi bộ nhớ của mảng 2d không liền kề nhau, thì tình huống sẽ phức tạp hơn một chút, vì người ta muốn tránh các lỗi bộ nhớ cache có thể xảy ra nếu trục bị xử lý sai.

Numpy đã có sẵn một máy móc để xử lý các trục theo thứ tự tốt nhất có thể. Một khả năng để sử dụng máy móc này là np.vectorize. Tuy nhiên, 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" - một chức năng python chậm giữ chức năng python chậm với toàn bộ chi phí liên quan! Một vấn đề khác là mức tiêu thụ bộ nhớ khổng lồ của nó - xem ví dụ bài SO này .

Khi một người muốn có hiệu suất của chức năng C nhưng sử dụng máy móc của numpy, một giải pháp tốt là sử dụng numba để tạo ufuncs, ví dụ:

# 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ó dễ dàng đập np.vectorizenhưng cũng có khi chức năng tương tự sẽ được thực hiện dưới dạng nhân / bổ sung mảng numpy, tức là

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

Xem phụ lục của câu trả lời này để biết mã đo thời gian:

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

Phiên bản của Numba (màu xanh lá cây) nhanh hơn khoảng 100 lần so với chức năng python (tức là np.vectorize), điều này không đáng ngạc nhiên. Nhưng nó cũng nhanh hơn khoảng 10 lần so với chức năng numpy, bởi vì phiên bản numbas không cần mảng trung gian và do đó sử dụng bộ đệm hiệu quả hơn.


Mặc dù cách tiếp cận ufunc của numba là một sự đánh đổi tốt giữa khả năng sử dụng và hiệu suất, nhưng nó vẫn không phải là cách tốt nhất chúng ta có thể làm. Tuy nhiên, không có viên đạn bạc hoặc cách tiếp cận tốt nhất cho bất kỳ nhiệm vụ nào - người ta phải hiểu giới hạn là gì và làm thế nào để giảm thiểu chúng.

Ví dụ, đối với chức năng siêu việt (ví dụ exp, sin, cos) numba không cung cấp bất kỳ lợi thế hơn NumPy của np.exp(không có mảng tạm thời tạo - nguồn chính của tăng tốc). Tuy nhiên, bản cài đặt Anaconda của tôi sử dụng VML của Intel cho các vectơ lớn hơn 8192 - nó chỉ không thể làm điều đó nếu bộ nhớ không liền kề. Vì vậy, tốt hơn là sao chép các phần tử vào bộ nhớ liền kề để có thể sử dụng VML của Intel:

import numba as nb
@nb.vectorize(target="cpu")
def nb_vexp(x):
    return np.exp(x)

def np_copy_exp(x):
    copy = np.ravel(x, 'K')
    return np.exp(copy).reshape(x.shape) 

Để công bằng cho việc so sánh, tôi đã tắt tính năng song song hóa của VML (xem mã trong phần phụ lục):

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

Như mọi người có thể thấy, một khi VML khởi động, chi phí sao chép sẽ được bù nhiều hơn. Tuy nhiên, một khi dữ liệu trở nên quá lớn đối với bộ đệm L3, lợi thế là tối thiểu khi tác vụ trở lại giới hạn băng thông bộ nhớ.

Mặt khác, numba cũng có thể sử dụng SVML của Intel, như được giải thích trong bài viết này :

from llvmlite import binding
# set before import
binding.set_option('SVML', '-vector-library=SVML')

import numba as nb

@nb.vectorize(target="cpu")
def nb_vexp_svml(x):
    return np.exp(x)

và sử dụng VML với năng suất song song:

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

Phiên bản của numba có ít chi phí hoạt động hơn, nhưng đối với một số kích thước, VML đánh bại SVML ngay cả khi có thêm chi phí sao chép - điều này không gây ngạc nhiên chút nào vì ufuncs của numba không song song.


Danh sách:

A. so sánh hàm đa thức:

import perfplot
perfplot.show(
    setup=lambda n: np.random.rand(n,n)[::2,::2],
    n_range=[2**k for k in range(0,12)],
    kernels=[
        f,
        vf, 
        nb_vf
        ],
    logx=True,
    logy=True,
    xlabel='len(x)'
    ) 

B. so sánh về exp:

import perfplot
import numexpr as ne # using ne is the easiest way to set vml_num_threads
ne.set_vml_num_threads(1)
perfplot.show(
    setup=lambda n: np.random.rand(n,n)[::2,::2],
    n_range=[2**k for k in range(0,12)],
    kernels=[
        nb_vexp, 
        np.exp,
        np_copy_exp,
        ],
    logx=True,
    logy=True,
    xlabel='len(x)',
    )

0

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. Tôi đang trình bày trường hợp chung cho mảng n chiều. Đối với hai chiều chỉ làm itercho 2D.

import numpy, time

def A(e):
    return e * e

def timeit():
    y = numpy.arange(1000000)
    now = time.time()
    numpy.array([A(x) for x in y.reshape(-1)]).reshape(y.shape)        
    print(time.time() - now)
    now = time.time()
    numpy.fromiter((A(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.fromiterchức năng bình phương của người dùng, sử dụng bất kỳ lựa chọn nào của bạn. Nếu hàm của bạn phụ thuộc vào i, j đó là các chỉ số của mảng, lặp đi lặp lại về kích thước của mảng như thế nào for ind in range(arr.size), hãy sử dụng numpy.unravel_indexđể có được i, j, ..dựa trên chỉ số 1D của bạn và hình dạng của mảng numpy.unravel_index

Câu trả lời này được lấy cảm hứng từ câu trả lời của tôi cho câu hỏi khác ở đây

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.