Ответ 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
.