Непрерывный массив - это просто массив, хранящийся в непрерывном блоке памяти: чтобы получить доступ к следующему значению в массиве, мы просто переходим к следующему адресу памяти.
Рассмотрим 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)
немного быстрее, чем:
np.sum(arr, axis=0)
Точно так же операции со столбцами будут немного быстрее для непрерывных массивов 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 в новый блок памяти (поскольку он не может вернуть представление исходных данных для этой формы).
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 требует постоянной длины шага на каждую ось.arr[:, ::-1]
как это представление того же буфера памятиarr
, что и NumPy не учитывает его порядок C или F, поскольку он перемещает значения в буфере в "нестандартном" порядке ...Возможно, вам поможет этот пример с 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
командам.источник