copie a matriz 2D na 3ª dimensão, N vezes (Python)

106

Eu gostaria de copiar um array 2D numpy para uma terceira dimensão. Por exemplo, dada a matriz numpy (2D):

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

converta-o em uma matriz 3D com N tais cópias em uma nova dimensão. Agindo arrcom N = 3, a saída deve ser:

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

Respostas:

146

Provavelmente, a maneira mais limpa é usar 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]]

Dito isso, muitas vezes você pode evitar a repetição de seus arrays completamente usando a transmissão . Por exemplo, digamos que eu queira adicionar um (3,)vetor:

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

a a. Eu poderia copiar o conteúdo de a3 vezes na terceira dimensão e, em seguida, copiar o conteúdo de cduas vezes na primeira e na segunda dimensões, de forma que meus dois arrays fossem (2, 2, 3), então calcular sua soma. No entanto, é muito mais simples e rápido fazer isso:

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

Aqui, a[..., None]tem forma (2, 2, 1)e c[None, None, :]tem forma (1, 1, 3)*. Quando eu calculo a soma, o resultado é 'transmitido' ao longo das dimensões de tamanho 1, dando-me um resultado de forma (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]]

A transmissão é uma técnica muito poderosa porque evita a sobrecarga adicional envolvida na criação de cópias repetidas de seus arrays de entrada na memória.


* Embora eu os tenha incluído para maior clareza, os Noneíndices em cnão são realmente necessários - você também pode fazer a[..., None] + c, ou seja, transmitir um (2, 2, 1)array contra um (3,)array. Isso ocorre porque, se um dos arrays tiver menos dimensões do que o outro, apenas as dimensões finais dos dois arrays precisam ser compatíveis. Para dar um exemplo mais complicado:

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
fonte
Para verificar que este fato dá o resultado certo, você também pode imprimir b[:,:,0], b[:,:,1]e b[:,:,2]. Cada fatia da terceira dimensão é uma cópia da matriz 2D original. Isso não é tão óbvio apenas olhando print(b).
ely
Qual é a diferença entre None e np.newaxis? Quando testei, deu o mesmo resultado.
monólito
1
@wedran Eles são exatamente iguais - np.newaxisé apenas um pseudônimo deNone
ali_m
27

Outra forma é usar numpy.dstack. Supondo que você queira repetir os a num_repeatstempos da matriz :

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

O truque é envolver a matriz aem uma lista de um único elemento e, em seguida, usar o *operador para duplicar os elementos nesta lista num_repeatsvezes.

Por exemplo, se:

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

Isso repete a matriz de [1 2; 1 2]5 vezes na terceira dimensão. Para verificar (em 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)

Ao final podemos ver que o formato da matriz é 2 x 2, com 5 fatias na terceira dimensão.

Rayryeng
fonte
Como isso se compara a reshape? Mais rápido? dá a mesma estrutura? É definitivamente mais puro.
Ander Biguri
@AnderBiguri Eu nunca fiz benchmarking ... Coloquei isso aqui principalmente para ser completo. Será interessante cronometrar e ver as diferenças.
rayryeng
1
Acabei de fazer img = np.dstack ([arr] * 3) e funcionou bem! Obrigado
thanos.a
1
Descobri que poderia propor uma saída visualizada para eficiência. Por ser um post antigo, as pessoas podem ter perdido isso. Adicionada uma solução neste Q&A.
Divakar
1
Solução mais legível IMHO, mas seria ótimo compará-la com outros métodos de comparação.
mrgloom
16

Use uma visualização e obtenha tempo de execução grátis! Estenda n-dimmatrizes genéricas paran+1-dim

Introduzido no NumPy1.10.0 , podemos alavancar numpy.broadcast_topara simplesmente gerar uma 3Dvisualização na 2Dmatriz de entrada. O benefício seria sem sobrecarga de memória extra e tempo de execução virtualmente livre. Isso seria essencial nos casos em que os arrays são grandes e podemos trabalhar com visualizações. Além disso, isso funcionaria com n-dimcasos genéricos .

Eu usaria a palavra stackno lugar de copy, pois os leitores podem confundi-la com a cópia de matrizes que cria cópias de memória.

Empilhar ao longo do primeiro eixo

Se quisermos empilhar a entrada arrao longo do primeiro eixo, a solução np.broadcast_topara criar a 3Dvisualização seria -

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

Empilhar ao longo do terceiro / último eixo

Para empilhar a entrada arrao longo do terceiro eixo, a solução para criar a 3Dvisualização seria -

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

Se realmente precisarmos de uma cópia da memória, podemos sempre anexá .copy()-la. Portanto, as soluções seriam -

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

Veja como funciona o empilhamento para os dois casos, mostrado com suas informações de forma para um caso de amostra -

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

A (s) mesma (s) solução (ões) funcionariam para estender uma n-dimentrada para n+1-dimvisualizar a saída ao longo do primeiro e do último eixo. Vamos explorar alguns casos de maior dim -

Caso de entrada 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)

Caso de entrada 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)

e assim por diante.

Horários

Vamos usar um grande 2Dcaso de amostra e obter os tempos e verificar se a saída é a view.

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

Vamos provar que a solução proposta é de fato uma visão. Usaremos empilhamento ao longo do primeiro eixo (os resultados seriam muito semelhantes para empilhamento ao longo do terceiro eixo) -

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

Vamos ver os horários para mostrar que é virtualmente grátis -

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

Sendo uma vista, aumentando Nde 3a 3000não mudou nada nas temporizações e ambos são insignificantes nas unidades de temporização. Conseqüentemente, eficiente tanto em memória quanto em desempenho!

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

Edite @ Mr.F, para preservar a ordem das dimensões:

B=B.T
Yevgeniy
fonte
Isso resulta em uma matriz N x 2 x 2 para mim, por exemplo, B.shapeimpressões (N, 2, 2)para qualquer valor de N. Se você transpor Bcom B.T, ele corresponde à saída esperada.
ely
@ Mr.F - Você está correto. Isso será transmitido ao longo da primeira dimensão e, ao fazer isso B[0], B[1],..., você terá a fatia certa, que vou argumentar e dizer que é mais fácil de digitar do que usar B[:,:,0], B[:,:,1], etc.
rayryeng
Pode ser mais fácil de digitar, mas, por exemplo, se você estiver fazendo isso com dados de imagem, seria amplamente incorreto, uma vez que quase todos os algoritmos esperam que as convenções da álgebra linear sejam usadas para as fatias 2D de canais de pixel. É difícil imaginar um aplicativo onde você começa com uma matriz 2D, tratando linhas e colunas com uma certa convenção, e então você quer várias cópias dessa mesma coisa se estendendo para um novo eixo, mas de repente você quer que o primeiro eixo mude de significado para ser o novo eixo ...
ely
@ Mr.F - Oh certamente. Não consigo adivinhar em quais aplicativos você desejará usar a matriz 3D no futuro. Dito isso, tudo depende do aplicativo. FWIW, eu prefiro o B[:,:,i]assim como é a que estou acostumada.
rayryeng
2

Aqui está um exemplo de transmissão que faz exatamente o que foi solicitado.

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

Então b*aé o resultado desejado e (b*a)[:,:,0]produz array([[1, 2],[1, 2]]), que é o original a, como faz (b*a)[:,:,1], etc.

Mike O'Connor
fonte
2

Isso agora também pode ser obtido usando np.tile da seguinte maneira:

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
fonte