web-dev-qa-db-fra.com

Intuition et idée derrière la transformation du tableau 4D en tableau 2D dans NumPy

Lors de l’implémentation de Kronecker-product pour raisons pédagogiques (sans utiliser la fonction np.kron() évidente et facilement disponible), j’ai obtenu un tableau à 4 dimensions en tant que résultat intermédiaire, que je dois modifier pour obtenir le résultat final.

Mais, je ne parviens toujours pas à réorganiser ces tableaux de grandes dimensions. J'ai ce tableau 4D:

array([[[[ 0,  0],
         [ 0,  0]],

        [[ 5, 10],
         [15, 20]]],


       [[[ 6, 12],
         [18, 24]],

        [[ 7, 14],
         [21, 28]]]])

C'est de la forme (2, 2, 2, 2) et j'aimerais la remodeler en (4,4). On pourrait penser que cela est évident à faire avec 

np.reshape(my4darr, (4,4))

Mais la transformation ci-dessus ne donne pas le résultat attendu qui est:

array([[ 0,  5,  0, 10],
       [ 6,  7, 12, 14],
       [ 0, 15,  0, 20],
       [18, 21, 24, 28]])

Comme vous pouvez le constater, tous les éléments du résultat attendu sont présents dans le tableau 4D. Je ne peux tout simplement pas comprendre le besoin de faire le reshape correctement. En plus de la réponse, quelques explications sur la façon de faire la variable reshape pour de tels tableaux de grande dimension seraient vraiment utiles. Merci!

14
kmario23

Idée générale pour la transformation de nd à nd

L’idée d’une telle transformation de nd à nd utilise seulement deux choses: axes de permutation (avec numpy.transpose ou numpy.rollaxis si l’ordre de permutation requis est un ordre roulé ou numpy.swapaxes si deux axes doivent être permutés) et Reshape. 

Axes permutés: Pour obtenir un ordre tel que la version aplatie correspond à la version aplatie de la sortie. Donc, si vous finissez par l'utiliser deux fois, regardez à nouveau car vous ne devriez pas.

Reshape: Pour fractionner les axes ou amener la sortie finale à la forme souhaitée. La division des axes est nécessaire surtout au début, lorsque l'entrée est de plus faible intensité et que nous devons nous séparer en blocs. Encore une fois, vous ne devriez pas en avoir besoin plus de deux fois.

Par conséquent, nous aurions généralement trois étapes:

    [ Reshape ]      --->  [ Permute axes ]   --->  [ Reshape ]

 Create more axes             Bring axes             Merge axes
                          into correct order

Méthode de suivi

Le moyen le plus sûr de résoudre, étant donné l’entrée et la sortie, est ce que l’on pourrait appeler la méthode de suivi, c’est-à-dire que vous divisez les axes de l’entrée (lorsque vous passez de nd plus petit à ndgrand__), ou les deux lorsque vous passez de nd plus grand à nd plus petit). L’idée de la scission est de ramener le nombre de dims de la plus petite variable nd à la valeur plus grande nd. Ensuite, étudiez les progrès de la sortie et faites-la correspondre à l'entrée pour obtenir l'ordre de permute requis. Enfin, une refonte (méthode par défaut ou ordre C) peut être nécessaire à la fin, si la dernière est une plus petite variable nd, pour fusionner les axes.

Si les entrées et les sorties ont le même nombre de dims, nous aurions besoin de les scinder, de les diviser en blocs et d’étudier leurs progrès les uns contre les autres. Dans de tels cas, nous devrions avoir le paramètre d'entrée supplémentaire de la taille des blocs, mais c'est probablement hors sujet.

Exemple

Utilisons ce cas spécifique pour montrer comment appliquer ces stratégies. Ici, l'entrée est 4D, tandis que la sortie est 2D. Donc, très probablement, nous n’aurons pas besoin de refaçonner pour nous séparer. Nous devons donc commencer par les axes permutants. Puisque la sortie finale n’est pas 4D, mais un 2D, nous aurions besoin d’un remodelage à la fin.

Maintenant, l'entrée est la suivante:

In [270]: a
Out[270]: 
array([[[[ 0,  0],
         [ 0,  0]],

        [[ 5, 10],
         [15, 20]]],


       [[[ 6, 12],
         [18, 24]],

        [[ 7, 14],
         [21, 28]]]])

Le résultat attendu est:

In [271]: out
Out[271]: 
array([[ 0,  5,  0, 10],
       [ 6,  7, 12, 14],
       [ 0, 15,  0, 20],
       [18, 21, 24, 28]])

De plus, il s’agit d’une transformation nd à __ plus petite nd plus petite. La méthode de suivi en retour impliquerait donc de scinder la sortie et d’étudier ses progrès et de faire correspondre les valeurs correspondantes en entrée:

                    axis = 3
                   ---      -->          

                    axis = 1                    
                   ------>           
axis=2|  axis=0|   [ 0,  5,  0, 10],        

               |   [ 6,  7, 12, 14],
               v  
      |            [ 0, 15,  0, 20],
      v
                   [18, 21, 24, 28]])

Par conséquent, l'ordre permuté nécessaire est (2,0,3,1):

In [275]: a.transpose((2, 0, 3, 1))
Out[275]: 
array([[[[ 0,  5],
         [ 0, 10]],

        [[ 6,  7],
         [12, 14]]],


       [[[ 0, 15],
         [ 0, 20]],

        [[18, 21],
         [24, 28]]]])

Ensuite, il suffit de remodeler à la forme attendue:

In [276]: a.transpose((2, 0, 3, 1)).reshape(4,4)
Out[276]: 
array([[ 0,  5,  0, 10],
       [ 6,  7, 12, 14],
       [ 0, 15,  0, 20],
       [18, 21, 24, 28]])

Plus d'exemples

J'ai déterré mon historique et trouvé quelques Q&As sur la base de transformations nd à nd. Celles-ci pourraient servir d’autres exemples, avec une explication moindre (la plupart du temps). Comme mentionné précédemment, au plus deux reshapes et au plus une swapaxes/transpose ont fait le travail partout. Ils sont énumérés ci-dessous:

24
Divakar

Il semble que vous cherchiez un transpose suivi d'un reshape

x.transpose((2, 0, 3, 1)).reshape(np.prod(x.shape[:2]), -1)

array([[ 0,  5,  0, 10],
       [ 6,  7, 12, 14],
       [ 0, 15,  0, 20],
       [18, 21, 24, 28]])

Pour vous aider à comprendre pourquoi une transposition est nécessaire, analysons votre sortie de forme incorrecte (obtenue par un seul appel reshape) pour comprendre pourquoi elle est incorrecte.

Une version 2D remodelée simple de ce résultat (sans aucune transposition) ressemble à ceci - 

x.reshape(4, 4)

array([[ 0,  0,  0,  0],
       [ 5, 10, 15, 20],
       [ 6, 12, 18, 24],
       [ 7, 14, 21, 28]])

Considérons maintenant cette sortie par rapport à votre sortie attendue - 

array([[ 0,  5,  0, 10],
       [ 6,  7, 12, 14],
       [ 0, 15,  0, 20],
       [18, 21, 24, 28]])

Vous remarquerez que votre résultat réel est obtenu par une traversée en Z de votre sortie de forme incorrecte - 

start
    | /|     /| /|
    |/ |    / |/ |
      /    /    / 
     /    /    /
    | /| /    | /|
    |/ |/     |/ |
                 end

Cela implique que vous devez vous déplacer sur le tableau à des degrés divers pour obtenir votre résultat actuel. En conclusion, une simple refonte ne suffit pas. Vous devez transposer le tableau d'origine, de manière à ce que ces éléments de type Z soient contigus l'un à l'autre, de sorte qu'un appel ultérieur à la mise en forme vous fournisse le résultat souhaité.

Pour comprendre comment transposer correctement, tracez les éléments le long de l'entrée et déterminez les axes à sauter pour accéder à chacun d'eux dans la sortie. La transposition suit en conséquence. La réponse de Divakar fait un travail remarquable pour expliquer cela.

11
coldspeed

La réponse de Divarkar est excellente , bien qu'il soit parfois plus facile pour moi de vérifier tous les cas possibles que transpose et reshape couvrent.

Par exemple, le code suivant

n, m = 4, 2
arr = np.arange(n*n*m*m).reshape(n,n,m,m)
for permut in itertools.permutations(range(4)):
    arr2 = (arr.transpose(permut)).reshape(n*m, n*m)
    print(permut, arr2[0])

me donne tout ce que l'on peut obtenir d'un tableau à 4 dimensions en utilisant transpose + reshape. Depuis, je sais à quoi la sortie devrait ressembler, je vais simplement choisir la permutation qui m'a montré la bonne réponse. Si je n'ai pas obtenu ce que je voulais, alors transpose + reshape n'est pas assez général pour couvrir mon cas et je dois faire quelque chose de plus compliqué.

0
cheyp