天天看點

從Numpy中的ascontiguousarray說起

1. 概述

在使用Numpy的時候,有時候會遇到下面的錯誤:

AttributeError: incompatible shape for a non-contiguous array           

複制

看報錯的字面意思,好像是不連續數組的shape不相容。

有的時候,在看别人代碼時會時不時看到

ascontiguous()

這樣的一個函數,查文檔會發現函數說明隻有一句話:“Return a contiguous array (ndim >= 1) in memory (C order).”

光靠這些資訊,似乎沒能道出Numpy裡面contiguous array和non-contiguous array有什麼差別,以及為什麼需要進行

ascontiguous

操作?帶着這些疑問,我搜了比較多的資料,在stack overflow上發現一個比較詳細的回答,簡單明白地将Numpy裡面的數組的連續性問題解釋清楚了,是以這裡翻譯過來,希望能幫助到别的有同樣疑問的小夥伴。

2. 額外知識: C order vs Fortran order

所謂

C order

,指的是行優先的順序(Row-major Order),即記憶體中同行的存在一起,而

Fortran Order

則指的是列優先的順序(Column-major Order),即記憶體中同列的存在一起。這種命名方式是根據C語言和Fortran語言中數組在記憶體中的存儲方式不同而來的。Pascal, C,C++,Python都是行優先存儲的,而Fortran,MatLab是列優先存儲的。

3. 譯文

所謂

contiguous array

,指的是數組在記憶體中存放的位址也是連續的(注意記憶體位址實際是一維的),即通路數組中的下一個元素,直接移動到記憶體中的下一個位址就可以。

考慮一個2維數組

arr = np.arange(12).reshape(3,4)

。這個數組看起來結構是這樣的:

從Numpy中的ascontiguousarray說起

在計算機的記憶體裡,數組

arr

實際存儲是像下圖所示的:

從Numpy中的ascontiguousarray說起

這意味着

arr

C連續的

C contiguous

)的,因為在記憶體是行優先的,即某個元素在記憶體中的下一個位置存儲的是它同行的下一個值。

如果想要向下移動一列,則隻需要跳過3個塊既可(例如,從0到4隻需要跳過1,2和3)。

上述數組的轉置

arr.T

則沒有了C連續特性,因為同一行中的相鄰元素現在并不是在記憶體中相鄰存儲的了:

從Numpy中的ascontiguousarray說起

這時候

arr.T

變成了

Fortran 連續的

Fortran contiguous

),因為相鄰列中的元素在記憶體中相鄰存儲的了。

從性能上來說,擷取記憶體中相鄰的位址比不相鄰的位址速度要快很多(從RAM讀取一個數值的時候可以連着一起讀一塊位址中的數值,并且可以儲存在Cache中)。這意味着對連續數組的操作會快很多。

由于

arr

是C連續的,是以對其進行行操作比進行列操作速度要快,例如,通常來說

np.sum(arr, axis=1) # 按行求和           

複制

會比

np.sum(arr, axis=0) # 按列求和           

複制

稍微快些。

同理,在

arr.T

上,列操作比行操作會快些。

4. 補充

Numpy中,随機初始化的數組預設都是C連續的,經過不規則的

slice

操作,則會改變連續性,可能會變成既不是C連續,也不是Fortran連續的。

Numpy可以通過

.flags

熟悉檢視一個數組是C連續還是Fortran連續的

>>> import numpy as np
>>> arr = np.arange(12).reshape(3, 4)
>>> arr.flags
  C_CONTIGUOUS : True
  F_CONTIGUOUS : False
  OWNDATA : False
  WRITEABLE : True
  ALIGNED : True
  WRITEBACKIFCOPY : False
  UPDATEIFCOPY : False           

複制

從輸出可以看到數組

arr

是C連續的。

arr

進行按列的

slice

操作,不改變每行的值,則還是C連續的:

>>> arr
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11]])
>>> arr1 = arr[:3, :]
>>> arr1
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11]])
>>> arr1.flags
  C_CONTIGUOUS : True
  F_CONTIGUOUS : False
  OWNDATA : False
  WRITEABLE : True
  ALIGNED : True
  WRITEBACKIFCOPY : False
  UPDATEIFCOPY : False           

複制

如果進行在行上的

slice

,則會改變連續性,成為既不C連續,也不Fortran連續的:

>>> arr1 = arr[:, 1:3]
>>> arr1.flags
  C_CONTIGUOUS : False
  F_CONTIGUOUS : False
  OWNDATA : False
  WRITEABLE : True
  ALIGNED : True
  WRITEBACKIFCOPY : False
  UPDATEIFCOPY : False           

複制

此時利用

ascontiguousarray

函數,可以将其變為連續的:

>>> arr2 = np.ascontiguousarray(arr1)
>>> arr2.flags
  C_CONTIGUOUS : True
  F_CONTIGUOUS : False
  OWNDATA : True
  WRITEABLE : True
  ALIGNED : True
  WRITEBACKIFCOPY : False
  UPDATEIFCOPY : False           

複制

可以這樣認為,

ascontiguousarray

函數将一個記憶體不連續存儲的數組轉換為記憶體連續存儲的數組,使得運作速度更快。