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)
。這個數組看起來結構是這樣的:
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIwIjNx8CX39CXy8CXycXZpZVZnFWbp9zZuBnLx5GeqdmcoZ2by9CX2EDN4gTNtUGall3LcVmdhNXLwRHdo9CXt92YucWbpRWdvx2Yx5yazF2Lc9CX6MHc0RHaiojIsJye.png)
在計算機的記憶體裡,數組
arr
實際存儲是像下圖所示的:
這意味着
arr
是
C連續的
(
C contiguous
)的,因為在記憶體是行優先的,即某個元素在記憶體中的下一個位置存儲的是它同行的下一個值。
如果想要向下移動一列,則隻需要跳過3個塊既可(例如,從0到4隻需要跳過1,2和3)。
上述數組的轉置
arr.T
則沒有了C連續特性,因為同一行中的相鄰元素現在并不是在記憶體中相鄰存儲的了:
這時候
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
函數将一個記憶體不連續存儲的數組轉換為記憶體連續存儲的數組,使得運作速度更快。