Pracuję z 3D pointcloud firmy Lidar. Punkty są przyznawane przez tablicę numpy, która wygląda następująco:
points = np.array([[61651921, 416326074, 39805], [61605255, 416360555, 41124], [61664810, 416313743, 39900], [61664837, 416313749, 39910], [61674456, 416316663, 39503], [61651933, 416326074, 39802], [61679969, 416318049, 39500], [61674494, 416316677, 39508], [61651908, 416326079, 39800], [61651908, 416326087, 39802], [61664845, 416313738, 39913], [61674480, 416316668, 39503], [61679996, 416318047, 39510], [61605290, 416360572, 41118], [61605270, 416360565, 41122], [61683939, 416313004, 41052], [61683936, 416313033, 41060], [61679976, 416318044, 39509], [61605279, 416360555, 41109], [61664837, 416313739, 39915], [61674487, 416316666, 39505], [61679961, 416318035, 39503], [61683943, 416313004, 41054], [61683930, 416313042, 41059]])
Chciałbym, aby moje dane pogrupowane w kostkę wielkości 50*50*50
tak, że każda kostka zachowuje pewną hashable indeksu i NumPy indeksy z moich points
zawiera . Aby uzyskać podział, przypisuję cubes = points \\ 50
które wyjścia do:
cubes = np.array([[1233038, 8326521, 796], [1232105, 8327211, 822], [1233296, 8326274, 798], [1233296, 8326274, 798], [1233489, 8326333, 790], [1233038, 8326521, 796], [1233599, 8326360, 790], [1233489, 8326333, 790], [1233038, 8326521, 796], [1233038, 8326521, 796], [1233296, 8326274, 798], [1233489, 8326333, 790], [1233599, 8326360, 790], [1232105, 8327211, 822], [1232105, 8327211, 822], [1233678, 8326260, 821], [1233678, 8326260, 821], [1233599, 8326360, 790], [1232105, 8327211, 822], [1233296, 8326274, 798], [1233489, 8326333, 790], [1233599, 8326360, 790], [1233678, 8326260, 821], [1233678, 8326260, 821]])
Moje pożądane wyniki wyglądają następująco:
{(1232105, 8327211, 822): [1, 13, 14, 18]),
(1233038, 8326521, 796): [0, 5, 8, 9],
(1233296, 8326274, 798): [2, 3, 10, 19],
(1233489, 8326333, 790): [4, 7, 11, 20],
(1233599, 8326360, 790): [6, 12, 17, 21],
(1233678, 8326260, 821): [15, 16, 22, 23]}
Moja prawdziwa chmura punktów zawiera do kilkuset milionów punktów 3D. Jaki jest najszybszy sposób na grupowanie tego rodzaju?
Wypróbowałem większość różnych rozwiązań. Oto porównanie obliczania czasu przy założeniu, że wielkość punktów wynosi około 20 milionów, a wielkość odrębnych kostek wynosi około 1 miliona:
Pandas [tuple (elem) -> np.array (dtype = int64)]
import pandas as pd
print(pd.DataFrame(cubes).groupby([0,1,2]).indices)
#takes 9sec
Defauldict [elem.tobytes () or tuple -> list]
#thanks @abc:
result = defaultdict(list)
for idx, elem in enumerate(cubes):
result[elem.tobytes()].append(idx) # takes 20.5sec
# result[elem[0], elem[1], elem[2]].append(idx) #takes 27sec
# result[tuple(elem)].append(idx) # takes 50sec
numpy_indexed [int -> np.array]
# thanks @Eelco Hoogendoorn for his library
values = npi.group_by(cubes).split(np.arange(len(cubes)))
result = dict(enumerate(values))
# takes 9.8sec
Pandy + redukcja wymiarowości [int -> np.array (dtype = int64)]
# thanks @Divakar for showing numexpr library:
import numexpr as ne
def dimensionality_reduction(cubes):
#cubes = cubes - np.min(cubes, axis=0) #in case some coords are negative
cubes = cubes.astype(np.int64)
s0, s1 = cubes[:,0].max()+1, cubes[:,1].max()+1
d = {'s0':s0,'s1':s1,'c0':cubes[:,0],'c1':cubes[:,1],'c2':cubes[:,2]}
c1D = ne.evaluate('c0+c1*s0+c2*s0*s1',d)
return c1D
cubes = dimensionality_reduction(cubes)
result = pd.DataFrame(cubes).groupby([0]).indices
# takes 2.5 seconds
Można pobrać cubes.npz
plik tutaj i użyć polecenia
cubes = np.load('cubes.npz')['array']
sprawdzić czas działania.
numpy_indexed
do tego podchodzi. Myślę, że to prawda.pandas
Obecnie używam do swoich procesów klasyfikacji.Odpowiedzi:
Stała liczba wskaźników na grupę
Podejście nr 1
Możemy wykonać
dimensionality-reduction
redukcjęcubes
do tablicy 1D. Jest to oparte na odwzorowaniu danych danych kostek na siatkę n-dim w celu obliczenia ekwiwalentów indeksu liniowego, omówionych szczegółowohere
. Następnie, w oparciu o wyjątkowość tych liniowych wskaźników, możemy segregować unikalne grupy i odpowiadające im wskaźniki. Dlatego zgodnie z tymi strategiami mielibyśmy jedno rozwiązanie, takie jak -Alternatywa # 1: Jeśli wartości całkowite w
cubes
są zbyt duże, możemy chcieć zrobićdimensionality-reduction
tak, aby wymiary o mniejszym zasięgu były wybierane jako osie podstawowe. Dlatego w tych przypadkach możemy zmodyfikować krok redukcji, aby uzyskaćc1D
-Podejście nr 2
Następnie możemy użyć
Cython-powered kd-tree
szybkiego wyszukiwania najbliższego sąsiada, aby uzyskać indeksy najbliższego sąsiedztwa, a tym samym rozwiązać nasz przypadek w ten sposób -Przypadek ogólny: Zmienna liczba wskaźników na grupę
Rozszerzymy metodę opartą na argsort z pewnym podziałem, aby uzyskać pożądaną wydajność, tak jak -
Korzystanie z wersji 1D grup
cubes
jako kluczyRozszerzymy wcześniej wymienioną metodę o grupy
cubes
kluczy as, aby uprościć proces tworzenia słownika, a także zwiększyć jego efektywność, tak jak to -Następnie wykorzystamy
numba
pakiet do iteracji i przejdziemy do ostatecznego wyjścia słownika haszującego. Idąc za tym, będą dwa rozwiązania - jedno, które pobiera klucze i wartości oddzielnie,numba
a główne wywołanie zostanie skompresowane i zamienione na dyktowanie, a drugie stworzynumba-supported
typ dykta, a zatem główna funkcja wywoływania nie wymaga dodatkowej pracy .Zatem mielibyśmy pierwsze
numba
rozwiązanie:I drugie
numba
rozwiązanie jako:Czasy z
cubes.npz
danymi -Alternatywa # 1: Możemy osiągnąć dalsze przyspieszenie przy
numexpr
obliczaniu dużych tablicc1D
, tak jak -Miałoby to zastosowanie we wszystkich miejscach, które tego wymagają
c1D
.źródło
dtypes
int32
iint64
number of indices per group would be a constant number
że zebrałem komentarze. Czy byłoby to bezpieczne założenie? Czy testujeszcubes.npz
także dane wyjściowe915791
?cubes.npz
tylko i dotyczy to983234
innych podejść, które zasugerowałem.Approach #3
ogólny przypadek zmiennej liczby indeksów.Możesz po prostu iterować i dodać indeks każdego elementu do odpowiedniej listy.
Środowisko wykonawcze można dodatkowo ulepszyć za pomocą tobytes () zamiast konwertowania klucza na krotkę.
źródło
res[tuple(elem)].append(idx)
zajęła 50 sekund w porównaniu do jej edycji,res[elem[0], elem[1], elem[2]].append(idx)
która zajęła 30 sekund.Możesz użyć Cython:
ale nie sprawi, że będziesz szybszy niż to, co robi Panda, chociaż po tym jest najszybszy (i być może
numpy_index
oparte na rozwiązaniu) i nie wiąże się z karą pamięci. Zbiór tego, co dotychczas zaproponowano, znajduje się tutaj .W maszynie OP, która powinna zbliżyć się do ~ 12 sekund czasu wykonania.
źródło