numpy维度交换_“lazy”的transpose()函数——从numpy 数组的内存布局讲起
1 數(shù)組的兩種內(nèi)存布局方式
行優(yōu)先與列優(yōu)先
首先我們回顧一下,矩陣數(shù)據(jù)在內(nèi)存中的兩種布局方式:
- 行優(yōu)先(row-major):以行為優(yōu)先單位,在內(nèi)存中逐行存儲/讀取;對于多維,意味著當(dāng)線性掃描內(nèi)存時,第一個維度的變化最慢。
- 列優(yōu)先(column-major):以列為優(yōu)先單位,在內(nèi)存中逐列存儲/讀取;對于多維,意味著當(dāng)線性掃描內(nèi)存時,最后一個維度的變化最慢。
以下面的[2, 2, 2]張量為例:
a = [[[1, 2],[3, 4]],[[5, 6],[7, 8]]]在內(nèi)存中的數(shù)據(jù)排布:
行優(yōu)先:1, 2 | 3, 4 || 5, 6 | 7, 8a[0,0] a[0,1] a[1,0] a[1,1] 列優(yōu)先:1, 5 | 3, 7 || 2, 6 | 4, 8a[0,0] a[1,0] a[0,1] a[1,1]誰更好?
選擇行優(yōu)先還是列優(yōu)先,主要取決于我們訪問數(shù)組的模式。由于每次從內(nèi)存中獲取數(shù)據(jù)時,CPU都會自動將該數(shù)據(jù)及其相鄰的內(nèi)存加載到緩存中,希望利用引用的局部性。因此,如果訪問數(shù)組時是逐列訪問的,我們就希望同一列的數(shù)據(jù)在內(nèi)存中靠得更近,便于一次性加載到CPU緩存中從而避免反復(fù)加載,亦即更加的“Cache-friendly”,此時列優(yōu)先顯然是最好的選擇。而對于逐行訪問的情況,則應(yīng)該選擇行優(yōu)先。
C和大多數(shù)DeepLearning庫用的都是行優(yōu)先,而Fortran和matlab等一些用于科學(xué)計算的語言,使用的是列優(yōu)先。不要問為什么,這是歷史的偶然選擇而已。如果要強(qiáng)行解釋,可以說Fortran是考慮到線性代數(shù)中的向量默認(rèn)為列向量,所以用列優(yōu)先與數(shù)學(xué)符號更匹配,雖然用列優(yōu)先并不會加速矩陣運(yùn)算(比如矩陣乘法中第一個矩陣是逐行訪問,第二個是逐列訪問,不可兼得),但是更能顯現(xiàn)出科學(xué)家與眾不同的裝逼特性 :-) 。
2 numpy 中的行優(yōu)先和列優(yōu)先
numpy支持這兩種內(nèi)存布局方式,默認(rèn)采用行優(yōu)先。可以在新建array,或者進(jìn)行reshape等操作時,通過指定order參數(shù)來決定數(shù)據(jù)的內(nèi)存布局方式。
array() 新建
函數(shù)原型:
array(object, dtype=None, copy=True, order='K', subok=False, ndmin=0)參數(shù):
- dtype: 存儲單元格式,有np.float32、np.bool、np.int32等。
- copy: 是否在內(nèi)存中新建array。
- subok: (不用管)If True, then sub-classes will be passed-through, otherwise the returned array will be forced to be a base-class array (default).
- ndmin: 返回的數(shù)組應(yīng)該具有至少ndmin個維數(shù),不足時補(bǔ)充若干個大小為1的維度。
- order: 新建的array在內(nèi)存中的布局方式(該參數(shù)在copy==True時才有意義),從 {‘K’, ‘A’, ‘C’, ‘F’} 中選擇;
舉個例子:
s = [[1,2,3], ['a','b','c']] # python序列采用行優(yōu)先布局 # 內(nèi)存中 s :1, 2, 3, 'a', 'b', 'c'a = np.array(s, order='C') # a.reshape(-1) :'1', '2', '3', 'a', 'b', 'c'b = np.array(s, order='F') # b.reshape(-1) :'1', 'a', '2', 'b', '3', 'c'reshape() 重整維度
函數(shù)原型:
reshape(array, newshape, order='C') array.reshape(newshape, order='C')參數(shù):
- newshape: 一個描述各維度大小的序列,也可以是單個int。
- order: 從 {‘A’, ‘C’, ‘F’} 中選擇。
b = reshape(a, newshape, order)相當(dāng)于:
b = np.array(a, order) # 在內(nèi)存中新建一個 b ,以 order 布局方式存儲從 a 中讀取的數(shù)據(jù) b.shape = newshape # 設(shè)定index指針的計算方式3 “l(fā)azy”的 transpose() 轉(zhuǎn)置
注意,numpy中的轉(zhuǎn)置transpose()是非常“l(fā)azy”的,亦即不對內(nèi)存中的數(shù)據(jù)進(jìn)行重排,僅僅改變讀取方式。
舉個例子:
''' a.shape = [1,2,3] ''' transpose_scheme = [2,1,0] # 維度0與2交換位置 b = np.transpose(a, axes=transpose_scheme) ''' 此時 b.shape 雖然變成了 [3,2,1] 但是 b 與 a 在內(nèi)存的排布是一樣的 '''transpose()等效于:在讀取/寫入函數(shù)函數(shù)外,包了一個能改變維度順序的函數(shù)裝飾器。
def change_axis_order(transpose_scheme):def get_func(func):@wraps(func)def wrapper(self, axes):transposed_axes = [axes[i] for i in transpose_scheme]return func(self, transposed_axes)return wrapperreturn get_func''' b = np.transpose(a, axes=transpose_scheme) 相當(dāng)于: ''' b = a.copy() b.__getitem__ = change_axis_order(transpose_scheme)(b.__getitem__) b.__setitem__ = change_axis_order(transpose_scheme)(b.__setitem__)之所以采用這種“l(fā)azy”的方式,是因為重新在內(nèi)存中排列數(shù)據(jù)的非常耗時的。
如果一定要在內(nèi)存中重新排列數(shù)據(jù),可以采用以下方法:
b = np.zeros_like(a) b[:] = np.array(a, axes=transpose_scheme)總結(jié)
以上是生活随笔為你收集整理的numpy维度交换_“lazy”的transpose()函数——从numpy 数组的内存布局讲起的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 创维S. Q. G. F. E等系列电视
- 下一篇: 北京环球影城一天够玩吗