Подтвердить что ты не робот

В чем разница между смежными и несмежными массивами?

В numpy manual о функции reshape() говорится:

>>> a = np.zeros((10, 2))
# A transpose make the array non-contiguous
>>> b = a.T
# Taking a view makes it possible to modify the shape without modifying the
# initial object.
>>> c = b.view()
>>> c.shape = (20)
AttributeError: incompatible shape for a non-contiguous array

Мои вопросы:

  • Что такое непрерывные и несмежные массивы? Это похоже на смежный блок памяти в C, например Что такое смежный блок памяти?
  • Есть ли разница в производительности между этими двумя? Когда мы должны использовать один или другой?
  • Почему transpose делает массив несмежным?
  • Почему c.shape = (20) выдает сообщение об ошибке incompatible shape for a non-contiguous array?

Спасибо за ваш ответ!

4b9b3361

Ответ 1

Смежный массив - это просто массив, хранящийся в непрерывном блоке памяти: для доступа к следующему значению в массиве мы просто переходим к следующему адресу памяти.

Рассмотрим 2D-массив arr = np.arange(12).reshape(3,4). Это выглядит так:

введите описание изображения здесь

В памяти компьютера значения arr сохраняются следующим образом:

введите описание изображения здесь

Это означает, что arr является массивом C непрерывным, поскольку строки хранятся как непрерывные блоки памяти. Следующий адрес памяти содержит следующее значение строки в этой строке. Если мы хотим двигаться вниз по столбцу, нам просто нужно перепрыгнуть через три блока (например, перепрыгнуть с 0 до 4, значит, мы пропустим более 1,2 и 3).

Транспонирование массива с помощью arr.T означает, что смежность C теряется, потому что смежные записи строк больше не находятся в смежных адресах памяти. Тем не менее, arr.T Fortran смежный, поскольку столбцы находятся в смежных блоках памяти:

введите описание изображения здесь


Производительность, доступ к адресам памяти, которые находятся рядом друг с другом, очень часто быстрее, чем доступ к адресам, которые более "разбросаны" (выборка из ОЗУ может повлечь за собой получение и смещение соседних адресов для ЦП.) Это означает, что операции над смежными массивами часто бывают быстрее.

Вследствие смежности памяти C последовательные операции обычно выполняются быстрее, чем операции с столбцами. Например, вы обычно обнаружите, что

np.sum(arr, axis=1) # sum the rows

немного быстрее, чем:

np.sum(arr, axis=0) # sum the columns

Аналогично, операции с столбцами будут немного быстрее для непрерывных массивов Fortran.


Наконец, почему мы не можем сгладить смежный массив Fortran, назначив новую форму?

>>> arr2 = arr.T
>>> arr2.shape = 12
AttributeError: incompatible shape for a non-contiguous array

Чтобы это было возможно, NumPy должен был бы свести строки arr.T следующим образом:

введите описание изображения здесь

(Установка атрибута shape напрямую предполагает C-порядок - то есть NumPy пытается выполнить операцию по строке.)

Это невозможно сделать. Для любой оси NumPy должен иметь постоянную длину шага (количество байтов для перемещения), чтобы перейти к следующему элементу массива. Сжатие arr.T таким образом потребовало бы пропускания вперед и назад в памяти для получения последовательных значений массива.

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

Ответ 2

Возможно, этот пример с 12 различными значениями массива поможет:

In [207]: x=np.arange(12).reshape(3,4).copy()

In [208]: x.flags
Out[208]: 
  C_CONTIGUOUS : True
  F_CONTIGUOUS : False
  OWNDATA : True
  ...
In [209]: x.T.flags
Out[209]: 
  C_CONTIGUOUS : False
  F_CONTIGUOUS : True
  OWNDATA : False
  ...

Значения C order находятся в том порядке, в котором они были сгенерированы. Транспонированные не являются

In [212]: x.reshape(12,)   # same as x.ravel()
Out[212]: array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11])

In [213]: x.T.reshape(12,)
Out[213]: array([ 0,  4,  8,  1,  5,  9,  2,  6, 10,  3,  7, 11])

Вы можете получить 1d-представление обоих

In [214]: x1=x.T

In [217]: x.shape=(12,)

можно изменить форму x.

In [220]: x1.shape=(12,)
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-220-cf2b1a308253> in <module>()
----> 1 x1.shape=(12,)

AttributeError: incompatible shape for a non-contiguous array

Но форма транспонирования не может быть изменена. data все еще находится в порядке 0,1,2,3,4..., к которому невозможно получить доступ как 0,4,8... в массиве 1d.

Но копия x1 может быть изменена:

In [227]: x2=x1.copy()

In [228]: x2.flags
Out[228]: 
  C_CONTIGUOUS : True
  F_CONTIGUOUS : False
  OWNDATA : True
  ...
In [229]: x2.shape=(12,)

Также может помочь просмотр strides. Шаги - это то, как далеко (в байтах) он должен шагнуть, чтобы перейти к следующему значению. Для 2d-массива будут два значения шага:

In [233]: x=np.arange(12).reshape(3,4).copy()

In [234]: x.strides
Out[234]: (16, 4)

Чтобы перейти к следующей строке, шаг 16 байт, следующий столбец всего 4.

In [235]: x1.strides
Out[235]: (4, 16)

Transpose просто переключает порядок шагов. Следующая строка - всего 4 байта, т.е. Следующее число.

In [236]: x.shape=(12,)

In [237]: x.strides
Out[237]: (4,)

Изменение формы также изменяет шаг - просто шаг за шагом 4 байта за раз.

In [238]: x2=x1.copy()

In [239]: x2.strides
Out[239]: (12, 4)

Даже если x2 выглядит так же, как x1, он имеет свой собственный буфер данных со значениями в другом порядке. Следующий столбец теперь имеет 4 байта, а следующая строка - 12 (3 * 4).

In [240]: x2.shape=(12,)

In [241]: x2.strides
Out[241]: (4,)

И как при x, изменение формы на 1d уменьшает шаги до (4,).

Для x1, с данными в порядке 0,1,2,..., нет 1d-шага, который дал бы 0,4,8....

__array_interface__ - еще один полезный способ отображения информации о массиве:

In [242]: x1.__array_interface__
Out[242]: 
{'strides': (4, 16),
 'typestr': '<i4',
 'shape': (4, 3),
 'version': 3,
 'data': (163336056, False),
 'descr': [('', '<i4')]}

Адрес буфера данных x1 будет таким же, как и для x, с которым он разделяет данные. x2 имеет другой адрес буфера.

Вы также можете поэкспериментировать с добавлением параметра order='F' в команды copy и reshape.