Почему NumPy создает представление для x [[slice (None), 1, 2]] - PullRequest
0 голосов
/ 03 июня 2018

В документации NumPy для расширенного индексирования упоминается, что

Также следует признать, что x[[1, 2, 3]] вызовет расширенное индексирование, тогда как x[[1, 2, slice(None)]] вызовет базовое срезание.

Матрица последовательно сохраняется в памяти.Я понимаю, что имеет смысл взглянуть на x[[1, 2, slice(None)]], поскольку элементы хранятся последовательно в памяти.Но почему Numpy возвращает представление x[[1, slice(None), 2]] или x[[slice(None), 1, 2]].Например, предположим, что

x = [[[ 0,  1,  2],
      [ 3,  4,  5],
      [ 6,  7,  8]],
     [[ 9, 10, 11],
      [12, 13, 14],
      [15, 16, 17]],
     [[18, 19, 20],
      [21, 22, 23],
      [24, 25, 26]]]

x[[1, slice(None), 2]] возвращает представление [11, 14, 17], которое не сохраняется последовательно в памяти, а также x[[slice(None), 1, 2]], которое возвращает [5, 14, 23].

Я хотел бы знать

  1. Почему NumPy даже возвращает представление в этих двух случаях

  2. Как NumPy обрабатывает адресацию памяти для создания этих представлений

Ответы [ 2 ]

0 голосов
/ 03 июня 2018

Мне нравится использовать __array_interface__ для проверки атрибутов массива:

С вашим x:

In [51]: x.__array_interface__
Out[51]: 
{'data': (43241792, False),
 'strides': None,
 'descr': [('', '<i8')],
 'typestr': '<i8',
 'shape': (3, 3, 3),
 'version': 3}
In [52]: x.strides
Out[52]: (72, 24, 8)

Это (3,3,3) массив.Последнюю ось можно сканировать, шагая по 8 байтов за раз, размер x.itemsize.3 * 8 шагов рядов и 3 * 3 * 8 шагов через плоскости (1-й размер).

In [53]: y = x[:,1,2]
In [54]: y.shape
Out[54]: (3,)
In [55]: y.strides
Out[55]: (72,)
In [56]: y.__array_interface__['data']
Out[56]: (43241832, False)

y элементы могут быть адресованы пошаговым переходом по плоскостям, 3 * 3 * 8.43241832 является отправной точкой, 40 байтов в буфер данных, 5 * 8

In [59]: y
Out[59]: array([ 5, 14, 23])

Таким образом, он начинается с 5-го элемента и перемещается вперед на одну плоскость за раз (9 элементов), всего3 элемента.

Тот факт, что y.__array_interface__['data'] попадает в диапазон x «данных», говорит мне, что y является представлением.Это представление, потому что комбинация этой начальной точки буфера, шагов и формы позволяет нам получить доступ ко всем значениям y.

С расширенным индексированием невозможно (в общем) получить доступ кэлементы с этими простыми параметрами, поэтому numpy должен сделать копию данных.


Обратное представление возможно только путем изменения шагов и начальной точки «данных»:

In [60]: z = y[::-1]
In [61]: z.__array_interface__
Out[61]: 
{'data': (43241976, False),
 'strides': (-72,),
 'descr': [('', '<i8')],
 'typestr': '<i8',
 'shape': (3,),
 'version': 3}

Транспонировать и изменения шагов:

In [62]: x.T.strides
Out[62]: (8, 24, 72)
0 голосов
/ 03 июня 2018

Из поваренной книги SciPy :

Основное правило для создания вида среза заключается в том, что к просматриваемым элементам можно обращаться со смещениями, шагами и количеством в оригинале.массив.

Когда у вас есть индексирование, подобное x[[1, slice(None), 2]], вы получаете представление, потому что разрезание по всей оси допускает определенное смещение, шаг и счет для представления среза с исходным массивом.

Например, с x = np.arange(27).reshape(3, 3, 3).copy() мы имеем:

In [79]: x_view = x[1, :, 2]  # or equivalently x[[1, slice(None), 2]]

In [80]: x_view
Out[80]: array([11, 14, 17])

In [81]: x_view.base
Out[81]: 
array([[[ 0,  1,  2],
        [ 3,  4,  5],
        [ 6,  7,  8]],

       [[ 9, 10, 11],
        [12, 13, 14],
        [15, 16, 17]],

       [[18, 19, 20],
        [21, 22, 23],
        [24, 25, 26]]])

Тогда мы можем использовать numpy.byte_bounds (не является частью публичного API, YMMV) дляпроиллюстрируем смещение, чтобы получить наш фрагмент из нашего исходного массива.

In [82]: np.byte_bounds(x_view)[0] - np.byte_bounds(x_view.base)[0]
Out[82]: 88

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

In [93]: (x.strides * np.array([1, 0, 2])).sum()
Out[93]: 88

Шаги в нашем срезе просто становятся такими, какими были шаги для x вдоль оси (или осей), на которой мы нарезаем.то есть x.strides[1] == x_view.strides[0].Теперь вместе смещение, новые шаги и количество являются достаточной информацией для NumPy, чтобы просмотреть наш срез из нашего исходного массива.

In [94]: x_view.strides
Out[94]: (24,)

In [95]: x_view.size
Out[95]: 3

Наконец, причина, по которой вы запускаете необычное индексирование, например, с помощью x[[0, 1, 2]], заключается в том, что в отсутствие полного среза оси, как правило, невозможно сформулировать какое-либо новое смещение, порядок байтов,шагает и считает так, что мы можем просматривать срез с теми же базовыми данными.

...