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

108

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

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

jdeng
источник

Ответы:

230

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

Рассмотрим 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

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

Алекс Райли
источник
Мне трудно это понять, не могли бы вы немного уточнить? В последнем графическом представлении невозможного упорядочивания в памяти, на мой взгляд, шаги постоянны. Например, для перехода от 0 к 1 шаг составляет 1 байт (скажем, каждый элемент является байтом), и он одинаков для каждого столбца. Точно так же шаг составляет 4 байта для перехода от одного элемента в строке к следующему, и он также постоянен.
Весног,
3
@Vesnog при неудачном изменении формы 2D arr2в 1D (12,)используется порядок C, что означает, что ось 1 раскручивается перед осью 0 (т.е. каждая из четырех строк должна быть размещена рядом друг с другом, чтобы создать желаемый массив 1D). Невозможно прочитать эту последовательность целых чисел (0, 4, 8, 1, 5, 9, 2, 6, 10, 3, 7, 11) из буфера, используя постоянную длину шага (байты для перехода к посещению эти элементы в последовательности будут 4, 4, -7, 4, 4, -7, 4, 4, 7, 4, 4). NumPy требует постоянной длины шага на каждую ось.
Алекс Райли,
Сначала спасибо. Я думал, что он создаст новый массив, но он использует память старого.
Весног
@AlexRiley Что происходит за кулисами, когда массив помечен как соседний C или F заказанный? Например, возьмите каждый массив NxD arr и напечатайте (arr [:, :: - 1] .flags). Что происходит в этой ситуации? Я предполагаю, что массив действительно упорядочен по C или F, но какой из них? И какие оптимизации numpy мы теряем, если оба флага ложны?
Jjang
@Jjang: считает ли NumPy массив C или F, порядок полностью зависит от формы и шагов (критерии здесь ). Таким образом, в то время arr[:, ::-1]как это представление того же буфера памяти arr, что и NumPy не учитывает его порядок C или F, поскольку он перемещает значения в буфере в "нестандартном" порядке ...
Алекс Райли
13

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

hpaulj
источник