numpy.array shape (R, 1) and (R,) 的區別
翻譯自:stackoverflow 回答 By Gareth Rees
原問題
在 numpy 中,有些運算返回 shape 為 (R,1)
而有些返回 (R,)
。由於需要顯式呼叫 reshape
,這會讓矩陣乘法變得更加繁瑣。舉例來說,假設有一個矩陣 M
,如果我們想執行 numpy.dot(M[:,0],numpy.ones((1,R)))
,其中 R
是行數(當然,換成列會有同樣的問題)。我們會得到 matrices are not aligned
異常,因為 M[:,0]
的 shape 是 (R,)
但是 numpy.ones((1,R))
的 shape 是 (1,R)
。
所以我的問題是:
-
shape
(R,1)
(R,)
有什麼區別。我知道從字面意思上一個是數的 list,另一個是隻包含一個數的 list 組成的 list。只是好奇為什麼不把numpy
設計成更傾向於 shape(R,1)
而不是(R,)
以讓矩陣乘法更簡單。 -
上面的例子有更好的方法嗎?而不用顯式地像這樣呼叫
reshape
:numpy.dot(M[:,0].reshape(R,1),R)))
。
回答
1. NumPy 中 shape 的含義
你寫到,“我知道從字面意思上一個是數的 list,另一個是隻包含一個數的 list 組成的 list”,但是用這種方式去想有點沒啥用。
考慮 NumPy 陣列的最好方式是它們包含兩個部分,一個 資料緩衝區
比如,如果我們建立一個包含 12 個整數的陣列:
>>> a = numpy.arange(12)
>>> a
array([ 0,1,2,3,4,5,6,7,8,9,10,11])
複製程式碼
這時 a
包含一個資料緩衝區,排列如下:
┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┐
│ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │ 9 │ 10 │ 11 │
└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘
複製程式碼
以及描述如何解釋資料的檢視:
>>> a.flags
C_CONTIGUOUS : True
F_CONTIGUOUS : True
OWNDATA : True
WRITEABLE : True
ALIGNED : True
UPDATEIFCOPY : False
>>> a.dtype
dtype('int64')
>>> a.itemsize
8
>>> a.strides
(8,)
>>> a.shape
(12,)
複製程式碼
這裡的 shape (12,)
表示這個陣列由單個索引索引,該索引從 0 到 11。從概念上講,如果我們標記這個索引 i
,陣列 a
看起來像這樣:
i= 0 1 2 3 4 5 6 7 8 9 10 11
┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┐
│ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │ 9 │ 10 │ 11 │
└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘
複製程式碼
如果我們 reshape 一個陣列,它不會改變資料緩衝區。相反,它建立了一個新檢視,描述瞭解釋資料的不同方式。所以當執行下面之後:
>>> b = a.reshape((3,4))
複製程式碼
陣列 b
和 a
有相同的資料緩衝區,但是現在被 兩個 索引索引,其中分別從 0 到 2 和 0 到 3。如果我們標記兩個索引為 i
和 j
,陣列 b
像這樣:
i= 0 0 0 0 1 1 1 1 2 2 2 2
j= 0 1 2 3 0 1 2 3 0 1 2 3
┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┐
│ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │ 9 │ 10 │ 11 │
└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘
複製程式碼
這意味著:
>>> b[2,1]
9
複製程式碼
你可以看到第二個索引改變得快一點,第一個索引改變得慢。如果你希望反過來,可以指定 order
引數:
>>> c = a.reshape((3,4),order='F')
複製程式碼
這會使得陣列索引像這樣:
i= 0 1 2 0 1 2 0 1 2 0 1 2
j= 0 0 0 1 1 1 2 2 2 3 3 3
┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┐
│ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │ 9 │ 10 │ 11 │
└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘
複製程式碼
這意味著:
>>> c[2,1]
5
複製程式碼
現在應該清楚了陣列具有一個或多個維度(dimensions)為 1 的 shape 意味著什麼。經過執行:
>>> d = a.reshape((12,1))
複製程式碼
陣列 d
被兩個索引索引,第一個從 0 到 11,第二個索引全是 0:
i= 0 1 2 3 4 5 6 7 8 9 10 11
j= 0 0 0 0 0 0 0 0 0 0 0 0
┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┐
│ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │ 9 │ 10 │ 11 │
└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘
複製程式碼
所以:
>>> d[10,0]
10
複製程式碼
維度為 1 是 “自由的”(某種意義上來說),所以沒人你能阻止你這麼幹:
>>> e = a.reshape((1,1))
複製程式碼
使得一個陣列的所有變成這樣:
i= 0 0 0 0 0 0 0 0 0 0 0 0
j= 0 0 0 0 0 0 1 1 1 1 1 1
k= 0 0 0 0 0 0 0 0 0 0 0 0
l= 0 1 2 3 4 5 0 1 2 3 4 5
m= 0 0 0 0 0 0 0 0 0 0 0 0
┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┐
│ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │ 9 │ 10 │ 11 │
└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘
複製程式碼
因此:
>>> e[0,0,0]
6
複製程式碼
有關如何實現陣列的更多詳細資訊,請參見 NumPy 內部檔案。
2. 該怎麼辦?
既然 numpy.reshape 只是建立了一個新檢視,那麼在有必要去使用它的時候不要感到害怕。當你想要以不同的方式索引陣列時,這是正確的工具。
然而,在長時間的計算中,通常可以首先安排去構造具有“正確”形狀的陣列,從而最大限度地減少 reshape 和 transpose 的次數。但在沒有看到導致有 reshape 需求的實際背景之前,很難說應該改變什麼。
你的問題中的例子是:
numpy.dot(M[:,0],numpy.ones((1,R)))
複製程式碼
但這是不現實的。首先,這個表示式:
M[:,0].sum()
複製程式碼
可以更簡單地計算結果。其次,第 0 列真的有什麼特別的嗎?也許你真正需要的是:
M.sum(axis=0)
複製程式碼