skopiuj tablicę 2D do trzeciego wymiaru, N razy (Python)

108

Chciałbym skopiować numpy tablicę 2D do trzeciego wymiaru. Na przykład, biorąc pod uwagę tablicę numpy (2D):

import numpy as np
arr = np.array([[1,2],[1,2]])
# arr.shape = (2, 2)

przekształcić go w macierz 3D z N takimi kopiami w nowym wymiarze. Działając arrz N = 3, wyjście powinno być:

new_arr = np.array([[[1,2],[1,2]],[[1,2],[1,2]],[[1,2],[1,2]]])
# new_arr.shape = (3, 2, 2)
anon01
źródło

Odpowiedzi:

146

Prawdopodobnie najczystszym sposobem jest użycie np.repeat:

a = np.array([[1, 2], [1, 2]])
print(a.shape)
# (2,  2)

# indexing with np.newaxis inserts a new 3rd dimension, which we then repeat the
# array along, (you can achieve the same effect by indexing with None, see below)
b = np.repeat(a[:, :, np.newaxis], 3, axis=2)

print(b.shape)
# (2, 2, 3)

print(b[:, :, 0])
# [[1 2]
#  [1 2]]

print(b[:, :, 1])
# [[1 2]
#  [1 2]]

print(b[:, :, 2])
# [[1 2]
#  [1 2]]

Powiedziawszy to, często można całkowicie uniknąć powtarzania tablic, używając rozgłaszania . Na przykład, powiedzmy, że chcę dodać (3,)wektor:

c = np.array([1, 2, 3])

do a. Mogłem skopiować zawartość a3 razy w trzecim wymiarze, a następnie skopiować zawartość cdwukrotnie w pierwszym i drugim wymiarze, tak aby obie moje tablice były (2, 2, 3), a następnie obliczyć ich sumę. Jest to jednak znacznie prostsze i szybsze:

d = a[..., None] + c[None, None, :]

Tutaj a[..., None]ma kształt (2, 2, 1)i c[None, None, :]ma kształt (1, 1, 3)*. Kiedy obliczam sumę, wynik jest `` rozrzucany '' wzdłuż wymiarów rozmiaru 1, dając mi wynik kształtu (2, 2, 3):

print(d.shape)
# (2,  2, 3)

print(d[..., 0])    # a + c[0]
# [[2 3]
#  [2 3]]

print(d[..., 1])    # a + c[1]
# [[3 4]
#  [3 4]]

print(d[..., 2])    # a + c[2]
# [[4 5]
#  [4 5]]

Rozgłaszanie jest bardzo skuteczną techniką, ponieważ pozwala uniknąć dodatkowego obciążenia związanego z tworzeniem powtarzających się kopii tablic wejściowych w pamięci.


* Chociaż włączyłem je dla jasności, Noneindeksy w cnie są w rzeczywistości potrzebne - możesz to zrobić a[..., None] + c, np. Rozgłaszać (2, 2, 1)tablicę względem (3,)tablicy. Dzieje się tak, ponieważ jeśli jedna z tablic ma mniej wymiarów niż druga, to tylko końcowe wymiary dwóch tablic muszą być zgodne. Aby podać bardziej skomplikowany przykład:

a = np.ones((6, 1, 4, 3, 1))  # 6 x 1 x 4 x 3 x 1
b = np.ones((5, 1, 3, 2))     #     5 x 1 x 3 x 2
result = a + b                # 6 x 5 x 4 x 3 x 2
ali_m
źródło
Aby sprawdzić, czy to rzeczywiście daje prawo wynik, można również wydrukować b[:,:,0], b[:,:,1]i b[:,:,2]. Każdy trzeci wycinek wymiaru jest kopią oryginalnej szyku 2D. Nie jest to takie oczywiste samo patrzenie print(b).
ely
Jaka jest różnica między None i np.newaxis? Kiedy go przetestowałem, dał ten sam wynik.
monolit
1
@wedran Są dokładnie takie same - np.newaxisto tylko aliasNone
ali_m
27

Innym sposobem jest użycie numpy.dstack. Przypuśćmy, że chcesz powtórzyć a num_repeatsczasy macierzy :

import numpy as np
b = np.dstack([a]*num_repeats)

Sztuczka polega na zawinięciu macierzy aw listę pojedynczego elementu, a następnie użycie *operatora do zduplikowania elementów z tej listy num_repeatsrazy.

Na przykład, jeśli:

a = np.array([[1, 2], [1, 2]])
num_repeats = 5

Powtarza to tablicę [1 2; 1 2]5 razy w trzecim wymiarze. Aby zweryfikować (w IPythonie):

In [110]: import numpy as np

In [111]: num_repeats = 5

In [112]: a = np.array([[1, 2], [1, 2]])

In [113]: b = np.dstack([a]*num_repeats)

In [114]: b[:,:,0]
Out[114]: 
array([[1, 2],
       [1, 2]])

In [115]: b[:,:,1]
Out[115]: 
array([[1, 2],
       [1, 2]])

In [116]: b[:,:,2]
Out[116]: 
array([[1, 2],
       [1, 2]])

In [117]: b[:,:,3]
Out[117]: 
array([[1, 2],
       [1, 2]])

In [118]: b[:,:,4]
Out[118]: 
array([[1, 2],
       [1, 2]])

In [119]: b.shape
Out[119]: (2, 2, 5)

Na końcu widzimy, że kształt matrycy jest 2 x 2z 5 warstwami w trzecim wymiarze.

rayryeng
źródło
Jak to się ma do tego reshape? Szybciej? daje taką samą strukturę? Jest zdecydowanie schludniej.
Ander Biguri,
@AnderBiguri Nigdy nie testowałem testu porównawczego ... Umieściłem to tutaj przede wszystkim dla kompletności. Ciekawie będzie zmierzyć się z czasem i zobaczyć różnice.
rayryeng
1
Właśnie zrobiłem img = np.dstack ([arr] * 3) i działało dobrze! Dziękuję
thanos.a
1
Pomyślałem, że mógłbym zaproponować oglądaną wydajność pod kątem wydajności. Będąc starym postem, ludzie mogli to przeoczyć. Dodano rozwiązanie do tego pytania i odpowiedzi.
Divakar
1
Najbardziej czytelne rozwiązanie IMHO, ale byłoby wspaniale porównać je z innymi metodami do porównania.
mrgloom
16

Użyj widoku i uzyskaj bezpłatne środowisko uruchomieniowe! Rozszerz n-dimtablice ogólne don+1-dim

Wprowadzone w NumPy1.10.0 , możemy wykorzystać, numpy.broadcast_toaby po prostu wygenerować 3Dwidok w 2Dtablicy wejściowej. Korzyścią byłoby brak dodatkowego obciążenia pamięci i praktycznie wolne środowisko wykonawcze. Byłoby to istotne w przypadkach, gdy tablice są duże i możemy pracować z widokami. Działa to również w n-dimprzypadku ogólnych przypadków.

Użyłbym tego słowa stackzamiast copy, ponieważ czytelnicy mogliby pomylić je z kopiowaniem tablic, które tworzą kopie pamięci.

Stos wzdłuż pierwszej osi

Jeśli chcemy ułożyć dane wejściowe arrwzdłuż pierwszej osi, rozwiązaniem np.broadcast_todo utworzenia 3Dwidoku byłoby -

np.broadcast_to(arr,(3,)+arr.shape) # N = 3 here

Stos wzdłuż trzeciej / ostatniej osi

Aby ułożyć dane wejściowe arrwzdłuż trzeciej osi, rozwiązaniem do utworzenia 3Dwidoku byłoby -

np.broadcast_to(arr[...,None],arr.shape+(3,))

Jeśli faktycznie potrzebujemy kopii pamięci, zawsze możemy ją .copy()tam dołączyć . W związku z tym rozwiązania byłyby:

np.broadcast_to(arr,(3,)+arr.shape).copy()
np.broadcast_to(arr[...,None],arr.shape+(3,)).copy()

Oto jak działa układanie w stos dla dwóch przypadków, pokazane wraz z informacjami o ich kształcie dla przykładowej sprawy -

# Create a sample input array of shape (4,5)
In [55]: arr = np.random.rand(4,5)

# Stack along first axis
In [56]: np.broadcast_to(arr,(3,)+arr.shape).shape
Out[56]: (3, 4, 5)

# Stack along third axis
In [57]: np.broadcast_to(arr[...,None],arr.shape+(3,)).shape
Out[57]: (4, 5, 3)

Te same rozwiązania działałyby, gdyby rozszerzyć dane n-dimwejściowe, aby n+1-dimwyświetlić dane wyjściowe wzdłuż pierwszej i ostatniej osi. Zbadajmy kilka bardziej ciemnych przypadków -

Obudowa wejściowa 3D:

In [58]: arr = np.random.rand(4,5,6)

# Stack along first axis
In [59]: np.broadcast_to(arr,(3,)+arr.shape).shape
Out[59]: (3, 4, 5, 6)

# Stack along last axis
In [60]: np.broadcast_to(arr[...,None],arr.shape+(3,)).shape
Out[60]: (4, 5, 6, 3)

Obudowa wejściowa 4D:

In [61]: arr = np.random.rand(4,5,6,7)

# Stack along first axis
In [62]: np.broadcast_to(arr,(3,)+arr.shape).shape
Out[62]: (3, 4, 5, 6, 7)

# Stack along last axis
In [63]: np.broadcast_to(arr[...,None],arr.shape+(3,)).shape
Out[63]: (4, 5, 6, 7, 3)

i tak dalej.

Czasy

Wykorzystajmy duży przykładowy 2Dprzypadek, pobierzmy czasy i sprawdźmy, czy wyjście jest plikiem view.

# Sample input array
In [19]: arr = np.random.rand(1000,1000)

Udowodnijmy, że proponowane rozwiązanie jest rzeczywiście poglądem. Użyjemy układania wzdłuż pierwszej osi (wyniki byłyby bardzo podobne do układania wzdłuż trzeciej osi) -

In [22]: np.shares_memory(arr, np.broadcast_to(arr,(3,)+arr.shape))
Out[22]: True

Sprawdźmy czasy, aby pokazać, że jest to praktycznie bezpłatne -

In [20]: %timeit np.broadcast_to(arr,(3,)+arr.shape)
100000 loops, best of 3: 3.56 µs per loop

In [21]: %timeit np.broadcast_to(arr,(3000,)+arr.shape)
100000 loops, best of 3: 3.51 µs per loop

Będąc widokiem, zwiększając się Nz 3do 3000niczego nie zmienia się w czasie i oba są pomijalne w jednostkach czasowych. Dlatego wydajne zarówno pod względem pamięci, jak i wydajności!

Divakar
źródło
3
A=np.array([[1,2],[3,4]])
B=np.asarray([A]*N)

Edytuj @ Mr.F, aby zachować kolejność wymiarów:

B=B.T
yevgeniy
źródło
Daje to dla mnie tablicę N x 2 x 2, np. B.shapeDrukuje (N, 2, 2)dla dowolnej wartości N. Jeśli dokonasz transpozycji Bz, B.Tto pasuje do oczekiwanego wyniku.
ely
@ Mr.F - Masz rację. To będzie nadawane w pierwszym wymiarze, a to B[0], B[1],...da ci właściwy wycinek, który będę argumentować i powiem, że jest łatwiejszy do B[:,:,0], B[:,:,1]
wpisania
Może to być łatwiejsze do wpisania, ale na przykład, jeśli robisz to z danymi obrazu, byłoby to w dużej mierze niepoprawne, ponieważ prawie wszystkie algorytmy będą oczekiwać, że konwencje algebry liniowej zostaną użyte dla wycinków 2D kanałów pikseli. Trudno wyobrazić sobie aplikacje, w których zaczynasz od tablicy 2D, traktując wiersze i kolumny zgodnie z określoną konwencją, a następnie chcesz, aby wiele kopii tej samej rzeczy rozciągało się na nową oś, ale nagle chcesz, aby pierwsza oś zmieniła znaczenie na bądź nową osią ...
ely
@ Mr.F - Och, oczywiście. Nie mogę zgadnąć, w jakich aplikacjach będziesz chciał używać matrycy 3D w przyszłości. To powiedziawszy, wszystko zależy od aplikacji. FWIW, wolę B[:,:,i]i do tego jestem przyzwyczajony.
rayryeng
2

Oto przykład nadawania, który robi dokładnie to, o co proszono.

a = np.array([[1, 2], [1, 2]])
a=a[:,:,None]
b=np.array([1]*5)[None,None,:]

Następnie b*ajest pożądany rezultat i (b*a)[:,:,0]produkuje array([[1, 2],[1, 2]]), który jest oryginałem a, tak jak robi (b*a)[:,:,1], itd.

Mike O'Connor
źródło
2

Można to teraz również osiągnąć za pomocą np.tile w następujący sposób:

import numpy as np

a = np.array([[1,2],[1,2]])
b = np.tile(a,(3, 1,1))

b.shape
(3,2,2)

b
array([[[1, 2],
        [1, 2]],

       [[1, 2],
        [1, 2]],

       [[1, 2],
        [1, 2]]])
FBruzzesi
źródło