скопировать 2D-массив в 3-е измерение, N раз (Python)

111

Я хотел бы скопировать numpy 2D-массив в третье измерение. Например, учитывая массив (2D) numpy:

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

преобразовать ее в трехмерную матрицу с 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)
анон01
источник

Ответы:

147

Наверное, самый чистый способ - использовать 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
ali_m
источник
Для того, чтобы убедиться , что это действительно дает правильный результат, вы можете также распечатать b[:,:,0], b[:,:,1]и b[:,:,2]. Каждый срез третьего измерения является копией исходного 2D-массива. Это не так очевидно, просто глядя print(b).
Эли
В чем разница между 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 2из 5 срезов в третьем измерении.

Rayryeng
источник
Как это по сравнению с reshape? Быстрее? дает такую ​​же структуру? Это определенно аккуратнее.
Андер Бигури 07
@AnderBiguri Я никогда не тестировал ... Я поместил это здесь в первую очередь для полноты картины. Будет интересно провести время и увидеть отличия.
Rayryeng 07
1
Я только что сделал img = np.dstack ([arr] * 3) и работал нормально! Спасибо
thanos.a 05
1
Подумал, что могу предложить просматриваемый результат для повышения эффективности. Это старый пост, люди могли его пропустить. Добавил решение на этот вопрос и ответ.
Divakar
1
ИМХО самое читаемое решение, но было бы здорово сравнить его с другими методами для сравнения.
mrgloom
18

Используйте представление и получите бесплатную среду выполнения! Расширить универсальные 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он соответствует ожидаемому результату.
Эли
@ Mr.F - Вы правы. Это будет транслироваться по первому измерению, и это B[0], B[1],...даст вам правильный фрагмент, который я буду утверждать и скажу, что его легче набирать, чем использовать B[:,:,0], B[:,:,1]и т. Д.
rayryeng
Это может быть проще для ввода, но, например, если вы делаете это с данными изображения, это будет в значительной степени неверным, поскольку почти все алгоритмы ожидают, что соглашения линейной алгебры будут использоваться для 2D-срезов пиксельных каналов. Трудно представить себе приложения, в которых вы начинаете с 2D-массива, обрабатываете строки и столбцы с определенным соглашением, а затем хотите, чтобы несколько копий того же самого объекта простирались на новую ось, но внезапно вы хотите, чтобы первая ось изменила значение на будь новой осью ...
Эли
@ Mr.F - О, конечно. Я не могу предположить, в каких приложениях вы захотите использовать 3D-матрицу в будущем. При этом все зависит от приложения. FWIW, я предпочитаю то B[:,:,i], к чему я привык.
Rayryeng
3

Теперь это также можно получить с помощью 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]]])
FBruzzesi
источник
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]и т. Д.

Майк О'Коннор
источник