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

В кратком руководстве о функции 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

Мои вопросы:

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

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

2 ответа

Решение

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

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

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

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

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


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

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

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

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

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

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


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

>>> 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 в новый блок памяти (поскольку он не может вернуть представление исходных данных для этой фигуры).

Может быть, этот пример с 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 может также помочь. Шагом является то, как далеко (в байтах) он должен перейти к следующему значению. Для двумерного массива будет 2 шага:

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)

Транспонировать просто переключает порядок шагов. Следующая строка занимает всего 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 команды.

Другие вопросы по тегам