Понимание функции numpy dstack

У меня есть некоторые проблемы с пониманием того, что делает функция numpy dstack. Документация довольно скудная и просто говорит:

Массивы стека в последовательности глубины (вдоль третьей оси).

Принимает последовательность массивов и складывает их вдоль третьей оси   для создания единого массива. Перестраивает массивы, деленные на dsplit.   Это простой способ укладывать 2D-массивы (изображения) в один   3D-массив для обработки.

Итак, либо я действительно глуп, и смысл этого очевиден, либо у меня, похоже, есть неправильное представление о терминах "укладка", "в последовательности", "глубина" или "вдоль оси". Тем не менее, у меня создалось впечатление, что я правильно поняла эти термины в контексте vstack и hstack.

Возьмем этот пример:

In [193]: a
Out[193]: 
array([[0, 3],
       [1, 4],
       [2, 5]])
In [194]: b
Out[194]: 
array([[ 6,  9],
       [ 7, 10],
       [ 8, 11]])
In [195]: dstack([a,b])
Out[195]: 
array([[[ 0,  6],
        [ 3,  9]],

       [[ 1,  7],
        [ 4, 10]],

       [[ 2,  8],
        [ 5, 11]]])

Прежде всего, a и b не имеют третьей оси, поэтому как бы я их складывал по "третьей оси" для начала? Во-вторых, если предположить, что a и b являются представлениями 2D-изображений, почему в итоге я получаю три 2D-массива в результате вместо двух 2D-массивов "в последовательности"?

Ответы

Ответ 1

Проще понять, что np.vstack, np.hstack и np.dstack * делают, посмотрев на атрибут .shape выходного массива.

Используя ваши два примера массивов:

print(a.shape, b.shape)
# (3, 2) (3, 2)
  • np.vstack объединяется по первому измерению...

    print(np.vstack((a, b)).shape)
    # (6, 2)
    
  • np.hstack объединяется по второму измерению...

    print(np.hstack((a, b)).shape)
    # (3, 4)
    
  • и np.dstack объединяются по третьему измерению.

    print(np.dstack((a, b)).shape)
    # (3, 2, 2)
    

Поскольку a и b оба являются двумерными, np.dstack расширяет их, вставляя третье измерение размера 1. Это эквивалентно индексации их в третьем измерении с помощью np.newaxis (или, альтернативно, None), например это:

print(a[:, :, np.newaxis].shape)
# (3, 2, 1)

Если c = np.dstack((a, b)), то c[:, :, 0] == a и c[:, :, 1] == b.

Вы можете сделать ту же операцию более явно, используя np.concatenate, например:

print(np.concatenate((a[..., None], b[..., None]), axis=2).shape)
# (3, 2, 2)

* Импорт всего содержимого модуля в ваше глобальное пространство имен с использованием import * считается плохой практикой по нескольким причинам. Идиоматический способ заключается в import numpy as np.

Ответ 2

Пусть x == dstack([a, b]). Тогда x[:, :, 0] совпадает с a, а x[:, :, 1] идентично b. В общем случае при dstacking 2D-массивах dstack создает такой вывод, что output[:, :, n] идентичен n-му входному массиву.

Если мы собираем 3D-массивы, а не 2D:

x = numpy.zeros([2, 2, 3])
y = numpy.ones([2, 2, 4])
z = numpy.dstack([x, y])

тогда z[:, :, :3] будет идентичным x, а z[:, :, 3:7] будет идентичным y.

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

Ответ 3

Поскольку вы упоминаете "изображения", я думаю, что этот пример был бы полезен. Если вы используете Keras для обучения 2D-сети свертки с входным X, тогда лучше сохранить X с размером (#images, dim1ofImage, dim2ofImage).

image1 = np.array([[4,2],[5,5]])
image2 = np.array([[3,1],[6,7]])

image1 = image1.reshape(1,2,2)
image2 = image2.reshape(1,2,2)

X = np.stack((image1,image2),axis=1) 
X
array([[[[4, 2],
         [5, 5]],
        [[3, 1],
        [6, 7]]]])

np.shape(X)         
X = X.reshape((2,2,2))   
X 
array([[[4, 2],
        [5, 5]],
       [[3, 1],
        [6, 7]]])

X[0] # image 1
array([[4, 2],
       [5, 5]])
X[1] # image 2
array([[3, 1],
       [6, 7]])             

Ответ 4

Я хотел бы попытаться визуально объяснить это (хотя принятый ответ имеет достаточно смысла, мне потребовалось несколько секунд, чтобы объяснить это разумом). Если представить 2d-массивы в виде списка списков, где 1-я ось дает один из внутренних списков, а 2-я ось дает значение в этом списке, тогда визуальное представление OP-массивов будет таким:

a = [
      [0, 3],
      [1, 4],
      [2, 5]
    ]
b = [
      [6,  9],
      [7, 10],
      [8, 11]
    ]
# Shape of each array is [3,2]

Теперь, согласно текущей документации, функция dstack добавляет третью ось, что означает, что каждый из массивов в конечном итоге будет выглядеть следующим образом:

a = [
      [[0], [3]],
      [[1], [4]],
      [[2], [5]]
    ]
b = [
      [[6],  [9]],
      [[7], [10]],
      [[8], [11]]
    ]
# Shape of each array is [3,2,1]

Теперь объединение обоих этих массивов в 3-е измерение просто означает, что результат должен выглядеть, как и ожидалось, следующим образом:

dstack([a,b]) = [
                  [[0, 6], [3, 9]],
                  [[1, 7], [4, 10]],
                  [[2, 8], [5, 11]]
                ]
# Shape of the combined array is [3,2,2]

Надеюсь, это поможет.