Peningkatan kinerja dalam transformasi numpy array
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
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.
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
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.
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)]