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

#python #numpy

Вопрос:

Я хотел бы скопировать массив numpy 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)
 

Комментарии:

1. Просто new_arr = np.массив([a]*3) , где a = [[1,2],[1,2]] работал на меня.

2. @JStrahl: если a имеет форму (r,c), то np.array([a]*3) создается массив формы (3,r,c), в то время как я предполагаю, что OP хочет (r,c,3).

3. @минут ОП хочет (3, r, c), как видно из new_arr.shape = (3,2,2). Так что это работает.

4. В качестве вопроса о том, как копировать, вставлять и использовать [[1, 2], [1, 2]] в качестве примера, это плохо. Я надеюсь, что смогу отказаться от голоса.

5. @Symbol1 Я этого не уловил — в чем проблема?

Ответ №1:

Вероятно, самый чистый способ-это использовать 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 . Я мог бы скопировать содержимое a 3 раза в третьем измерении, затем скопировать содержимое 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
 

Комментарии:

1. Чтобы убедиться, что это действительно дает правильный результат, вы также можете распечатать b[:,:,0] , b[:,:,1] , и b[:,:,2] . Каждый срез третьего измерения является копией исходного 2D-массива. Это не так очевидно, просто взглянув print(b) .

2. В чем разница между None и np.newaxis? Когда я протестировал его, он дал тот же результат.

3. @wedran Они точно такие же — np.newaxis это просто псевдоним None

Ответ №2:

Другой способ-использовать 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 срезами в третьем измерении.

Комментарии:

1. Как это соотносится с reshape ? Быстрее? дает ту же структуру? Это определенно аккуратнее.

2. @AnderBiguri Я никогда не проводил сравнительный анализ… Я поместил это здесь в первую очередь для полноты картины. Будет интересно провести время и увидеть различия.

3. Я только что сделал img = np.dstack([arr] * 3) и работал отлично! Спасибо

4. Подумал, что я мог бы предложить лучший результат для повышения эффективности. Будучи старым постом, люди, возможно, пропустили это. Добавлено решение по этому вопросу и ответу.

Ответ №3:

Используйте представление и получите бесплатное время выполнения! Расширить универсальные n-dim массивы до n 1-dim

Введенный в NumPy 1.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 примером, получим тайминги и проверим, что вывод является a 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 ничего не изменило в таймингах, и оба они незначительны в единицах измерения времени. Следовательно, эффективен как по памяти, так и по производительности!

Ответ №4:

Теперь это также может быть достигнуто с помощью 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]]])
 

Ответ №5:

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

Отредактируйте @Mr. F, чтобы сохранить порядок измерений:

 B=B.T
 

Комментарии:

1. Это приводит к массиву N x 2 x 2 для меня, например B.shape , печатает (N, 2, 2) для любого значения N . Если вы транспонируете B с B.T помощью, то это соответствует ожидаемому результату.

2. @Мистер Ф — Вы правы. Это будет транслироваться по первому измерению, и таким образом B[0], B[1],... вы получите правильный срез, который я приведу и скажу, что его легче печатать , чем использовать B[:,:,0], B[:,:,1] , и т. Д.

3. Это может быть проще для ввода, но, например, если вы делаете это с данными изображений, это было бы в значительной степени неверно, поскольку почти все алгоритмы ожидают, что соглашения линейной алгебры будут использоваться для 2D-срезов пиксельных каналов. Трудно представить приложения, в которых вы начинаете с 2D-массива, обрабатываете строки и столбцы в соответствии с определенным соглашением, а затем вам нужно несколько копий одного и того же, расширяющихся на новую ось, но внезапно вы хотите, чтобы значение первой оси изменилось на новую ось…

4. @Мистер Ф — О, конечно. Я не могу догадаться, в каких приложениях вы захотите использовать 3D-матрицу в будущем. Тем не менее, все зависит от приложения. Фу-у-у, я предпочитаю то, к B[:,:,i] чему так хорошо привык.

Ответ №6:

Вот пример трансляции, который делает именно то, что было запрошено.

 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] и т. д.

Ответ №7:

Einops обеспечивает более удобный и читаемый способ записи повторов:

 y = einops.repeat(x, 'i j -> 3 i j')
 

Этот код добавляет еще одну ось длиной 3 в массив 2D numpy.