Peningkatan kinerja dalam transformasi numpy array

Oct 31 2020

Diberikan tiga numpyarray 1D, saya ingin mengubahnya sebagai berikut:

import numpy as np

Xd = np.asarray([0, 0,   1,   1,   0.5])
Yd = np.asarray([0, 0,   0,   2.5, 2.5])
Zd = np.asarray([0, 1.5, 1.5, 1.5, 1.5])

points = np.stack([Xd, Yd, Zd], axis=1).reshape(-1, 1, 3)
segments = np.concatenate([points[:-1], points[1:]], axis = 1)    

print(segments.shape)
print(segments)

Keluaran:

(4, 2, 3)
[[[0.  0.  0. ]
  [0.  0.  1.5]]

 [[0.  0.  1.5]
  [1.  0.  1.5]]

 [[1.  0.  1.5]
  [1.  2.5 1.5]]

 [[1.  2.5 1.5]
  [0.5 2.5 1.5]]]

Adakah cara untuk meningkatkan kinerja transformasi ini?

Latar Belakang

Transformasi ini diperlukan untuk menggunakan XYZkoordinat matplotlibdengan Line3DCollection. Sejauh ini, saya hanya melihat variasi dari kode di atas tetapi dengan ribuan koordinat atau data interpolasi untuk resolusi yang lebih baik, diperlukan pendekatan yang dioptimalkan.

Ringkasan

Berkat @Mercury , dapat disimpulkan bahwa untuk array yang lebih pendek (<1k panjangnya) jawaban oleh @Miguel berkinerja lebih baik tetapi pendekatan oleh @mathfux berskala jauh lebih baik ketika array semakin panjang.

Jawaban

3 mathfux Oct 31 2020 at 13:03

Sepertinya Anda mencoba menggulung bentuk jendela (2, 3)dalam larik 2D. Ini mirip dengan konvolusi gambar yang dapat dilakukan dengan np.lib.stride_trickscara yang sangat efisien.

a = np.transpose([Xd, Yd, Zd])
window = (2, 3)
view_shape = (len(a) - window[0] + 1,) + window # (4,2,3) if len(a) == 5
sub_matrix = np.lib.stride_tricks.as_strided(a, shape = view_shape, strides = (a.itemsize,) + a.strides)
>>> sub_matrix
array([[[0. , 0. , 0. ],
        [0. , 0. , 1.5]],

       [[0. , 0. , 1.5],
        [1. , 0. , 1.5]],

       [[1. , 0. , 1.5],
        [1. , 2.5, 1.5]],

       [[1. , 2.5, 1.5],
        [0.5, 2.5, 1.5]]])

Perhatikan bahwa np.lib.stride_trickssangat efektif terhadap cara alternatif apa pun.

4 Miguel Oct 31 2020 at 12:22

Sebagai saran umum, jika Anda menginginkan kecepatan, Anda sebaiknya mencoba menghindari tumpukan dan penggabungan, karena ini sering kali berarti menyalin data yang sama sekitar beberapa kali.

Bagaimanapun, inilah cara saya melakukannya, kode yang sedikit lebih panjang tetapi tidak melakukan lebih banyak pekerjaan daripada yang dibutuhkan

n = len(Xd)
segments = np.empty((n-1, 2, 3))

segments[:,0,0] = Xd[:-1]
segments[:,1,0] = Xd[1:]

segments[:,0,1] = Yd[:-1]
segments[:,1,1] = Yd[1:]

segments[:,0,2] = Zd[:-1]
segments[:,1,2] = Zd[1:]

[EDIT] - Berikut ini dibuat untuk sains / kesenangan, jangan mereproduksi e

Jadi saya mencoba untuk melihat apakah saya dapat memeras sedikit lebih banyak kinerja dari jawaban @mathfux , dan saya keluar dengan kode jelek ini:

a = np.empty(3*n)
a[:n]    = Xd
a[n:n+n] = Yd
a[n+n:]  = Zd

interface = dict(a.__array_interface__)
interface['shape'] = (n-1, 2, 3)
interface['strides'] = (a.itemsize, a.itemsize, n*a.itemsize)
segments= np.array(np.lib.stride_tricks.DummyArray(interface, base=a), copy=False)

Di mesin saya, ini terukur lebih cepat (hingga ~ 30% tergantung pada ukuran input). Keuntungan sebagian karena pembangunan adan melewatkan pemeriksaanas_strided

1 Mercury Oct 31 2020 at 13:03

Berikut adalah beberapa pengujian waktu, pada larik yang lebih besar, yang membuat perbedaannya lebih jelas.

import numpy as np
from timeit import timeit

# original
def f1(x, y, z):
    points = np.stack([x, y, z], axis=1).reshape(-1, 1, 3)
    return np.concatenate([points[:-1], points[1:]], axis = 1)

# preallocating and then assigning
def f2(x, y, z):
    segments = np.empty((len(x)-1, 2, 3))

    segments[:,0,0] = x[:-1]
    segments[:,1,0] = x[1:]

    segments[:,0,1] = y[:-1]
    segments[:,1,1] = y[1:]

    segments[:,0,2] = z[:-1]
    segments[:,1,2] = z[1:]
    return segments

# stacking, but in one go
def f3(x, y, z):
    segments = np.stack([x[:-1], y[:-1], z[:-1], x[1:], y[1:],z[1:]], axis=1)
    return segments.reshape(-1, 2, 3)

# list comparison
def f4(x, y, z):
    z_ = [i for i in zip(x,y,z)]
    return [[[z_[i]],[z_[i+1]]] for i in range(len(z_)-1)]

#np.lib.stride_tricks approach
def f5(x, y, z):
    a = np.transpose([x, y, z])
    window = (2, 3)
    view_shape = (len(a) - window[0] + 1,) + window # (4,2,3) if len(a) == 5
    return np.lib.stride_tricks.as_strided(a, shape = view_shape, strides = (a.itemsize,) + a.strides)
    

ntime = 5000 #number of test runs
nxd = 500    #array length

Xd = np.random.randn(nxd)
Yd = np.random.randn(nxd)
Zd = np.random.randn(nxd)

print(timeit(lambda: f1(Xd, Yd, Zd), number=ntime))
#0.11369249999999999

print(timeit(lambda: f2(Xd, Yd, Zd), number=ntime))
#0.0480651

print(timeit(lambda: f3(Xd, Yd, Zd), number=ntime))
#0.10202380000000003

print(timeit(lambda: f4(Xd, Yd, Zd), number=ntime))
#1.8407391

print(timeit(lambda: f5(Xd, Yd, Zd), number=ntime))
#0.09132560000000023
    
ntime = 50     #number of test runs
nxd = 500000   #array length

Xd = np.random.randn(nxd)
Yd = np.random.randn(nxd)
Zd = np.random.randn(nxd)

print(timeit(lambda: f1(Xd, Yd, Zd), number=ntime))
#1.7519548999999999

print(timeit(lambda: f2(Xd, Yd, Zd), number=ntime))
#1.504727

print(timeit(lambda: f3(Xd, Yd, Zd), number=ntime))
#1.5010566

print(timeit(lambda: f4(Xd, Yd, Zd), number=ntime))
#22.6208157

print(timeit(lambda: f5(Xd, Yd, Zd), number=ntime))
#0.46465339999999955

Seperti yang Anda lihat, cara @ Miguel adalah caranya: melakukan pra-alokasi array dan kemudian menetapkan adalah cara paling efisien untuk melakukan ini. Bahkan jika Anda menumpuknya dengan cara yang lebih cerdas seperti di f3 (), ini masih lebih lambat dari f2 (). Tapi tidak ada yang mengalahkan f5 () ketika panjang array meningkat secara substansial.

venky__ Oct 31 2020 at 12:31

Saya menemukan ini lebih cepat daripada kode @ Miguel.

z = [i for i in zip(Xd,Yd,Zd)]
segments = [[[z[i]],[z[i+1]]] for i in range(len(z)-1)]