скопіюйте 2D масив у 3-й вимір, N разів (Python)


106

Я хотів би скопіювати нумерований 2D масив у третій вимір. Наприклад, з урахуванням масиву (2D) numpy:

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

перетворити його в 3D-матрицю з N таких копій у новому вимірі. Діючи arrз N = 3, вихід повинен бути:

new_arr = np.array([[[1,2],[1,2]],[[1,2],[1,2]],[[1,2],[1,2]]])
# new_arr.shape = (3, 2, 2)

Відповіді:


146

Напевно, найчистішим способом є використання 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]]

Сказавши це, ви часто можете уникати повторення масивів, використовуючи широкомовні програми . Наприклад, скажімо, що я хотів додати (3,)вектор:

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

до a. Я міг скопіювати вміст a3 рази у третій вимір, потім скопіювати вміст у cдва рази як у першому, так і в другому вимірах, так що обидва мої масиви були (2, 2, 3), а потім обчислити їх суму. Однак зробити це набагато простіше і швидше:

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

Тут a[..., None]має форму (2, 2, 1)і c[None, None, :]має форму (1, 1, 3)*. Коли я обчислюю суму, результат отримує "трансляцію" за розмірами 1, даючи мені форму (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]]

Трансляція - це дуже потужна техніка, оскільки вона дозволяє уникнути додаткових накладних витрат, створюваних повторних копій вхідних масивів у пам'яті.


* Хоча я включив їх для ясності, Noneіндекси в cнасправді не потрібні - ви також можете це зробити a[..., None] + c, тобто транслювати (2, 2, 1)масив проти (3,)масиву. Це тому , що якщо один з масивів мають менше розмірів , ніж інші , то тільки кінцеві розміри двох масивів повинні бути сумісні. Навести більш складний приклад:

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

Для того, щоб переконатися , що це дійсно дає правильний результат, ви можете також роздрукувати b[:,:,0], b[:,:,1]і b[:,:,2]. Кожен третій розмірний фрагмент є копією вихідного 2D масиву. Це не так очевидно, просто дивлячись print(b).
ely

Яка різниця між None і np.newaxis? Коли я перевірив це, він дав той же результат.
моноліт

1
@wedran Вони абсолютно однакові - np.newaxisце лише псевдонімNone
ali_m

27

Інший спосіб - використання numpy.dstack. Припустимо, що ви хочете повторити матричний a num_repeatsраз:

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

Трюк полягає в тому, щоб загортати матрицю aв список одного елемента, а потім використовувати *оператор для дублювання елементів у цьому спискуnum_repeats разів.

Наприклад, якщо:

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

Це повторює масив у [1 2; 1 2]5 разів у третьому вимірі. Щоб перевірити (в IPython):

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)

Наприкінці ми бачимо, що форма матриці має 2 x 25 фрагментів у третьому вимірі.


Як це порівнюється reshape? Швидше? дає однакову структуру? Це, безумовно, акуратніше.
Андер Бігурі

@AnderBiguri Я ніколи не орієнтувався ... Я вкладаю це насамперед для повноти. Буде цікаво час і побачити відмінності.
rayryeng

1
Я просто зробив img = np.dstack ([arr] * 3) і добре працював! Дякую
thanos.a

1
Зрозуміло, я можу запропонувати переглянутий вихід для ефективності. Будучи старим постом, люди, можливо, пропустили це. Додано рішення щодо цього питання.
Дивакар

1
IMHO - це найбільш читабельне рішення, але було б чудово порівняти його з іншими методами для порівняння.
mrgloom

16

Скористайтеся поданням та отримайте безкоштовний час виконання! Розширити родовеn-dim масиви доn+1-dim

Представлений у NumPy1.10.0 , ми можемо використовувати numpy.broadcast_toпросто генерування 3Dподання у 2Dвхідний масив. Користь не матиме зайвих витрат на пам'ять та практично безкоштовне виконання. Це було б вкрай важливо у випадках, коли масиви великі, і ми добре працюємо з думками. Крім того, це працювало б із загальнимn-dim випадками.

Я б використав це слово stackзамістьcopy , оскільки читачі можуть переплутати його з копіюванням масивів, які створюють копії пам'яті.

Складіть уздовж першої осі

Якщо ми хочемо скласти вхід arrпо першій осі, рішенням np.broadcast_toдля створення 3Dпредставлення буде:

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

Складіть уздовж третьої / останньої осі

Для arrскладання вводу вздовж третьої осі рішенням для створення 3Dперегляду було б -

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

Якщо нам справді потрібна копія пам'яті, ми завжди можемо її додати .copy(). Отже, рішення були б -

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

Ось як працює укладання для двох випадків, показана з їх формою для зразка випадку -

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

Це ж рішення (и) буде працювати над розширенням n-dimвходу для n+1-dimперегляду результатів по першій та останній осях. Давайте вивчимо кілька вищих темних випадків -

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)

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)

і так далі.

Хронометраж

Давайте скористаємося великим зразком 2Dвипадку і отримаємо терміни та перевіримо, чи є вихід view.

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

Доведемо, що запропоноване рішення справді є думкою. Ми будемо використовувати укладання вздовж першої осі (результати будуть дуже схожі на укладання вздовж третьої осі) -

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

Давайте визначимо, що це практично безкоштовно -

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

Будучи видом, збільшуючи Nвід , 3щоб 3000змінилися ні на таймингах і обидва є незначними по об'єктах синхронізації. Отже, ефективні як у пам’яті, так і в продуктивності!


3
A=np.array([[1,2],[3,4]])
B=np.asarray([A]*N)

Редагуйте @ Mr.F, щоб зберегти порядок розмірів:

B=B.T

Це призводить до отримання масиву N x 2 x 2, наприклад, для B.shapeдруку (N, 2, 2)будь-якого значення N. Якщо транспонувати Bз B.Tто , що він відповідає очікуваному результату.
ely

@ Mr.F - Ви маєте рацію. Це транслюватиметься за першим виміром, і, таким чином B[0], B[1],..., ви отримаєте правильний фрагмент, який я стверджую і скажу, що це легше набрати, а не використовувати B[:,:,0], B[:,:,1]тощо.
rayryeng

Це може бути простіше ввести, але, наприклад, якщо ви робите це з даними зображення, це в значній мірі буде неправильним, оскільки майже всі алгоритми очікують, що конвенції лінійної алгебри будуть використані для 2D-зрізів піксельних каналів. Важко уявити програми, де ви починаєте з 2D-масиву, обробляючи рядки та стовпці з певним умовою, і тоді вам потрібно, щоб кілька копій тієї самої речі поширилися на нову вісь, але раптом ви хочете, щоб перша вісь змінила значення на бути новою віссю ...
ely

@ Mr.F - О, звичайно. Я не можу здогадатися, в яких додатках ви хочете використовувати 3D-матрицю в майбутньому. За словами, все залежить від програми. FWIW, я віддаю перевагу B[:,:,i]так само, як і те, до чого я звик.
rayryeng

2

Ось приклад трансляції, який робить саме те, що було запропоновано.

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

Тоді b*aє бажаний результат і (b*a)[:,:,0]виробляється array([[1, 2],[1, 2]]), який є оригінальним a, як і (b*a)[:,:,1]т.д.


2

Тепер це також можна досягти за допомогою np.tile таким чином:

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]]])
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.