copier un tableau 2D en 3ème dimension, N fois (Python)

107

Je voudrais copier un tableau 2D numpy dans une troisième dimension. Par exemple, étant donné le tableau numpy (2D):

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

le convertir en une matrice 3D avec N de ces copies dans une nouvelle dimension. En agissant arravec N = 3, la sortie doit être:

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

Réponses:

146

Le moyen le plus propre est probablement d'utiliser 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]]

Cela dit, vous pouvez souvent éviter de répéter complètement vos tableaux en utilisant la diffusion . Par exemple, disons que je voulais ajouter un (3,)vecteur:

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

à a. Je pourrais copier le contenu de a3 fois dans la troisième dimension, puis copier le contenu de cdeux fois dans les première et deuxième dimensions, de sorte que mes deux tableaux soient (2, 2, 3), puis calculer leur somme. Cependant, c'est beaucoup plus simple et rapide de le faire:

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

Ici, a[..., None]a forme (2, 2, 1)et c[None, None, :]a forme (1, 1, 3)*. Lorsque je calcule la somme, le résultat est `` diffusé '' le long des dimensions de taille 1, me donnant un résultat de forme (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]]

La diffusion est une technique très puissante car elle évite la surcharge supplémentaire liée à la création de copies répétées de vos tableaux d'entrée en mémoire.


* Bien que je les ai inclus pour plus de clarté, les Noneindex dans cne sont pas réellement nécessaires - vous pouvez également le faire a[..., None] + c, c'est-à-dire diffuser un (2, 2, 1)tableau sur un (3,)tableau. En effet , si l' un des réseaux a moins de dimensions que l'autre alors que les arrière dimensions des deux réseaux doivent être compatibles. Pour donner un exemple plus compliqué:

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
la source
Pour vérifier que cela donne en effet le bon résultat, vous pouvez imprimer b[:,:,0], b[:,:,1]et b[:,:,2]. Chaque tranche de troisième dimension est une copie du tableau 2D d'origine. Ce n'est pas aussi évident à regarder print(b).
ely
Quelle est la différence entre None et np.newaxis? Quand je l'ai testé, il a donné le même résultat.
monolith
1
@wedran Ils sont exactement les mêmes - np.newaxisest juste un alias deNone
ali_m
27

Une autre façon est d'utiliser numpy.dstack. Supposons que vous souhaitiez répéter les a num_repeatsheures de la matrice :

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

L'astuce consiste à envelopper la matrice adans une liste d'un seul élément, puis à utiliser l' *opérateur pour dupliquer les éléments dans cette liste num_repeatsfois.

Par exemple, si:

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

Cela répète le tableau de [1 2; 1 2]5 fois dans la troisième dimension. Pour vérifier (dans 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)

À la fin, nous pouvons voir que la forme de la matrice est 2 x 2, avec 5 tranches dans la troisième dimension.

Rayryeng
la source
Comment cela se compare- reshapet-il? Plus rapide? donne la même structure? C'est vraiment plus soigné.
Ander Biguri
@AnderBiguri Je n'ai jamais fait de benchmark ... Je l'ai mis ici principalement par souci d'exhaustivité. Il sera intéressant de chronométrer et de voir les différences.
rayryeng
1
Je viens de faire img = np.dstack ([arr] * 3) et j'ai bien fonctionné! Merci
thanos.a
1
Je pensais pouvoir proposer une sortie vue pour l'efficacité. Étant un ancien poste, les gens ont peut-être manqué cela. Ajout d'une solution à ce Q&R.
Divakar
1
IMHO solution la plus lisible, mais il serait bon de la comparer à d'autres méthodes de comparaison.
mrgloom
16

Utilisez une vue et obtenez une exécution gratuite! Étendre les n-dimbaies génériques àn+1-dim

Introduit dans NumPy1.10.0 , nous pouvons utiliser numpy.broadcast_topour générer simplement une 3Dvue dans le 2Dtableau d'entrée. L'avantage serait aucune surcharge de mémoire supplémentaire et une durée d'exécution pratiquement gratuite. Ce serait essentiel dans les cas où les tableaux sont grands et où nous pouvons travailler avec des vues. En outre, cela fonctionnerait avec des n-dimcas génériques .

J'utiliserais le mot stackà la place de copy, car les lecteurs pourraient le confondre avec la copie de tableaux qui crée des copies de mémoire.

Empiler le long du premier axe

Si nous voulons empiler les entrées le arrlong du premier axe, la solution avec np.broadcast_topour créer une 3Dvue serait -

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

Empiler le long du troisième / dernier axe

Pour empiler les entrées le arrlong du troisième axe, la solution pour créer une 3Dvue serait -

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

Si nous avons réellement besoin d'une copie mémoire, nous pouvons toujours .copy()y ajouter . Par conséquent, les solutions seraient -

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

Voici comment fonctionne l'empilement pour les deux cas, montré avec leurs informations de forme pour un exemple de cas -

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

Les mêmes solutions fonctionneraient pour étendre une n-dimentrée pour n+1-dimafficher la sortie le long des premier et dernier axes. Explorons quelques cas plus faibles -

Cas d'entrée 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)

Cas d'entrée 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)

etc.

Timings

Utilisons un grand exemple de 2Dcas, obtenons les horaires et vérifions que la sortie est un view.

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

Prouvons que la solution proposée est bien une vue. Nous utiliserons l'empilement le long du premier axe (les résultats seraient très similaires pour l'empilement le long du troisième axe) -

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

Prenons les horaires pour montrer que c'est pratiquement gratuit -

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

Être une vue, passer Nde 3à 3000rien changé sur les timings et les deux sont négligeables sur les unités de chronométrage. Par conséquent, efficace à la fois sur la mémoire et les performances!

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

Modifier @ Mr.F, pour conserver l'ordre des dimensions:

B=B.T
yevgeniy
la source
Cela se traduit par un tableau N x 2 x 2 pour moi, par exemple B.shapeimprime (N, 2, 2)pour n'importe quelle valeur de N. Si vous transposez Bavec, B.Til correspond à la sortie attendue.
ely
@ Mr.F - Vous avez raison. Cela diffusera le long de la première dimension, ce B[0], B[1],...qui vous donnera la bonne tranche, ce que je soutiendrai et dirai que c'est plus facile à taper qu'à utiliser B[:,:,0], B[:,:,1], etc.
rayryeng
Il peut être plus facile de taper, mais par exemple, si vous faites cela avec des données d'image, ce serait largement incorrect, car presque tous les algorithmes s'attendront à ce que les conventions de l'algèbre linéaire soient utilisées pour les tranches 2D des canaux de pixels. Il est difficile d'imaginer une application où vous commencez avec un tableau 2D, traitant les lignes et les colonnes avec une certaine convention, puis vous voulez plusieurs copies de la même chose s'étendant dans un nouvel axe, mais soudainement vous voulez que le premier axe change de sens en être le nouvel axe ...
ely
@ Mr.F - Oh certainement. Je ne peux pas deviner quelles applications vous voudrez utiliser la matrice 3D à l'avenir. Cela étant dit, tout dépend de l'application. FWIW, je préfère le B[:,:,i]aussi bien que c'est ce à quoi je suis habitué.
rayryeng
2

Voici un exemple de diffusion qui fait exactement ce qui a été demandé.

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

Puis b*aest le résultat souhaité et (b*a)[:,:,0]produit array([[1, 2],[1, 2]]), qui est l'original a, comme le fait (b*a)[:,:,1], etc.

Mike O'Connor
la source
2

Cela peut maintenant également être réalisé en utilisant np.tile comme suit:

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
la source