Cara paling efisien untuk memetakan fungsi lebih dari array numpy

338

Apa cara paling efisien untuk memetakan suatu fungsi pada array yang numpy? Cara saya melakukannya dalam proyek saya saat ini adalah sebagai berikut:

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

Namun, ini sepertinya sangat tidak efisien, karena saya menggunakan pemahaman daftar untuk membangun array baru sebagai daftar Python sebelum mengubahnya kembali menjadi array numpy.

Bisakah kita berbuat lebih baik?

Ryan
sumber
10
mengapa tidak "kuadrat = x ** 2"? Apakah Anda memiliki fungsi yang jauh lebih rumit yang perlu Anda evaluasi?
22 derajat
4
Bagaimana kalau saja squarer(x)?
Kehidupan
1
Mungkin ini tidak secara langsung menjawab pertanyaan, tetapi saya pernah mendengar bahwa numba dapat mengkompilasi kode python yang ada menjadi instruksi mesin paralel. Saya akan meninjau kembali dan merevisi pos ini ketika saya benar-benar memiliki kesempatan untuk menggunakannya.
把 友情 留 在 无 盐
x = np.array([1, 2, 3, 4, 5]); x**2bekerja
Shark Deng

Jawaban:

283

Saya telah menguji semua metode yang disarankan ditambah np.array(map(f, x))dengan perfplot(proyek kecil saya).

Pesan # 1: Jika Anda dapat menggunakan fungsi asli numpy, lakukan itu.

Jika fungsi yang Anda coba vektorkan sudah menjadi vektor (seperti x**2contoh di posting asli), menggunakan itu jauh lebih cepat daripada yang lain (perhatikan skala log):

masukkan deskripsi gambar di sini

Jika Anda benar-benar membutuhkan vektorisasi, tidak terlalu penting varian mana yang Anda gunakan.

masukkan deskripsi gambar di sini


Kode untuk mereproduksi plot:

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)",
)
Nico Schlömer
sumber
7
Anda tampaknya telah meninggalkan f(x)rencana Anda. Itu mungkin tidak berlaku untuk setiap orang f, tetapi itu berlaku di sini, dan itu dengan mudah solusi tercepat saat berlaku.
user2357112 mendukung Monica
2
Selain itu, plot Anda tidak mendukung klaim Anda yang vf = np.vectorize(f); y = vf(x)menang untuk input pendek.
user2357112 mendukung Monica
Setelah menginstal perfplot (v0.3.2) via pip ( pip install -U perfplot), saya melihat pesan: AttributeError: 'module' object has no attribute 'save'ketika menempelkan kode contoh.
tsherwen
Bagaimana dengan vanilla for loop?
Catiger3331
1
@Vlad cukup gunakan math.sqrt seperti yang dikomentari.
Nico Schlömer
138

Bagaimana kalau menggunakan 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])
satomacoto
sumber
36
Ini tidak lebih efisien.
user2357112 mendukung Monica
78
Dari dokumen itu: The vectorize function is provided primarily for convenience, not for performance. The implementation is essentially a for loop. Dalam pertanyaan lain saya menemukan bahwa vectorizemungkin menggandakan kecepatan iterasi pengguna. Tetapi speedup sebenarnya adalah dengan numpyoperasi array nyata .
hpaulj
2
Perhatikan bahwa vectorize setidaknya membuat hal-hal berfungsi untuk array non-1d
Eric
Tetapi squarer(x)sudah bekerja untuk array non-1d. vectorizehanya benar-benar memiliki keunggulan dibandingkan pemahaman daftar (seperti yang ada di pertanyaan), tidak lebih squarer(x).
user2357112 mendukung Monica
79

TL; DR

Seperti dicatat oleh @ user2357112 , metode "langsung" menerapkan fungsi selalu merupakan cara tercepat dan termudah untuk memetakan fungsi melalui array Numpy:

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

Umumnya menghindari np.vectorize, karena tidak berkinerja baik, dan memiliki (atau pernah) sejumlah masalah . Jika Anda menangani tipe data lain, Anda mungkin ingin menyelidiki metode lain yang ditunjukkan di bawah ini.

Perbandingan metode

Berikut adalah beberapa tes sederhana untuk membandingkan tiga metode untuk memetakan suatu fungsi, contoh ini menggunakan dengan Python 3.6 dan NumPy 1.15.4. Pertama, fungsi pengaturan untuk pengujian:

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

Pengujian dengan lima elemen (diurutkan dari yang tercepat ke yang terlambat):

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

Dengan 100-an elemen:

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

Dan dengan 1000s elemen array atau lebih:

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

Versi berbeda dari Python / NumPy dan optimisasi kompiler akan memiliki hasil yang berbeda, jadi lakukan tes serupa untuk lingkungan Anda.

Mike T
sumber
2
Jika Anda menggunakan countargumen dan ekspresi generator maka np.fromitersecara signifikan lebih cepat.
juanpa.arrivillaga
3
Jadi, misalnya, gunakan'np.fromiter((f(xi) for xi in x), x.dtype, count=len(x))'
juanpa.arrivillaga
4
Anda tidak menguji solusi langsung f(x), yang mengalahkan segalanya dengan lebih dari satu urutan besarnya .
user2357112 mendukung Monica
4
Bagaimana jika fmemiliki 2 variabel dan array adalah 2D?
Sigur
2
Saya bingung bagaimana versi 'f (x)' ("langsung") sebenarnya dianggap sebanding ketika OP bertanya bagaimana "memetakan" suatu fungsi di seluruh array? Dalam kasus f (x) = x ** 2 ** dilakukan oleh numpy pada seluruh array bukan pada basis per elemen. Misalnya jika f (x) adalah 'lambda x: x + x "maka jawabannya sangat berbeda karena numpy menggabungkan array daripada melakukan per penambahan elemen. Apakah ini benar-benar perbandingan yang dimaksudkan? Tolong jelaskan.
Andrew Mellinger
49

Ada numexpr , numba dan cython di sekitar, tujuan dari jawaban ini adalah untuk mempertimbangkan kemungkinan-kemungkinan ini.

Tapi pertama-tama mari kita nyatakan yang jelas: tidak peduli bagaimana Anda memetakan fungsi-Python ke array-numpy, tetap fungsi Python, yang berarti untuk setiap evaluasi:

  • elemen numpy-array harus dikonversi ke objek-Python (misalnya a Float).
  • semua perhitungan dilakukan dengan objek Python, yang berarti memiliki overhead juru bahasa, pengiriman dinamis, dan objek tidak berubah.

Jadi mesin mana yang digunakan untuk benar-benar loop melalui array tidak memainkan peran besar karena overhead yang disebutkan di atas - itu tetap jauh lebih lambat daripada menggunakan fungsi built-in numpy.

Mari kita lihat contoh berikut:

# 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.vectorizedipilih sebagai perwakilan dari kelas fungsi python murni dari pendekatan. Dengan menggunakan perfplot(lihat kode pada lampiran jawaban ini) kami mendapatkan waktu berjalan berikut:

masukkan deskripsi gambar di sini

Kita bisa melihat, bahwa pendekatan numpy adalah 10x-100x lebih cepat daripada versi python murni. Penurunan kinerja untuk ukuran array yang lebih besar mungkin karena data tidak lagi sesuai dengan cache.

Perlu juga disebutkan, yang vectorizejuga menggunakan banyak memori, jadi sering penggunaan memori adalah botol-leher (lihat pertanyaan SO terkait ). Juga perhatikan, bahwa dokumentasi numpy np.vectorizemenyatakan bahwa itu "disediakan terutama untuk kenyamanan, bukan untuk kinerja".

Alat lain harus digunakan, ketika kinerja diinginkan, selain menulis ekstensi-C dari awal, ada beberapa kemungkinan berikut:


Orang sering mendengar, bahwa kinerja numpy sebagus yang didapat, karena murni C di bawah tenda. Namun ada banyak ruang untuk perbaikan!

Versi numpy vektor menggunakan banyak memori tambahan dan akses memori. Numexp-library mencoba untuk men-tile numpy-array dan karenanya mendapatkan pemanfaatan cache yang lebih baik:

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

Mengarah ke perbandingan berikut:

masukkan deskripsi gambar di sini

Saya tidak bisa menjelaskan semuanya dalam plot di atas: kita bisa melihat overhead yang lebih besar untuk numexpr-library di awal, tetapi karena menggunakan cache lebih baik, ini sekitar 10 kali lebih cepat untuk array yang lebih besar!


Pendekatan lain adalah untuk mengkompilasi fungsi dan dengan demikian mendapatkan UFunc pure-C yang nyata. Ini adalah pendekatan 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

Ini 10 kali lebih cepat dari pendekatan numpy asli:

masukkan deskripsi gambar di sini


Namun, tugasnya memaralelkan, sehingga kita juga bisa menggunakan prangeuntuk menghitung loop secara paralel:

@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

Seperti yang diharapkan, fungsi paralel lebih lambat untuk input yang lebih kecil, tetapi lebih cepat (hampir faktor 2) untuk ukuran yang lebih besar:

masukkan deskripsi gambar di sini


Sementara numba berspesialisasi dalam mengoptimalkan operasi dengan numpy-array, Cython adalah alat yang lebih umum. Lebih rumit untuk mengekstraksi kinerja yang sama dengan numba - seringkali turun ke llvm (numba) vs kompiler lokal (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 menghasilkan fungsi yang agak lambat:

masukkan deskripsi gambar di sini


Kesimpulan

Jelas, pengujian hanya untuk satu fungsi tidak membuktikan apa-apa. Juga harus diingat, bahwa untuk fungsi-contoh yang dipilih, bandwidth memori adalah leher botol untuk ukuran lebih besar dari 10 ^ 5 elemen - sehingga kami memiliki kinerja yang sama untuk numba, numexpr dan cython di wilayah ini.

Pada akhirnya, jawaban ultimatif tergantung pada jenis fungsi, perangkat keras, distribusi Python dan faktor lainnya. Misalnya Anaconda-distribusi menggunakan Intel VML untuk fungsi numpy dan dengan demikian melebihi Numba (kecuali menggunakan SVML, melihat ini SO-posting ) mudah untuk fungsi-fungsi transendental seperti exp, sin, cosdan sejenis - lihat misalnya berikut SO-post .

Namun dari penyelidikan ini dan dari pengalaman saya sejauh ini, saya akan menyatakan, bahwa numba tampaknya menjadi alat yang paling mudah dengan kinerja terbaik selama tidak ada fungsi transendental yang terlibat.


Merencanakan waktu berjalan dengan perfplot -paket:

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)'
    )
benar-benar
sumber
1
Numba dapat menggunakan Intel SVML biasanya yang menghasilkan timing yang cukup dapat dibandingkan dengan Intel VML, tetapi implementasinya agak buggy dalam versi (0.43-0.47). Saya telah menambahkan plot kinerja stackoverflow.com/a/56939240/4045774 untuk perbandingan ke cy_expsum Anda.
max9111
29
squares = squarer(x)

Operasi aritmatika pada array secara otomatis diterapkan dengan elemen, dengan loop tingkat-C yang efisien yang menghindari semua overhead juru yang akan berlaku untuk loop level Python atau pemahaman.

Sebagian besar fungsi yang ingin Anda terapkan ke array NumPy hanya akan berfungsi, meskipun beberapa mungkin perlu diubah. Misalnya, iftidak berfungsi secara elemen. Anda ingin mengonversi yang menggunakan konstruksi seperti numpy.where:

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

menjadi

def using_where(x):
    return numpy.where(x < 5, x, x**2)
user2357112 mendukung Monica
sumber
9

Dalam banyak kasus, numpy.apply_along_axis akan menjadi pilihan terbaik. Ini meningkatkan kinerja sekitar 100x dibandingkan dengan pendekatan lain - dan tidak hanya untuk fungsi tes sepele, tetapi juga untuk komposisi fungsi yang lebih kompleks dari numpy dan scipy.

Ketika saya menambahkan metode:

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

ke kode perfplot, saya mendapatkan hasil berikut: masukkan deskripsi gambar di sini

LyteFM
sumber
Saya sangat terkejut dengan kenyataan bahwa kebanyakan orang tampaknya tidak menyadari hal yang sederhana, dapat diukur dan tertanam ini selama bertahun-tahun ....
Bill Huang
8

Saya percaya pada versi yang lebih baru (saya menggunakan 1,13) numpy Anda cukup memanggil fungsi dengan meneruskan array numpy ke fungsinya yang Anda tulis untuk tipe skalar, itu akan secara otomatis menerapkan pemanggilan fungsi ke setiap elemen melalui array numpy dan mengembalikan Anda array numpy lainnya

>>> 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])
Peiti Li
sumber
3
Ini bukan hal baru - itu selalu terjadi - ini adalah salah satu fitur inti dari numpy.
Eric
8
Ini adalah **operator yang melamar perhitungan untuk setiap t elemen t. Itu numpy biasa. Membungkusnya di lambdatidak melakukan apa-apa ekstra.
hpaulj
Ini tidak berfungsi jika pernyataan seperti saat ini ditampilkan.
TriHard8
7

Tampaknya tidak ada yang menyebutkan metode pabrik built-in memproduksi ufuncdalam paket numpy: np.frompyfuncyang telah saya uji lagi np.vectorizedan mengungguli sekitar 20 ~ 30%. Tentu saja itu akan bekerja dengan baik seperti kode C yang ditentukan atau bahkan numba(yang belum saya uji), tetapi bisa menjadi alternatif yang lebih baik daripadanp.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

Saya juga telah menguji sampel yang lebih besar, dan peningkatannya proporsional. Lihat dokumentasi juga di sini

Wunderbar
sumber
1
Saya mengulangi tes waktu di atas, dan juga menemukan peningkatan kinerja (lebih dari np.vektorat) sekitar 30%
Julian - BrainAnnex.org
2

Seperti yang disebutkan dalam posting ini , cukup gunakan ekspresi generator seperti:

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

Semua jawaban di atas sebanding dengan baik, tetapi jika Anda perlu menggunakan fungsi kustom untuk pemetaan, dan Anda miliki numpy.ndarray, dan Anda perlu mempertahankan bentuk array.

Saya telah membandingkan hanya dua, tetapi akan mempertahankan bentuk ndarray. Saya telah menggunakan array dengan 1 juta entri untuk perbandingan. Di sini saya menggunakan fungsi persegi, yang juga built in numpy dan memiliki peningkatan kinerja yang hebat, karena di sana seperti membutuhkan sesuatu, Anda dapat menggunakan fungsi pilihan Anda.

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)

Keluaran

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

di sini Anda dapat melihat dengan jelas numpy.fromiterpekerjaan yang bagus mengingat pendekatan yang sederhana, dan jika tersedia fungsi inbuilt, gunakan itu.

Rushikesh
sumber