Интуиция и идея перестройки 4D-массива в 2D-массив в NumPy

При реализации Kronecker-product по педагогическим причинам (без использования очевидного и легкодоступного np.kron()) я получил 4-мерную массив как промежуточный результат, который я должен изменить, чтобы получить окончательный результат.

Но, я все еще не могу обернуть голову вокруг изменения этих массивных массивов. У меня есть массив 4D:

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

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


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

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

Это имеет форму (2, 2, 2, 2), и я хотел бы изменить его на (4,4). Можно подумать, что это очевидно с

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

Но приведенное выше изменение не дает мне ожидаемого результата:

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

Как вы можете видеть, все элементы ожидаемого результата присутствуют в массиве 4D. Я просто не могу понять, как правильно изменить форму. В дополнение к ответу, некоторое объяснение того, как сделать reshape для таких массивов с высоким размером, было бы действительно полезно. Спасибо!

Ответ 1

Общая идея для преобразования nd в nd

Идея с таким преобразованием nd to nd использует только две вещи: перенумеровать оси (с помощью numpy.transpose или numpy.rollaxis, если необходимый порядок перестановки является свернутым или numpy.swapaxes, если требуется только две оси для замены) и Reshape.

Разрешенные оси: Чтобы получить порядок, чтобы сглаженная версия соответствовала сглаженной версии вывода. Итак, если вы каким-то образом воспользуетесь им дважды, посмотрите снова, потому что вы не должны.

Reshape: Чтобы разделить оси или довести конечный результат до нужной формы. Разделение осей требуется в основном в начале, когда вход имеет более низкий угол, и нам необходимо разбить на блоки. Опять же, вам не нужно это больше, чем дважды.

Следовательно, обычно мы бы выполнили три шага:

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

 Create more axes             Bring axes             Merge axes
                          into correct order

Метод обратного отслеживания

Самый безопасный способ решения, учитывая вход и выход, - это то, что можно назвать методом обратного отслеживания, т.е. разделить оси входа (при переходе от меньшего nd к большему nd) или разделить оси вывода (при переходе от более крупного nd к меньшему nd). Идея с расщеплением состоит в том, чтобы принести количество тусклых изображений меньшего размера nd так же, как и большее nd. Затем изучите шаги выхода и сопоставьте его с вводом, чтобы получить требуемый порядок перестановки. Наконец, в конце может потребоваться изменение (вариант по умолчанию или порядок C), если последний является меньшим nd, чтобы объединить оси.

Если оба входа и выхода имеют одинаковое количество тускнеет, тогда нам нужно разделить их и разбить на блоки и изучить их шаги друг против друга. В таких случаях у нас должен быть дополнительный входной параметр размеров блока, но это, вероятно, вне темы.

Пример

Позвольте использовать этот конкретный случай, чтобы продемонстрировать, как применять эти стратегии. Здесь вход 4D, а выход - 2D. Так что, скорее всего, нам не понадобится менять форму разделения. Итак, нам нужно начать с перестановочных осей. Поскольку конечный результат не 4D, но a 2D один, нам нужно будет изменить форму в конце.

Теперь вход здесь:

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

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


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

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

Ожидаемый результат:

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

Кроме того, это более крупное преобразование nd to less nd, поэтому метод обратного отслеживания будет включать в себя разделение вывода и изучение его шагов и сопоставление с соответствующими значениями на входе:

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

Следовательно, требуемый переупорядоченный порядок равен (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]]]])

Затем просто перейдем к ожидаемой форме:

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

Дополнительные примеры

Я выкопал свою историю и нашел несколько Q&As на основе преобразований nd to nd. Они могут служить примером для других примеров, хотя и с меньшим объяснением (в основном). Как упоминалось ранее, не более двух reshapes и не более одного swapaxes/transpose выполняли всю работу. Они перечислены ниже:

Ответ 2

Кажется, что вы ищете transpose, за которым следует 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]])

Чтобы понять, зачем нужна перестановка, проанализируйте свой результат неправильной формы (полученный одним вызовом reshape), чтобы понять, почему он неверен.

Простая 2D-модифицированная версия этого результата (без какой-либо транспозиции) выглядит так:

x.reshape(4, 4)

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

Теперь рассмотрим этот вывод в отношении ожидаемого результата -

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

Вы заметите, что ваш фактический результат получен путем Z-подобной обходной части вашего вывода неправильной формы -

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

Это означает, что вы должны перемещаться по массиву с разными шагами, чтобы получить фактический результат. В заключение, простой перестройки недостаточно. Вы должны перенести исходный массив таким образом, чтобы эти Z-подобные элементы были соприкасающимися друг с другом, так что последующий перетасовый вызов дает вам нужный вам результат.

Чтобы понять, как правильно транспонировать, вы должны проследить элементы вдоль ввода и выяснить, какие оси вам нужно прыгать, чтобы добраться до каждого из них. Соответственно следует перестановка. Ответ на Divakar делает простую работу по разъяснению этого.